Astrid Collaboration Project
- sync with Astrid.com server - people activity for delegating and sharing tasks - shared tags activity for adding users to tags - c2dm push notificationspull/14/head
After Width: | Height: | Size: 1.0 KiB |
@ -0,0 +1,224 @@
|
||||
package com.todoroo.andlib.utility;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Stack;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.todoroo.astrid.api.R;
|
||||
|
||||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
public class ImageLoader {
|
||||
|
||||
// the simplest in-memory cache implementation. This should be replaced with
|
||||
// something like SoftReference or BitmapOptions.inPurgeable(since 1.6)
|
||||
private final HashMap<String, Uri> cache = new HashMap<String, Uri>();
|
||||
|
||||
private File cacheDir;
|
||||
|
||||
public ImageLoader(Context context) {
|
||||
// Make the background thread low priority. This way it will not affect
|
||||
// the UI performance
|
||||
photoLoaderThread.setPriority(Thread.NORM_PRIORITY - 1);
|
||||
|
||||
// Find the dir to save cached images
|
||||
if (android.os.Environment.getExternalStorageState().equals(
|
||||
android.os.Environment.MEDIA_MOUNTED))
|
||||
cacheDir = new File(
|
||||
android.os.Environment.getExternalStorageDirectory(),
|
||||
"Android/data/com.todoroo.astrid/cache/"); //$NON-NLS-1$
|
||||
else
|
||||
cacheDir = context.getCacheDir();
|
||||
if (!cacheDir.exists())
|
||||
cacheDir.mkdirs();
|
||||
}
|
||||
|
||||
final int stub_id = R.drawable.image_placeholder;
|
||||
|
||||
public void displayImage(String url, ImageView imageView) {
|
||||
if (cache.containsKey(url))
|
||||
imageView.setImageURI(cache.get(url));
|
||||
else {
|
||||
queuePhoto(url, imageView);
|
||||
imageView.setImageResource(stub_id);
|
||||
}
|
||||
}
|
||||
|
||||
private void queuePhoto(String url, ImageView imageView) {
|
||||
// This ImageView may be used for other images before. So there may be
|
||||
// some old tasks in the queue. We need to discard them.
|
||||
photosQueue.Clean(imageView);
|
||||
PhotoToLoad p = new PhotoToLoad(url, imageView);
|
||||
synchronized (photosQueue.photosToLoad) {
|
||||
photosQueue.photosToLoad.push(p);
|
||||
photosQueue.photosToLoad.notifyAll();
|
||||
}
|
||||
|
||||
// start thread if it's not started yet
|
||||
if (photoLoaderThread.getState() == Thread.State.NEW)
|
||||
photoLoaderThread.start();
|
||||
}
|
||||
|
||||
private Uri getUri(String url) {
|
||||
if(!TextUtils.isEmpty(url) && url.contains("://")) { //$NON-NLS-1$
|
||||
// identify images by hashcode. Not a perfect solution.
|
||||
String filename = String.valueOf(url.hashCode());
|
||||
File f = new File(cacheDir, filename);
|
||||
|
||||
// from SD cache
|
||||
if (f.exists()) {
|
||||
Uri b = Uri.fromFile(f);
|
||||
System.out.println(f.toString());
|
||||
return b;
|
||||
}
|
||||
|
||||
// from web
|
||||
try {
|
||||
Uri bitmap = null;
|
||||
InputStream is = new URL(url).openStream();
|
||||
OutputStream os = new FileOutputStream(f);
|
||||
AndroidUtilities.copyStream(is, os);
|
||||
os.close();
|
||||
bitmap = Uri.fromFile(f);
|
||||
System.out.println(f.toString());
|
||||
return bitmap;
|
||||
} catch (Exception e) {
|
||||
Log.e("imagel-loader", "Unable to get URL", e); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Task for the queue
|
||||
private class PhotoToLoad {
|
||||
public String url;
|
||||
public ImageView imageView;
|
||||
|
||||
public PhotoToLoad(String u, ImageView i) {
|
||||
url = u;
|
||||
imageView = i;
|
||||
imageView.setTag(url);
|
||||
}
|
||||
}
|
||||
|
||||
PhotosQueue photosQueue = new PhotosQueue();
|
||||
|
||||
public void stopThread() {
|
||||
photoLoaderThread.interrupt();
|
||||
}
|
||||
|
||||
// stores list of photos to download
|
||||
class PhotosQueue {
|
||||
private final Stack<PhotoToLoad> photosToLoad = new Stack<PhotoToLoad>();
|
||||
|
||||
// removes all instances of this ImageView
|
||||
public void Clean(ImageView image) {
|
||||
for (int j = 0; j < photosToLoad.size();) {
|
||||
if (photosToLoad.get(j).imageView == image)
|
||||
photosToLoad.remove(j);
|
||||
else
|
||||
++j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PhotosLoader extends Thread {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
while (true) {
|
||||
// thread waits until there are any images to load in the
|
||||
// queue
|
||||
if (photosQueue.photosToLoad.size() == 0)
|
||||
synchronized (photosQueue.photosToLoad) {
|
||||
photosQueue.photosToLoad.wait();
|
||||
}
|
||||
if (photosQueue.photosToLoad.size() != 0) {
|
||||
PhotoToLoad photoToLoad;
|
||||
synchronized (photosQueue.photosToLoad) {
|
||||
photoToLoad = photosQueue.photosToLoad.pop();
|
||||
}
|
||||
Uri bmp = getUri(photoToLoad.url);
|
||||
cache.put(photoToLoad.url, bmp);
|
||||
if (((String) photoToLoad.imageView.getTag()).equals(photoToLoad.url)) {
|
||||
UriDisplayer bd = new UriDisplayer(bmp,
|
||||
photoToLoad.imageView);
|
||||
Activity a = (Activity) photoToLoad.imageView.getContext();
|
||||
a.runOnUiThread(bd);
|
||||
}
|
||||
}
|
||||
if (Thread.interrupted())
|
||||
break;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// allow thread to exit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PhotosLoader photoLoaderThread = new PhotosLoader();
|
||||
|
||||
class UriDisplayer implements Runnable {
|
||||
Uri uri;
|
||||
ImageView imageView;
|
||||
|
||||
public UriDisplayer(Uri u, ImageView i) {
|
||||
uri = u;
|
||||
imageView = i;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
if(uri == null)
|
||||
return;
|
||||
|
||||
File f = new File(uri.getPath());
|
||||
if (f.exists()) {
|
||||
imageView.setImageURI(Uri.parse(f.toString()));
|
||||
} else {
|
||||
imageView.setImageResource(stub_id);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void clearCache() {
|
||||
// clear memory cache
|
||||
cache.clear();
|
||||
|
||||
// clear SD cache
|
||||
File[] files = cacheDir.listFiles();
|
||||
for (File f : files)
|
||||
f.delete();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package com.todoroo.astrid.data;
|
||||
|
||||
import com.todoroo.andlib.data.AbstractModel;
|
||||
import com.todoroo.andlib.data.Property.LongProperty;
|
||||
import com.todoroo.andlib.data.Property.StringProperty;
|
||||
|
||||
/**
|
||||
* A model that is synchronized to a remote server and has a remote id
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
abstract public class RemoteModel extends AbstractModel {
|
||||
|
||||
/** remote id property common to all remote models */
|
||||
public static final String REMOTE_ID_PROPERTY_NAME = "remoteId"; //$NON-NLS-1$
|
||||
|
||||
/** remote id property */
|
||||
public static final LongProperty REMOTE_ID_PROPERTY = new LongProperty(null, REMOTE_ID_PROPERTY_NAME);
|
||||
|
||||
/** user id property common to all remote models */
|
||||
protected static final String USER_ID_PROPERTY_NAME = "userId"; //$NON-NLS-1$
|
||||
|
||||
/** user id property */
|
||||
public static final LongProperty USER_ID_PROPERTY = new LongProperty(null, USER_ID_PROPERTY_NAME);
|
||||
|
||||
/** user json property common to all remote models */
|
||||
protected static final String USER_JSON_PROPERTY_NAME = "user"; //$NON-NLS-1$
|
||||
|
||||
/** user json property */
|
||||
public static final StringProperty USER_JSON_PROPERTY = new StringProperty(null, USER_JSON_PROPERTY_NAME);
|
||||
|
||||
}
|
@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Todoroo Inc
|
||||
* All Rights Reserved
|
||||
* http://www.todoroo.com
|
||||
*/
|
||||
package com.todoroo.astrid.data;
|
||||
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.net.Uri;
|
||||
|
||||
import com.todoroo.andlib.data.AbstractModel;
|
||||
import com.todoroo.andlib.data.Property;
|
||||
import com.todoroo.andlib.data.Property.IntegerProperty;
|
||||
import com.todoroo.andlib.data.Property.LongProperty;
|
||||
import com.todoroo.andlib.data.Property.StringProperty;
|
||||
import com.todoroo.andlib.data.Table;
|
||||
import com.todoroo.andlib.data.TodorooCursor;
|
||||
import com.todoroo.astrid.api.AstridApiConstants;
|
||||
|
||||
/**
|
||||
* Data Model which represents a collaboration space for users / tasks.
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public final class TagData extends RemoteModel {
|
||||
|
||||
// --- table and uri
|
||||
|
||||
/** table for this model */
|
||||
public static final Table TABLE = new Table("tagdata", TagData.class);
|
||||
|
||||
/** content uri for this model */
|
||||
public static final Uri CONTENT_URI = Uri.parse("content://" + AstridApiConstants.PACKAGE + "/" +
|
||||
TABLE.name);
|
||||
|
||||
// --- properties
|
||||
|
||||
/** ID */
|
||||
public static final LongProperty ID = new LongProperty(
|
||||
TABLE, ID_PROPERTY_NAME);
|
||||
|
||||
/** User id */
|
||||
public static final LongProperty USER_ID = new LongProperty(
|
||||
TABLE, USER_ID_PROPERTY_NAME);
|
||||
|
||||
/** User Object (JSON) */
|
||||
public static final StringProperty USER = new StringProperty(
|
||||
TABLE, USER_JSON_PROPERTY_NAME);
|
||||
|
||||
/** Remote goal id */
|
||||
public static final LongProperty REMOTE_ID = new LongProperty(
|
||||
TABLE, REMOTE_ID_PROPERTY_NAME);
|
||||
|
||||
/** Name of Tag */
|
||||
public static final StringProperty NAME = new StringProperty(
|
||||
TABLE, "name");
|
||||
|
||||
/** Project picture */
|
||||
public static final StringProperty PICTURE = new StringProperty(
|
||||
TABLE, "picture");
|
||||
|
||||
/** Tag team array (JSON) */
|
||||
public static final StringProperty MEMBERS = new StringProperty(
|
||||
TABLE, "members");
|
||||
|
||||
/** Tag member count */
|
||||
public static final IntegerProperty MEMBER_COUNT = new IntegerProperty(
|
||||
TABLE, "memberCount");
|
||||
|
||||
/** Flags */
|
||||
public static final IntegerProperty FLAGS = new IntegerProperty(
|
||||
TABLE, "flags");
|
||||
|
||||
/** Unixtime Project was created */
|
||||
public static final LongProperty CREATION_DATE = new LongProperty(
|
||||
TABLE, "created");
|
||||
|
||||
/** Unixtime Project was last touched */
|
||||
public static final LongProperty MODIFICATION_DATE = new LongProperty(
|
||||
TABLE, "modified");
|
||||
|
||||
/** Unixtime Project was completed. 0 means active */
|
||||
public static final LongProperty COMPLETION_DATE = new LongProperty(
|
||||
TABLE, "completed");
|
||||
|
||||
/** Unixtime Project was deleted. 0 means not deleted */
|
||||
public static final LongProperty DELETION_DATE = new LongProperty(
|
||||
TABLE, "deleted");
|
||||
|
||||
/** Project picture thumbnail */
|
||||
public static final StringProperty THUMB = new StringProperty(
|
||||
TABLE, "thumb");
|
||||
|
||||
/** Project last activity date */
|
||||
public static final LongProperty LAST_ACTIVITY_DATE = new LongProperty(
|
||||
TABLE, "lastActivityDate");
|
||||
|
||||
/** Whether user is part of Tag team */
|
||||
public static final IntegerProperty IS_TEAM = new IntegerProperty(
|
||||
TABLE, "isTeam");
|
||||
|
||||
/** Whether Tag has unread activity */
|
||||
public static final IntegerProperty IS_UNREAD = new IntegerProperty(
|
||||
TABLE, "isUnread");
|
||||
|
||||
/** Task count */
|
||||
public static final IntegerProperty TASK_COUNT = new IntegerProperty(
|
||||
TABLE, "taskCount");
|
||||
|
||||
/** List of all properties for this model */
|
||||
public static final Property<?>[] PROPERTIES = generateProperties(TagData.class);
|
||||
|
||||
// --- flags
|
||||
|
||||
/** whether tag is publicly visible */
|
||||
public static final int FLAG_PUBLIC = 1 << 0;
|
||||
|
||||
/** whether user should not be notified of tag activity */
|
||||
public static final int FLAG_SILENT = 1 << 1;
|
||||
|
||||
/** whether tag is emergent */
|
||||
public static final int FLAG_EMERGENT = 1 << 2;
|
||||
|
||||
// --- defaults
|
||||
|
||||
/** Default values container */
|
||||
private static final ContentValues defaultValues = new ContentValues();
|
||||
|
||||
static {
|
||||
defaultValues.put(USER_ID.name, 0);
|
||||
defaultValues.put(USER.name, "{}");
|
||||
defaultValues.put(REMOTE_ID.name, 0);
|
||||
defaultValues.put(NAME.name, "");
|
||||
defaultValues.put(PICTURE.name, "");
|
||||
defaultValues.put(IS_TEAM.name, 1);
|
||||
defaultValues.put(MEMBERS.name, "[]");
|
||||
defaultValues.put(MEMBER_COUNT.name, 0);
|
||||
defaultValues.put(FLAGS.name, 0);
|
||||
defaultValues.put(COMPLETION_DATE.name, 0);
|
||||
defaultValues.put(DELETION_DATE.name, 0);
|
||||
|
||||
defaultValues.put(THUMB.name, "");
|
||||
defaultValues.put(LAST_ACTIVITY_DATE.name, 0);
|
||||
defaultValues.put(IS_UNREAD.name, 0);
|
||||
defaultValues.put(TASK_COUNT.name, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContentValues getDefaultValues() {
|
||||
return defaultValues;
|
||||
}
|
||||
|
||||
// --- data access boilerplate
|
||||
|
||||
public TagData() {
|
||||
super();
|
||||
}
|
||||
|
||||
public TagData(TodorooCursor<TagData> cursor) {
|
||||
this();
|
||||
readPropertiesFromCursor(cursor);
|
||||
}
|
||||
|
||||
public void readFromCursor(TodorooCursor<TagData> cursor) {
|
||||
super.readPropertiesFromCursor(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getId() {
|
||||
return getIdHelper(ID);
|
||||
}
|
||||
|
||||
// --- parcelable helpers
|
||||
|
||||
public static final Creator<TagData> CREATOR = new ModelCreator<TagData>(TagData.class);
|
||||
|
||||
@Override
|
||||
protected Creator<? extends AbstractModel> getCreator() {
|
||||
return CREATOR;
|
||||
}
|
||||
|
||||
// --- data access methods
|
||||
|
||||
/** Checks whether task is done. Requires COMPLETION_DATE */
|
||||
public boolean isCompleted() {
|
||||
return getValue(COMPLETION_DATE) > 0;
|
||||
}
|
||||
|
||||
/** Checks whether task is deleted. Will return false if DELETION_DATE not read */
|
||||
public boolean isDeleted() {
|
||||
// assume false if we didn't load deletion date
|
||||
if(!containsValue(DELETION_DATE))
|
||||
return false;
|
||||
else
|
||||
return getValue(DELETION_DATE) > 0;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.data;
|
||||
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.net.Uri;
|
||||
|
||||
import com.todoroo.andlib.data.AbstractModel;
|
||||
import com.todoroo.andlib.data.Property;
|
||||
import com.todoroo.andlib.data.Property.LongProperty;
|
||||
import com.todoroo.andlib.data.Property.StringProperty;
|
||||
import com.todoroo.andlib.data.Table;
|
||||
import com.todoroo.andlib.data.TodorooCursor;
|
||||
import com.todoroo.astrid.api.AstridApiConstants;
|
||||
|
||||
/**
|
||||
* Data Model which represents an update (e.g. a comment or data update event)
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public class Update extends RemoteModel {
|
||||
|
||||
// --- table
|
||||
|
||||
/** table for this model */
|
||||
public static final Table TABLE = new Table("updates", Update.class);
|
||||
|
||||
/** content uri for this model */
|
||||
public static final Uri CONTENT_URI = Uri.parse("content://" + AstridApiConstants.PACKAGE + "/" +
|
||||
TABLE.name);
|
||||
|
||||
// --- properties
|
||||
|
||||
/** ID */
|
||||
public static final LongProperty ID = new LongProperty(
|
||||
TABLE, ID_PROPERTY_NAME);
|
||||
|
||||
/** Remote ID */
|
||||
public static final LongProperty REMOTE_ID = new LongProperty(
|
||||
TABLE, REMOTE_ID_PROPERTY_NAME);
|
||||
|
||||
/** Associated Task (if any) */
|
||||
public static final LongProperty TASK = new LongProperty(
|
||||
TABLE, "task");
|
||||
|
||||
/** Associated Project (if any) */
|
||||
public static final LongProperty TAG = new LongProperty(
|
||||
TABLE, "tag");
|
||||
|
||||
/** From user id */
|
||||
public static final LongProperty USER_ID = new LongProperty(
|
||||
TABLE, USER_ID_PROPERTY_NAME);
|
||||
|
||||
/** From User Object (JSON) */
|
||||
public static final StringProperty USER = new StringProperty(
|
||||
TABLE, USER_JSON_PROPERTY_NAME);
|
||||
|
||||
/** Action text */
|
||||
public static final StringProperty ACTION = new StringProperty(
|
||||
TABLE, "action");
|
||||
|
||||
/** Action code */
|
||||
public static final StringProperty ACTION_CODE = new StringProperty(
|
||||
TABLE, "actionCode");
|
||||
|
||||
/** Message */
|
||||
public static final StringProperty MESSAGE = new StringProperty(
|
||||
TABLE, "message");
|
||||
|
||||
/** Target Object Name */
|
||||
public static final StringProperty TARGET_NAME = new StringProperty(
|
||||
TABLE, "targetName");
|
||||
|
||||
/** From User Object (JSON) */
|
||||
public static final StringProperty PICTURE = new StringProperty(
|
||||
TABLE, "picture");
|
||||
|
||||
/** Unixtime Metadata was created */
|
||||
public static final LongProperty CREATION_DATE = new LongProperty(
|
||||
TABLE, "created");
|
||||
|
||||
/** List of all properties for this model */
|
||||
public static final Property<?>[] PROPERTIES = generateProperties(Update.class);
|
||||
|
||||
// --- defaults
|
||||
|
||||
/** Default values container */
|
||||
private static final ContentValues defaultValues = new ContentValues();
|
||||
|
||||
@Override
|
||||
public ContentValues getDefaultValues() {
|
||||
return defaultValues;
|
||||
}
|
||||
|
||||
static {
|
||||
defaultValues.put(REMOTE_ID.name, 0);
|
||||
defaultValues.put(TASK.name, 0);
|
||||
defaultValues.put(TAG.name, 0);
|
||||
defaultValues.put(USER_ID.name, 0);
|
||||
defaultValues.put(USER.name, "");
|
||||
defaultValues.put(ACTION.name, "");
|
||||
defaultValues.put(ACTION_CODE.name, "");
|
||||
defaultValues.put(MESSAGE.name, "");
|
||||
defaultValues.put(TARGET_NAME.name, "");
|
||||
defaultValues.put(PICTURE.name, "");
|
||||
}
|
||||
|
||||
// --- data access boilerplate
|
||||
|
||||
public Update() {
|
||||
super();
|
||||
}
|
||||
|
||||
public Update(TodorooCursor<Update> cursor) {
|
||||
this();
|
||||
readPropertiesFromCursor(cursor);
|
||||
}
|
||||
|
||||
public void readFromCursor(TodorooCursor<Update> cursor) {
|
||||
super.readPropertiesFromCursor(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getId() {
|
||||
return getIdHelper(ID);
|
||||
};
|
||||
|
||||
// --- parcelable helpers
|
||||
|
||||
private static final Creator<Update> CREATOR = new ModelCreator<Update>(Update.class);
|
||||
|
||||
@Override
|
||||
protected Creator<? extends AbstractModel> getCreator() {
|
||||
return CREATOR;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Todoroo Inc
|
||||
* All Rights Reserved
|
||||
* http://www.todoroo.com
|
||||
*/
|
||||
package com.todoroo.astrid.data;
|
||||
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.net.Uri;
|
||||
|
||||
import com.todoroo.andlib.data.AbstractModel;
|
||||
import com.todoroo.andlib.data.Property;
|
||||
import com.todoroo.andlib.data.Property.LongProperty;
|
||||
import com.todoroo.andlib.data.Property.StringProperty;
|
||||
import com.todoroo.andlib.data.Table;
|
||||
import com.todoroo.andlib.data.TodorooCursor;
|
||||
import com.todoroo.astrid.api.AstridApiConstants;
|
||||
|
||||
/**
|
||||
* Data Model which represents a user.
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public final class User extends RemoteModel {
|
||||
|
||||
// --- table and uri
|
||||
|
||||
/** table for this model */
|
||||
public static final Table TABLE = new Table("users", User.class);
|
||||
|
||||
/** content uri for this model */
|
||||
public static final Uri CONTENT_URI = Uri.parse("content://" + AstridApiConstants.PACKAGE + "/" +
|
||||
TABLE.name);
|
||||
|
||||
// --- properties
|
||||
|
||||
/** ID */
|
||||
public static final LongProperty ID = new LongProperty(
|
||||
TABLE, ID_PROPERTY_NAME);
|
||||
|
||||
/** User Name */
|
||||
public static final StringProperty NAME = new StringProperty(
|
||||
TABLE, "name");
|
||||
|
||||
/** User Email */
|
||||
public static final StringProperty EMAIL = new StringProperty(
|
||||
TABLE, "email");
|
||||
|
||||
/** User picture */
|
||||
public static final StringProperty PICTURE = new StringProperty(
|
||||
TABLE, "picture");
|
||||
|
||||
/** User picture thumbnail */
|
||||
public static final StringProperty THUMB = new StringProperty(
|
||||
TABLE, "thumb");
|
||||
|
||||
/** User last activity string */
|
||||
public static final StringProperty LAST_ACTIVITY = new StringProperty(
|
||||
TABLE, "lastActivity");
|
||||
|
||||
/** User last activity date */
|
||||
public static final LongProperty LAST_ACTIVITY_DATE = new LongProperty(
|
||||
TABLE, "lastActivityDate");
|
||||
|
||||
/** Remote id */
|
||||
public static final LongProperty REMOTE_ID = new LongProperty(
|
||||
TABLE, REMOTE_ID_PROPERTY_NAME);
|
||||
|
||||
/** List of all properties for this model */
|
||||
public static final Property<?>[] PROPERTIES = generateProperties(User.class);
|
||||
|
||||
// --- defaults
|
||||
|
||||
/** Default values container */
|
||||
private static final ContentValues defaultValues = new ContentValues();
|
||||
|
||||
static {
|
||||
defaultValues.put(NAME.name, "");
|
||||
defaultValues.put(EMAIL.name, "");
|
||||
defaultValues.put(PICTURE.name, "");
|
||||
defaultValues.put(THUMB.name, "");
|
||||
defaultValues.put(LAST_ACTIVITY.name, "");
|
||||
defaultValues.put(LAST_ACTIVITY_DATE.name, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContentValues getDefaultValues() {
|
||||
return defaultValues;
|
||||
}
|
||||
|
||||
// --- data access boilerplate
|
||||
|
||||
public User() {
|
||||
super();
|
||||
}
|
||||
|
||||
public User(TodorooCursor<User> cursor) {
|
||||
this();
|
||||
readPropertiesFromCursor(cursor);
|
||||
}
|
||||
|
||||
public void readFromCursor(TodorooCursor<User> cursor) {
|
||||
super.readPropertiesFromCursor(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getId() {
|
||||
return getIdHelper(ID);
|
||||
}
|
||||
|
||||
// --- parcelable helpers
|
||||
|
||||
public static final Creator<User> CREATOR = new ModelCreator<User>(User.class);
|
||||
|
||||
@Override
|
||||
protected Creator<? extends AbstractModel> getCreator() {
|
||||
return CREATOR;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,225 @@
|
||||
package com.timsu.astrid;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import com.todoroo.andlib.data.TodorooCursor;
|
||||
import com.todoroo.andlib.service.Autowired;
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
import com.todoroo.andlib.service.DependencyInjectionService;
|
||||
import com.todoroo.andlib.service.NotificationManager;
|
||||
import com.todoroo.andlib.service.NotificationManager.AndroidNotificationManager;
|
||||
import com.todoroo.andlib.sql.Query;
|
||||
import com.todoroo.andlib.utility.DateUtilities;
|
||||
import com.todoroo.andlib.utility.Preferences;
|
||||
import com.todoroo.astrid.actfm.TagViewActivity;
|
||||
import com.todoroo.astrid.actfm.sync.ActFmSyncService;
|
||||
import com.todoroo.astrid.activity.ShortcutActivity;
|
||||
import com.todoroo.astrid.activity.TaskListActivity;
|
||||
import com.todoroo.astrid.api.AstridApiConstants;
|
||||
import com.todoroo.astrid.api.FilterWithCustomIntent;
|
||||
import com.todoroo.astrid.core.CoreFilterExposer;
|
||||
import com.todoroo.astrid.dao.UpdateDao;
|
||||
import com.todoroo.astrid.data.TagData;
|
||||
import com.todoroo.astrid.data.Update;
|
||||
import com.todoroo.astrid.reminders.Notifications;
|
||||
import com.todoroo.astrid.service.TagDataService;
|
||||
import com.todoroo.astrid.tags.TagFilterExposer;
|
||||
import com.todoroo.astrid.utility.Constants;
|
||||
import com.todoroo.astrid.utility.Flags;
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
public class C2DMReceiver extends BroadcastReceiver {
|
||||
|
||||
public static final String C2DM_SENDER = "c2dm@astrid.com"; //$NON-NLS-1$
|
||||
|
||||
private static final String PREF_REGISTRATION = "c2dm_key";
|
||||
|
||||
@Autowired ActFmSyncService actFmSyncService;
|
||||
@Autowired TagDataService tagDataService;
|
||||
@Autowired UpdateDao updateDao;
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, final Intent intent) {
|
||||
ContextManager.setContext(context);
|
||||
DependencyInjectionService.getInstance().inject(this);
|
||||
if (intent.getAction().equals("com.google.android.c2dm.intent.REGISTRATION")) {
|
||||
handleRegistration(intent);
|
||||
} else if (intent.getAction().equals("com.google.android.c2dm.intent.RECEIVE")) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handleMessage(intent);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
/** Handle message. Run on separate thread. */
|
||||
private void handleMessage(Intent intent) {
|
||||
String message = intent.getStringExtra("alert");
|
||||
Context context = ContextManager.getContext();
|
||||
|
||||
Intent notifyIntent = null;
|
||||
int notifId;
|
||||
|
||||
// fetch data
|
||||
if(intent.hasExtra("tag_id")) {
|
||||
notifyIntent = createTagIntent(context, intent);
|
||||
notifId = (int) Long.parseLong(intent.getStringExtra("tag_id"));
|
||||
} else {
|
||||
notifId = Constants.NOTIFICATION_ACTFM;
|
||||
}
|
||||
|
||||
if(notifyIntent == null)
|
||||
notifyIntent = ShortcutActivity.createIntent(CoreFilterExposer.buildInboxFilter(context.getResources()));
|
||||
|
||||
notifyIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(context,
|
||||
Constants.NOTIFICATION_ACTFM, notifyIntent, 0);
|
||||
|
||||
// create notification
|
||||
NotificationManager nm = new AndroidNotificationManager(ContextManager.getContext());
|
||||
Notification notification = new Notification(R.drawable.notif_pink_alarm,
|
||||
message, System.currentTimeMillis());
|
||||
String title;
|
||||
if(intent.hasExtra("title"))
|
||||
title = "Astrid: " + intent.getStringExtra("title");
|
||||
else
|
||||
title = ContextManager.getString(R.string.app_name);
|
||||
notification.setLatestEventInfo(ContextManager.getContext(), title,
|
||||
message, pendingIntent);
|
||||
notification.flags |= Notification.FLAG_AUTO_CANCEL;
|
||||
|
||||
boolean sounds = !"false".equals(intent.getStringExtra("sound"));
|
||||
notification.defaults = 0;
|
||||
if(sounds && !Notifications.isQuietHours()) {
|
||||
notification.defaults |= Notification.DEFAULT_SOUND;
|
||||
notification.defaults |= Notification.DEFAULT_VIBRATE;
|
||||
}
|
||||
|
||||
nm.notify(notifId, notification);
|
||||
|
||||
if(intent.hasExtra("tag_id")) {
|
||||
Intent broadcastIntent = new Intent(TagViewActivity.BROADCAST_TAG_ACTIVITY);
|
||||
broadcastIntent.putExtras(intent);
|
||||
ContextManager.getContext().sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
|
||||
}
|
||||
}
|
||||
|
||||
private Intent createTagIntent(final Context context, final Intent intent) {
|
||||
TodorooCursor<TagData> cursor = tagDataService.query(
|
||||
Query.select(TagData.PROPERTIES).where(TagData.REMOTE_ID.eq(
|
||||
intent.getStringExtra("tag_id"))));
|
||||
try {
|
||||
final TagData tagData = new TagData();
|
||||
if(cursor.getCount() == 0) {
|
||||
tagData.setValue(TagData.NAME, intent.getStringExtra("title"));
|
||||
tagData.setValue(TagData.REMOTE_ID, Long.parseLong(intent.getStringExtra("tag_id")));
|
||||
Flags.set(Flags.SUPPRESS_SYNC);
|
||||
tagDataService.save(tagData);
|
||||
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
actFmSyncService.fetchTag(tagData);
|
||||
} catch (IOException e) {
|
||||
Log.e("c2dm-tag-rx", "io-exception", e);
|
||||
} catch (JSONException e) {
|
||||
Log.e("c2dm-tag-rx", "json-exception", e);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
cursor.moveToNext();
|
||||
tagData.readFromCursor(cursor);
|
||||
}
|
||||
|
||||
FilterWithCustomIntent filter = (FilterWithCustomIntent)TagFilterExposer.filterFromTagData(context, tagData);
|
||||
if(intent.hasExtra("activity_id")) {
|
||||
filter.customExtras.putInt(TagViewActivity.EXTRA_START_TAB, 1);
|
||||
|
||||
try {
|
||||
Update update = new Update();
|
||||
update.setValue(Update.REMOTE_ID, Long.parseLong(intent.getStringExtra("activity_id")));
|
||||
update.setValue(Update.USER_ID, Long.parseLong(intent.getStringExtra("user_id")));
|
||||
JSONObject user = new JSONObject();
|
||||
user.put("id", update.getValue(Update.USER_ID));
|
||||
user.put("name", intent.getStringExtra("user_name"));
|
||||
update.setValue(Update.USER, user.toString());
|
||||
update.setValue(Update.ACTION, "commented");
|
||||
update.setValue(Update.ACTION_CODE, "tag_comment");
|
||||
update.setValue(Update.TARGET_NAME, intent.getStringExtra("title"));
|
||||
String message = intent.getStringExtra("alert");
|
||||
if(message.contains(":"))
|
||||
message = message.substring(message.indexOf(':') + 2);
|
||||
update.setValue(Update.MESSAGE, message);
|
||||
update.setValue(Update.CREATION_DATE, DateUtilities.now());
|
||||
update.setValue(Update.TAG, tagData.getId());
|
||||
updateDao.createNew(update);
|
||||
} catch (JSONException e) {
|
||||
//
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Intent launchIntent = new Intent();
|
||||
launchIntent.putExtra(TaskListActivity.TOKEN_FILTER, filter);
|
||||
launchIntent.setComponent(filter.customTaskList);
|
||||
launchIntent.putExtras(filter.customExtras);
|
||||
|
||||
return launchIntent;
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleRegistration(Intent intent) {
|
||||
String registration = intent.getStringExtra("registration_id");
|
||||
if (intent.getStringExtra("error") != null) {
|
||||
Log.w("astrid-actfm", "error-c2dm: " + intent.getStringExtra("error"));
|
||||
} else if (intent.getStringExtra("unregistered") != null) {
|
||||
// un-registration done
|
||||
} else if (registration != null) {
|
||||
try {
|
||||
DependencyInjectionService.getInstance().inject(this);
|
||||
actFmSyncService.invoke("user_set_c2dm", "c2dm", registration);
|
||||
Preferences.setString(PREF_REGISTRATION, registration);
|
||||
} catch (IOException e) {
|
||||
Log.e("astrid-actfm", "error-c2dm-transfer", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** try to request registration from c2dm service */
|
||||
public static void register() {
|
||||
if(Preferences.getStringValue(PREF_REGISTRATION) != null)
|
||||
return;
|
||||
|
||||
Context context = ContextManager.getContext();
|
||||
Intent registrationIntent = new Intent("com.google.android.c2dm.intent.REGISTER");
|
||||
registrationIntent.putExtra("app", PendingIntent.getBroadcast(context, 0, new Intent(), 0)); // boilerplate
|
||||
registrationIntent.putExtra("sender", C2DM_SENDER);
|
||||
context.startService(registrationIntent);
|
||||
}
|
||||
|
||||
/** unregister with c2dm service */
|
||||
public static void unregister() {
|
||||
Preferences.setString(PREF_REGISTRATION, null);
|
||||
Context context = ContextManager.getContext();
|
||||
Intent unregIntent = new Intent("com.google.android.c2dm.intent.UNREGISTER");
|
||||
unregIntent.putExtra("app", PendingIntent.getBroadcast(context, 0, new Intent(), 0));
|
||||
context.startService(unregIntent);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package com.todoroo.astrid.actfm;
|
||||
|
||||
import com.todoroo.andlib.service.Autowired;
|
||||
import com.todoroo.andlib.service.DependencyInjectionService;
|
||||
import com.todoroo.astrid.actfm.sync.ActFmPreferenceService;
|
||||
import com.todoroo.astrid.actfm.sync.ActFmSyncProvider;
|
||||
import com.todoroo.astrid.service.StatisticsService;
|
||||
import com.todoroo.astrid.sync.SyncBackgroundService;
|
||||
import com.todoroo.astrid.sync.SyncProvider;
|
||||
import com.todoroo.astrid.sync.SyncProviderUtilities;
|
||||
|
||||
/**
|
||||
* SynchronizationService is the service that performs Astrid's background
|
||||
* synchronization with online task managers. Starting this service
|
||||
* schedules a repeating alarm which handles the synchronization
|
||||
*
|
||||
* @author Tim Su
|
||||
*
|
||||
*/
|
||||
public class ActFmBackgroundService extends SyncBackgroundService {
|
||||
|
||||
@Autowired ActFmPreferenceService actFmPreferenceService;
|
||||
|
||||
public ActFmBackgroundService() {
|
||||
DependencyInjectionService.getInstance().inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SyncProvider<?> getSyncProvider() {
|
||||
return new ActFmSyncProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SyncProviderUtilities getSyncUtilities() {
|
||||
return actFmPreferenceService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
StatisticsService.sessionStart(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
StatisticsService.sessionStop(this);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,249 @@
|
||||
/*
|
||||
* ASTRID: Android's Simple Task Recording Dashboard
|
||||
*
|
||||
* Copyright (c) 2009 Tim Su
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
package com.todoroo.astrid.actfm;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.facebook.android.AsyncFacebookRunner;
|
||||
import com.facebook.android.AsyncFacebookRunner.RequestListener;
|
||||
import com.facebook.android.AuthListener;
|
||||
import com.facebook.android.Facebook;
|
||||
import com.facebook.android.FacebookError;
|
||||
import com.facebook.android.LoginButton;
|
||||
import com.facebook.android.Util;
|
||||
import com.timsu.astrid.C2DMReceiver;
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.andlib.service.Autowired;
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
import com.todoroo.andlib.service.DependencyInjectionService;
|
||||
import com.todoroo.andlib.utility.DialogUtilities;
|
||||
import com.todoroo.andlib.utility.Preferences;
|
||||
import com.todoroo.astrid.actfm.sync.ActFmInvoker;
|
||||
import com.todoroo.astrid.actfm.sync.ActFmPreferenceService;
|
||||
import com.todoroo.astrid.service.AstridDependencyInjector;
|
||||
import com.todoroo.astrid.service.TaskService;
|
||||
|
||||
/**
|
||||
* This activity allows users to sign in or log in to Producteev
|
||||
*
|
||||
* @author arne.jans
|
||||
*
|
||||
*/
|
||||
public class ActFmLoginActivity extends Activity implements AuthListener {
|
||||
|
||||
public static final String APP_ID = "183862944961271"; //$NON-NLS-1$
|
||||
|
||||
@Autowired TaskService taskService;
|
||||
@Autowired ActFmPreferenceService actFmPreferenceService;
|
||||
private final ActFmInvoker actFmInvoker = new ActFmInvoker();
|
||||
|
||||
private Facebook facebook;
|
||||
private AsyncFacebookRunner facebookRunner;
|
||||
private TextView errors;
|
||||
private boolean noSync = false;
|
||||
|
||||
// --- ui initialization
|
||||
|
||||
static {
|
||||
AstridDependencyInjector.initialize();
|
||||
}
|
||||
|
||||
public static final String EXTRA_DO_NOT_SYNC = "nosync"; //$NON-NLS-1$
|
||||
|
||||
public ActFmLoginActivity() {
|
||||
super();
|
||||
DependencyInjectionService.getInstance().inject(this);
|
||||
}
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
ContextManager.setContext(this);
|
||||
|
||||
setContentView(R.layout.sharing_login_activity);
|
||||
setTitle(R.string.sharing_SLA_title);
|
||||
|
||||
noSync = getIntent().getBooleanExtra(EXTRA_DO_NOT_SYNC, false);
|
||||
|
||||
facebook = new Facebook(APP_ID);
|
||||
facebookRunner = new AsyncFacebookRunner(facebook);
|
||||
|
||||
errors = (TextView) findViewById(R.id.error);
|
||||
LoginButton loginButton = (LoginButton) findViewById(R.id.fb_login);
|
||||
loginButton.init(this, facebook, this, new String[] {
|
||||
"email",
|
||||
"offline_access",
|
||||
"publish_stream"
|
||||
});
|
||||
|
||||
getWindow().setFormat(PixelFormat.RGBA_8888);
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DITHER);
|
||||
|
||||
setResult(RESULT_CANCELED);
|
||||
}
|
||||
|
||||
// --- facebook handler
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode,
|
||||
Intent data) {
|
||||
String error = data.getStringExtra("error");
|
||||
if (error == null) {
|
||||
error = data.getStringExtra("error_type");
|
||||
}
|
||||
String token = data.getStringExtra("access_token");
|
||||
if(error != null) {
|
||||
onFBAuthFail(error);
|
||||
} else if(token == null) {
|
||||
onFBAuthFail("Something went wrong! Please try again.");
|
||||
} else {
|
||||
facebook.setAccessToken(token);
|
||||
onFBAuthSucceed();
|
||||
}
|
||||
errors.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
public void onFBAuthSucceed() {
|
||||
createUserAccountFB();
|
||||
}
|
||||
|
||||
public void onFBAuthFail(String error) {
|
||||
DialogUtilities.okDialog(this, getString(R.string.sharing_SLA_title),
|
||||
android.R.drawable.ic_dialog_alert, error, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFBAuthCancel() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
// --- astrid social handler
|
||||
|
||||
ProgressDialog progressDialog;
|
||||
|
||||
/**
|
||||
* Create user account via FB
|
||||
*/
|
||||
public void createUserAccountFB() {
|
||||
progressDialog = DialogUtilities.progressDialog(this,
|
||||
getString(R.string.DLG_please_wait));
|
||||
facebookRunner.request("me", new SLARequestListener()); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
private class SLARequestListener implements RequestListener {
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
@Override
|
||||
public void onComplete(String response, Object state) {
|
||||
JSONObject json;
|
||||
try {
|
||||
json = Util.parseJson(response);
|
||||
String name = json.getString("name"); //$NON-NLS-1$
|
||||
String email = json.getString("email"); //$NON-NLS-1$
|
||||
|
||||
JSONObject result = actFmInvoker.authenticate(email, name, ActFmInvoker.PROVIDER_FACEBOOK,
|
||||
facebook.getAccessToken());
|
||||
|
||||
String token = actFmInvoker.getToken();
|
||||
actFmPreferenceService.setToken(token);
|
||||
|
||||
if(Preferences.getStringValue(R.string.actfm_APr_interval_key) == null)
|
||||
Preferences.setStringFromInteger(R.string.actfm_APr_interval_key, 3600);
|
||||
|
||||
Preferences.setLong(ActFmPreferenceService.PREF_USER_ID,
|
||||
result.optLong("id"));
|
||||
Preferences.setString(ActFmPreferenceService.PREF_NAME, result.optString("name"));
|
||||
Preferences.setString(ActFmPreferenceService.PREF_EMAIL, result.optString("email"));
|
||||
Preferences.setString(ActFmPreferenceService.PREF_PICTURE, result.optString("picture"));
|
||||
|
||||
C2DMReceiver.register();
|
||||
|
||||
progressDialog.dismiss();
|
||||
setResult(RESULT_OK);
|
||||
finish();
|
||||
|
||||
if(!noSync) {
|
||||
synchronize();
|
||||
}
|
||||
|
||||
} catch (Throwable e) {
|
||||
handleError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleError(final Throwable e) {
|
||||
progressDialog.dismiss();
|
||||
Log.e("astrid-sharing", "error-doing-sla", e); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
errors.setText(e.toString());
|
||||
errors.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFacebookError(FacebookError e, Object state) {
|
||||
handleError(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFileNotFoundException(FileNotFoundException e,
|
||||
Object state) {
|
||||
handleError(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIOException(IOException e, Object state) {
|
||||
handleError(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMalformedURLException(MalformedURLException e,
|
||||
Object state) {
|
||||
handleError(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void synchronize() {
|
||||
startService(new Intent(null, null,
|
||||
this, ActFmBackgroundService.class));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package com.todoroo.astrid.actfm;
|
||||
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.andlib.service.Autowired;
|
||||
import com.todoroo.astrid.actfm.sync.ActFmPreferenceService;
|
||||
import com.todoroo.astrid.actfm.sync.ActFmSyncProvider;
|
||||
import com.todoroo.astrid.sync.SyncProviderPreferences;
|
||||
import com.todoroo.astrid.sync.SyncProviderUtilities;
|
||||
|
||||
/**
|
||||
* Displays synchronization preferences and an action panel so users can
|
||||
* initiate actions from the menu.
|
||||
*
|
||||
* @author timsu
|
||||
*
|
||||
*/
|
||||
public class ActFmPreferences extends SyncProviderPreferences {
|
||||
|
||||
@Autowired ActFmPreferenceService actFmPreferenceService;
|
||||
|
||||
@Override
|
||||
public int getPreferenceResource() {
|
||||
return R.xml.preferences_actfm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startSync() {
|
||||
new ActFmSyncProvider().synchronize(this);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logOut() {
|
||||
new ActFmSyncProvider().signOut();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SyncProviderUtilities getUtilities() {
|
||||
return actFmPreferenceService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
new ActFmBackgroundService().scheduleService();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.actfm;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.andlib.service.Autowired;
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
import com.todoroo.andlib.service.DependencyInjectionService;
|
||||
import com.todoroo.astrid.actfm.sync.ActFmPreferenceService;
|
||||
import com.todoroo.astrid.api.AstridApiConstants;
|
||||
import com.todoroo.astrid.api.SyncAction;
|
||||
|
||||
/**
|
||||
* Exposes sync action
|
||||
*
|
||||
*/
|
||||
public class ActFmSyncActionExposer extends BroadcastReceiver {
|
||||
|
||||
@Autowired ActFmPreferenceService actFmPreferenceService;
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
ContextManager.setContext(context);
|
||||
DependencyInjectionService.getInstance().inject(this);
|
||||
|
||||
// if we aren't logged in, don't expose sync action
|
||||
if(!actFmPreferenceService.isLoggedIn())
|
||||
return;
|
||||
|
||||
Intent syncIntent = new Intent(null, null,
|
||||
context, ActFmBackgroundService.class);
|
||||
PendingIntent pendingIntent = PendingIntent.getService(context, 0, syncIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
SyncAction syncAction = new SyncAction(context.getString(R.string.actfm_APr_header),
|
||||
pendingIntent);
|
||||
|
||||
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_SYNC_ACTIONS);
|
||||
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, ActFmPreferenceService.IDENTIFIER);
|
||||
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, syncAction);
|
||||
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,575 @@
|
||||
package com.todoroo.astrid.actfm;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemSelectedListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.andlib.data.TodorooCursor;
|
||||
import com.todoroo.andlib.service.Autowired;
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
import com.todoroo.andlib.service.DependencyInjectionService;
|
||||
import com.todoroo.andlib.service.ExceptionService;
|
||||
import com.todoroo.andlib.utility.DialogUtilities;
|
||||
import com.todoroo.astrid.actfm.sync.ActFmInvoker;
|
||||
import com.todoroo.astrid.actfm.sync.ActFmPreferenceService;
|
||||
import com.todoroo.astrid.actfm.sync.ActFmSyncService;
|
||||
import com.todoroo.astrid.actfm.sync.ActFmSyncService.JsonHelper;
|
||||
import com.todoroo.astrid.api.AstridApiConstants;
|
||||
import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria;
|
||||
import com.todoroo.astrid.data.Metadata;
|
||||
import com.todoroo.astrid.data.TagData;
|
||||
import com.todoroo.astrid.data.Task;
|
||||
import com.todoroo.astrid.service.MetadataService;
|
||||
import com.todoroo.astrid.service.TagDataService;
|
||||
import com.todoroo.astrid.service.TaskService;
|
||||
import com.todoroo.astrid.service.ThemeService;
|
||||
import com.todoroo.astrid.tags.TagService;
|
||||
import com.todoroo.astrid.ui.PeopleContainer;
|
||||
import com.todoroo.astrid.ui.PeopleContainer.OnAddNewPersonListener;
|
||||
import com.todoroo.astrid.utility.Flags;
|
||||
|
||||
public class EditPeopleActivity extends Activity {
|
||||
|
||||
public static final String EXTRA_TASK_ID = "task"; //$NON-NLS-1$
|
||||
|
||||
private static final int REQUEST_LOG_IN = 0;
|
||||
|
||||
private Task task;
|
||||
|
||||
private final ArrayList<Metadata> nonSharedTags = new ArrayList<Metadata>();
|
||||
|
||||
@Autowired ActFmPreferenceService actFmPreferenceService;
|
||||
|
||||
@Autowired ActFmSyncService actFmSyncService;
|
||||
|
||||
@Autowired TaskService taskService;
|
||||
|
||||
@Autowired MetadataService metadataService;
|
||||
|
||||
@Autowired ExceptionService exceptionService;
|
||||
|
||||
@Autowired TagDataService tagDataService;
|
||||
|
||||
private PeopleContainer sharedWithContainer;
|
||||
|
||||
private CheckBox cbFacebook;
|
||||
|
||||
private CheckBox cbTwitter;
|
||||
|
||||
private Spinner assignedSpinner;
|
||||
|
||||
private EditText assignedCustom;
|
||||
|
||||
private final ArrayList<AssignedToUser> spinnerValues = new ArrayList<AssignedToUser>();
|
||||
|
||||
public EditPeopleActivity() {
|
||||
DependencyInjectionService.getInstance().inject(this);
|
||||
}
|
||||
|
||||
// --- UI initialization
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.edit_people_activity);
|
||||
setTitle(getString(R.string.actfm_EPA_title));
|
||||
ThemeService.applyTheme(this);
|
||||
|
||||
task = taskService.fetchById(
|
||||
getIntent().getLongExtra(EXTRA_TASK_ID, 41L), Task.ID, Task.REMOTE_ID,
|
||||
Task.TITLE, Task.USER, Task.USER_ID, Task.SHARED_WITH, Task.FLAGS);
|
||||
if(task == null) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
((TextView) findViewById(R.id.title)).setText(task.getValue(Task.TITLE));
|
||||
sharedWithContainer = (PeopleContainer) findViewById(R.id.share_container);
|
||||
assignedCustom = (EditText) findViewById(R.id.assigned_custom);
|
||||
assignedSpinner = (Spinner) findViewById(R.id.assigned_spinner);
|
||||
cbFacebook = (CheckBox) findViewById(R.id.checkbox_facebook);
|
||||
cbTwitter = (CheckBox) findViewById(R.id.checkbox_twitter);
|
||||
|
||||
sharedWithContainer.addPerson(""); //$NON-NLS-1$
|
||||
setUpListeners();
|
||||
setUpData();
|
||||
}
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
private void setUpData() {
|
||||
try {
|
||||
JSONObject sharedWith;
|
||||
if(task.getValue(Task.SHARED_WITH).length() > 0)
|
||||
sharedWith = new JSONObject(task.getValue(Task.SHARED_WITH));
|
||||
else
|
||||
sharedWith = new JSONObject();
|
||||
|
||||
cbFacebook.setChecked(sharedWith.optBoolean("fb", false));
|
||||
cbTwitter.setChecked(sharedWith.optBoolean("tw", false));
|
||||
|
||||
final ArrayList<JSONObject> sharedPeople = new ArrayList<JSONObject>();
|
||||
JSONArray people = sharedWith.optJSONArray("p");
|
||||
if(people != null) {
|
||||
for(int i = 0; i < people.length(); i++) {
|
||||
String person = people.getString(i);
|
||||
TextView textView = sharedWithContainer.addPerson(person);
|
||||
textView.setEnabled(false);
|
||||
sharedPeople.add(PeopleContainer.createUserJson(textView));
|
||||
}
|
||||
}
|
||||
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
TodorooCursor<Metadata> tags = TagService.getInstance().getTags(task.getId());
|
||||
try {
|
||||
Metadata metadata = new Metadata();
|
||||
for(tags.moveToFirst(); !tags.isAfterLast(); tags.moveToNext()) {
|
||||
metadata.readFromCursor(tags);
|
||||
final String tag = metadata.getValue(TagService.TAG);
|
||||
TagData tagData = tagDataService.getTag(tag, TagData.MEMBER_COUNT, TagData.MEMBERS, TagData.USER);
|
||||
if(tagData != null && tagData.getValue(TagData.MEMBER_COUNT) > 0) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
TextView textView = sharedWithContainer.addPerson("#" + tag);
|
||||
textView.setEnabled(false);
|
||||
}
|
||||
});
|
||||
JSONArray members = new JSONArray(tagData.getValue(TagData.MEMBERS));
|
||||
for(int i = 0; i < members.length(); i++)
|
||||
sharedPeople.add(members.getJSONObject(i));
|
||||
if(!TextUtils.isEmpty(tagData.getValue(TagData.USER)))
|
||||
sharedPeople.add(new JSONObject(tagData.getValue(TagData.USER)));
|
||||
} else {
|
||||
nonSharedTags.add((Metadata) metadata.clone());
|
||||
}
|
||||
}
|
||||
|
||||
buildAssignedToSpinner(sharedPeople);
|
||||
} catch (JSONException e) {
|
||||
exceptionService.reportError("json-reading-data", e);
|
||||
} finally {
|
||||
tags.close();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
|
||||
} catch (JSONException e) {
|
||||
exceptionService.reportError("json-reading-data", e);
|
||||
}
|
||||
}
|
||||
|
||||
private class AssignedToUser {
|
||||
public String label;
|
||||
public JSONObject user;
|
||||
|
||||
public AssignedToUser(String label, JSONObject user) {
|
||||
super();
|
||||
this.label = label;
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
private void buildAssignedToSpinner(ArrayList<JSONObject> sharedPeople) throws JSONException {
|
||||
HashSet<Long> userIds = new HashSet<Long>();
|
||||
HashSet<String> emails = new HashSet<String>();
|
||||
HashMap<String, AssignedToUser> names = new HashMap<String, AssignedToUser>();
|
||||
|
||||
JSONObject myself = new JSONObject();
|
||||
myself.put("id", 0L);
|
||||
sharedPeople.add(0, myself);
|
||||
if(task.getValue(Task.USER_ID) != 0) {
|
||||
JSONObject user = new JSONObject(task.getValue(Task.USER));
|
||||
sharedPeople.add(0, user);
|
||||
}
|
||||
|
||||
// de-duplicate by user id and/or email
|
||||
spinnerValues.clear();
|
||||
for(int i = 0; i < sharedPeople.size(); i++) {
|
||||
JSONObject person = sharedPeople.get(i);
|
||||
if(person == null)
|
||||
continue;
|
||||
long id = person.optLong("id", -1);
|
||||
if(id == ActFmPreferenceService.userId() || (id > -1 && userIds.contains(id)))
|
||||
continue;
|
||||
userIds.add(id);
|
||||
|
||||
String email = person.optString("email");
|
||||
if(!TextUtils.isEmpty(email) && emails.contains(email))
|
||||
continue;
|
||||
emails.add(email);
|
||||
|
||||
String name = person.optString("name");
|
||||
if(id == 0)
|
||||
name = getString(R.string.actfm_EPA_assign_me);
|
||||
AssignedToUser atu = new AssignedToUser(name, person);
|
||||
spinnerValues.add(atu);
|
||||
if(names.containsKey(name)) {
|
||||
AssignedToUser user = names.get(name);
|
||||
if(user != null && user.user.has("email")) {
|
||||
user.label += " (" + user.user.optString("email") + ")";
|
||||
names.put(name, null);
|
||||
}
|
||||
if(!TextUtils.isEmpty("email"))
|
||||
atu.label += " (" + email + ")";
|
||||
} else if(TextUtils.isEmpty(name)) {
|
||||
if(!TextUtils.isEmpty("email"))
|
||||
atu.label = email;
|
||||
else
|
||||
spinnerValues.remove(atu);
|
||||
} else
|
||||
names.put(name, atu);
|
||||
}
|
||||
|
||||
spinnerValues.add(new AssignedToUser(getString(R.string.actfm_EPA_assign_custom), null));
|
||||
|
||||
final ArrayAdapter<AssignedToUser> usersAdapter = new ArrayAdapter<AssignedToUser>(this,
|
||||
android.R.layout.simple_spinner_item, spinnerValues);
|
||||
usersAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
assignedSpinner.setAdapter(usersAdapter);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setUpListeners() {
|
||||
final View assignedClear = findViewById(R.id.assigned_clear);
|
||||
|
||||
assignedSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View v,
|
||||
int index, long id) {
|
||||
if(index == spinnerValues.size() - 1) {
|
||||
assignedCustom.setVisibility(View.VISIBLE);
|
||||
assignedClear.setVisibility(View.VISIBLE);
|
||||
assignedSpinner.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> arg0) {
|
||||
//
|
||||
}
|
||||
});
|
||||
findViewById(R.id.discard).setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
findViewById(R.id.discard).requestFocus();
|
||||
|
||||
findViewById(R.id.save).setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
save();
|
||||
}
|
||||
});
|
||||
|
||||
assignedClear.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
assignedCustom.setVisibility(View.GONE);
|
||||
assignedClear.setVisibility(View.GONE);
|
||||
assignedCustom.setText(""); //$NON-NLS-1$
|
||||
assignedSpinner.setVisibility(View.VISIBLE);
|
||||
assignedSpinner.setSelection(0);
|
||||
}
|
||||
});
|
||||
|
||||
sharedWithContainer.setOnAddNewPerson(new OnAddNewPersonListener() {
|
||||
@Override
|
||||
public void textChanged(String text) {
|
||||
findViewById(R.id.share_additional).setVisibility(View.VISIBLE);
|
||||
if(text.indexOf('@') > -1) {
|
||||
findViewById(R.id.tag_label).setVisibility(View.VISIBLE);
|
||||
findViewById(R.id.tag_name).setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// --- events
|
||||
|
||||
/** Save sharing settings */
|
||||
@SuppressWarnings("nls")
|
||||
private void save() {
|
||||
if(!actFmPreferenceService.isLoggedIn()) {
|
||||
startActivityForResult(new Intent(this, ActFmLoginActivity.class),
|
||||
REQUEST_LOG_IN);
|
||||
return;
|
||||
}
|
||||
|
||||
setResult(RESULT_OK);
|
||||
Flags.set(Flags.REFRESH);
|
||||
try {
|
||||
JSONObject userJson;
|
||||
AssignedToUser assignedTo = (AssignedToUser) assignedSpinner.getSelectedItem();
|
||||
if(assignedTo == null)
|
||||
userJson = PeopleContainer.createUserJson(assignedCustom);
|
||||
else
|
||||
userJson = assignedTo.user;
|
||||
if(userJson == null || userJson.optLong("id", -1) == 0) {
|
||||
task.setValue(Task.USER_ID, 0L);
|
||||
task.setValue(Task.USER, "{}");
|
||||
} else {
|
||||
task.setValue(Task.USER_ID, userJson.optLong("id", -1));
|
||||
task.setValue(Task.USER, userJson.toString());
|
||||
}
|
||||
|
||||
ArrayList<Metadata> metadata = new ArrayList<Metadata>(nonSharedTags);
|
||||
JSONObject sharedWith = parseSharedWithAndTags(metadata);
|
||||
task.setValue(Task.SHARED_WITH, sharedWith.toString());
|
||||
|
||||
metadataService.synchronizeMetadata(task.getId(), metadata, MetadataCriteria.withKey(TagService.KEY));
|
||||
|
||||
Flags.set(Flags.SUPPRESS_SYNC);
|
||||
taskService.save(task);
|
||||
shareTask(sharedWith, metadata);
|
||||
} catch (JSONException e) {
|
||||
exceptionService.displayAndReportError(this, "save-people", e);
|
||||
} catch (ParseSharedException e) {
|
||||
e.view.setTextColor(Color.RED);
|
||||
e.view.requestFocus();
|
||||
System.err.println(e.message);
|
||||
DialogUtilities.okDialog(this, e.message, null);
|
||||
}
|
||||
}
|
||||
|
||||
private class ParseSharedException extends Exception {
|
||||
private static final long serialVersionUID = -4135848250086302970L;
|
||||
public TextView view;
|
||||
public String message;
|
||||
|
||||
public ParseSharedException(TextView view, String message) {
|
||||
this.view = view;
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
private JSONObject parseSharedWithAndTags(ArrayList<Metadata> metadata) throws
|
||||
JSONException, ParseSharedException {
|
||||
JSONObject sharedWith = new JSONObject();
|
||||
if(cbFacebook.isChecked())
|
||||
sharedWith.put("fb", true);
|
||||
if(cbTwitter.isChecked())
|
||||
sharedWith.put("tw", true);
|
||||
|
||||
JSONArray peopleList = new JSONArray();
|
||||
for(int i = 0; i < sharedWithContainer.getChildCount(); i++) {
|
||||
TextView textView = sharedWithContainer.getTextView(i);
|
||||
textView.setTextAppearance(this, android.R.style.TextAppearance_Medium_Inverse);
|
||||
String text = textView.getText().toString();
|
||||
|
||||
if(text.length() == 0)
|
||||
continue;
|
||||
if(text.startsWith("#")) {
|
||||
text = text.substring(1);
|
||||
TagData tagData = tagDataService.getTag(text, TagData.REMOTE_ID);
|
||||
if(tagData == null)
|
||||
throw new ParseSharedException(textView,
|
||||
getString(R.string.actfm_EPA_invalid_tag, text));
|
||||
Metadata tag = new Metadata();
|
||||
tag.setValue(Metadata.KEY, TagService.KEY);
|
||||
tag.setValue(TagService.TAG, text);
|
||||
tag.setValue(TagService.REMOTE_ID, tagData.getValue(TagData.REMOTE_ID));
|
||||
metadata.add(tag);
|
||||
|
||||
} else {
|
||||
if(text.indexOf('@') == -1)
|
||||
throw new ParseSharedException(textView,
|
||||
getString(R.string.actfm_EPA_invalid_email, text));
|
||||
peopleList.put(text);
|
||||
}
|
||||
}
|
||||
sharedWith.put("p", peopleList);
|
||||
|
||||
return sharedWith;
|
||||
}
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
private void shareTask(final JSONObject sharedWith, final ArrayList<Metadata> metadata) {
|
||||
final JSONArray emails = sharedWith.optJSONArray("p");
|
||||
|
||||
final ProgressDialog pd = DialogUtilities.progressDialog(this,
|
||||
getString(R.string.DLG_please_wait));
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
ActFmInvoker invoker = new ActFmInvoker(actFmPreferenceService.getToken());
|
||||
try {
|
||||
if(task.getValue(Task.REMOTE_ID) == 0) {
|
||||
actFmSyncService.pushTask(task.getId());
|
||||
task.setValue(Task.REMOTE_ID, taskService.fetchById(task.getId(),
|
||||
Task.REMOTE_ID).getValue(Task.REMOTE_ID));
|
||||
}
|
||||
|
||||
Object[] args = buildSharingArgs(emails, metadata);
|
||||
JSONObject result = invoker.invoke("task_share", args);
|
||||
|
||||
sharedWith.remove("p");
|
||||
task.setValue(Task.SHARED_WITH, sharedWith.toString());
|
||||
task.setValue(Task.DETAILS_DATE, 0L);
|
||||
|
||||
readTagData(result.getJSONArray("tags"));
|
||||
JsonHelper.readUser(result.getJSONObject("assignee"),
|
||||
task, Task.USER_ID, Task.USER);
|
||||
Flags.set(Flags.SUPPRESS_SYNC);
|
||||
taskService.save(task);
|
||||
|
||||
int count = result.optInt("shared", 0);
|
||||
final String toast;
|
||||
if(count > 0)
|
||||
toast = getString(R.string.actfm_EPA_emailed_toast,
|
||||
getResources().getQuantityString(R.plurals.Npeople, count, count));
|
||||
else
|
||||
toast = getString(R.string.actfm_EPA_saved_toast);
|
||||
|
||||
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_EVENT_REFRESH);
|
||||
ContextManager.getContext().sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
Toast.makeText(EditPeopleActivity.this, toast, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
DialogUtilities.okDialog(EditPeopleActivity.this, getString(R.string.SyP_ioerror),
|
||||
android.R.drawable.ic_dialog_alert, e.toString(), null);
|
||||
} catch (JSONException e) {
|
||||
DialogUtilities.okDialog(EditPeopleActivity.this, getString(R.string.SyP_ioerror),
|
||||
android.R.drawable.ic_dialog_alert, e.toString(), null);
|
||||
} finally {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
pd.dismiss();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}.start();
|
||||
}
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
private void readTagData(JSONArray tags) throws JSONException {
|
||||
ArrayList<Metadata> metadata = new ArrayList<Metadata>();
|
||||
for(int i = 0; i < tags.length(); i++) {
|
||||
JSONObject tagObject = tags.getJSONObject(i);
|
||||
TagData tagData = tagDataService.getTag(tagObject.getString("name"), TagData.ID);
|
||||
if(tagData == null)
|
||||
tagData = new TagData();
|
||||
ActFmSyncService.JsonHelper.tagFromJson(tagObject, tagData);
|
||||
tagDataService.save(tagData);
|
||||
|
||||
Metadata tagMeta = new Metadata();
|
||||
tagMeta.setValue(Metadata.KEY, TagService.KEY);
|
||||
tagMeta.setValue(TagService.TAG, tagData.getValue(TagData.NAME));
|
||||
tagMeta.setValue(TagService.REMOTE_ID, tagData.getValue(TagData.REMOTE_ID));
|
||||
metadata.add(tagMeta);
|
||||
}
|
||||
|
||||
metadataService.synchronizeMetadata(task.getId(), metadata, MetadataCriteria.withKey(TagService.KEY));
|
||||
}
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
protected Object[] buildSharingArgs(JSONArray emails, ArrayList<Metadata>
|
||||
tags) throws JSONException {
|
||||
ArrayList<Object> values = new ArrayList<Object>();
|
||||
long currentTaskID = task.getValue(Task.REMOTE_ID);
|
||||
values.add("id");
|
||||
values.add(currentTaskID);
|
||||
|
||||
for(int i = 0; i < emails.length(); i++) {
|
||||
String email = emails.optString(i);
|
||||
if(email == null || email.indexOf('@') == -1)
|
||||
continue;
|
||||
values.add("emails[]");
|
||||
values.add(email);
|
||||
}
|
||||
|
||||
for(int i = 0; i < tags.size(); i++) {
|
||||
Metadata tag = tags.get(i);
|
||||
if(tag.containsNonNullValue(TagService.REMOTE_ID) &&
|
||||
tag.getValue(TagService.REMOTE_ID) > 0) {
|
||||
values.add("tag_ids[]");
|
||||
values.add(tag.getValue(TagService.REMOTE_ID));
|
||||
} else {
|
||||
values.add("tags[]");
|
||||
values.add(tag.getValue(TagService.TAG));
|
||||
}
|
||||
}
|
||||
|
||||
values.add("assignee");
|
||||
if(task.getValue(Task.USER_ID) == 0) {
|
||||
values.add("");
|
||||
} else {
|
||||
if(task.getValue(Task.USER_ID) > 0)
|
||||
values.add(task.getValue(Task.USER_ID));
|
||||
else {
|
||||
JSONObject user = new JSONObject(task.getValue(Task.USER));
|
||||
values.add(user.getString("email"));
|
||||
}
|
||||
}
|
||||
|
||||
String message = ((TextView) findViewById(R.id.message)).getText().toString();
|
||||
if(!TextUtils.isEmpty(message) && findViewById(R.id.share_additional).getVisibility() == View.VISIBLE) {
|
||||
values.add("message");
|
||||
values.add(message);
|
||||
}
|
||||
|
||||
String tag = ((TextView) findViewById(R.id.tag_name)).getText().toString();
|
||||
if(!TextUtils.isEmpty(tag)) {
|
||||
values.add("tag");
|
||||
values.add(tag);
|
||||
}
|
||||
|
||||
return values.toArray(new Object[values.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if(requestCode == REQUEST_LOG_IN) {
|
||||
if(resultCode == RESULT_OK)
|
||||
save();
|
||||
return;
|
||||
}
|
||||
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.actfm;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
import com.todoroo.astrid.actfm.sync.ActFmPreferenceService;
|
||||
import com.todoroo.astrid.api.AstridApiConstants;
|
||||
import com.todoroo.astrid.api.TaskAction;
|
||||
import com.todoroo.astrid.api.TaskDecoration;
|
||||
|
||||
/**
|
||||
* Exposes {@link TaskDecoration} for timers
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class EditPeopleExposer extends BroadcastReceiver {
|
||||
|
||||
private static final String ACTION = "com.todoroo.astrid.EDIT_PEOPLE"; //$NON-NLS-1$
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
ContextManager.setContext(context);
|
||||
long taskId = intent.getLongExtra(AstridApiConstants.EXTRAS_TASK_ID, -1);
|
||||
if(taskId == -1)
|
||||
return;
|
||||
|
||||
if(AstridApiConstants.BROADCAST_REQUEST_ACTIONS.equals(intent.getAction())) {
|
||||
final String label = context.getString(R.string.EPE_action);
|
||||
final Drawable drawable = context.getResources().getDrawable(R.drawable.tango_users);
|
||||
Intent newIntent = new Intent(ACTION);
|
||||
newIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId);
|
||||
Bitmap icon = ((BitmapDrawable)drawable).getBitmap();
|
||||
TaskAction action = new TaskAction(label,
|
||||
PendingIntent.getBroadcast(context, (int)taskId, newIntent, 0), icon);
|
||||
|
||||
// transmit
|
||||
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_ACTIONS);
|
||||
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, ActFmPreferenceService.IDENTIFIER);
|
||||
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, action);
|
||||
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId);
|
||||
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
|
||||
} else if(ACTION.equals(intent.getAction())) {
|
||||
Intent launchIntent = new Intent(context, EditPeopleActivity.class);
|
||||
launchIntent.putExtra(EditPeopleActivity.EXTRA_TASK_ID, taskId);
|
||||
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
ContextManager.getContext().startActivity(launchIntent);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,229 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.actfm;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import android.app.ListActivity;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.Window;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.andlib.data.TodorooCursor;
|
||||
import com.todoroo.andlib.service.Autowired;
|
||||
import com.todoroo.andlib.service.DependencyInjectionService;
|
||||
import com.todoroo.andlib.service.ExceptionService;
|
||||
import com.todoroo.andlib.sql.QueryTemplate;
|
||||
import com.todoroo.andlib.utility.DialogUtilities;
|
||||
import com.todoroo.astrid.actfm.sync.ActFmPreferenceService;
|
||||
import com.todoroo.astrid.actfm.sync.ActFmSyncService;
|
||||
import com.todoroo.astrid.activity.TaskListActivity;
|
||||
import com.todoroo.astrid.adapter.TagDataAdapter;
|
||||
import com.todoroo.astrid.dao.TagDataDao.TagDataCriteria;
|
||||
import com.todoroo.astrid.data.TagData;
|
||||
import com.todoroo.astrid.service.AstridDependencyInjector;
|
||||
import com.todoroo.astrid.service.TagDataService;
|
||||
import com.todoroo.astrid.service.StatisticsService;
|
||||
import com.todoroo.astrid.service.ThemeService;
|
||||
|
||||
/**
|
||||
* Activity that displays a user's task lists and allows users
|
||||
* to filter their task list.
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class TagDataListActivity extends ListActivity implements OnItemClickListener {
|
||||
|
||||
// --- constants
|
||||
|
||||
private static final int REQUEST_LOG_IN = 1;
|
||||
private static final int REQUEST_SHOW_GOAL = 2;
|
||||
|
||||
private static final int MENU_REFRESH_ID = Menu.FIRST + 0;
|
||||
|
||||
// --- instance variables
|
||||
|
||||
@Autowired ExceptionService exceptionService;
|
||||
@Autowired TagDataService tagDataService;
|
||||
@Autowired ActFmPreferenceService actFmPreferenceService;
|
||||
@Autowired ActFmSyncService actFmSyncService;
|
||||
|
||||
protected TagDataAdapter adapter = null;
|
||||
protected AtomicReference<String> queryTemplate = new AtomicReference<String>();
|
||||
|
||||
/* ======================================================================
|
||||
* ======================================================= initialization
|
||||
* ====================================================================== */
|
||||
|
||||
static {
|
||||
AstridDependencyInjector.initialize();
|
||||
}
|
||||
|
||||
/** Called when loading up the activity */
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
DependencyInjectionService.getInstance().inject(this);
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.tagData_list_activity);
|
||||
ThemeService.applyTheme(this);
|
||||
|
||||
if(!actFmPreferenceService.isLoggedIn()) {
|
||||
Intent login = new Intent(this, ActFmLoginActivity.class);
|
||||
login.putExtra(ActFmLoginActivity.EXTRA_DO_NOT_SYNC, true);
|
||||
startActivityForResult(login, REQUEST_LOG_IN);
|
||||
}
|
||||
|
||||
initializeUIComponents();
|
||||
setUpList();
|
||||
refreshList(false);
|
||||
}
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
private void initializeUIComponents() {
|
||||
((ImageButton) findViewById(R.id.extendedAddButton)).setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
DialogUtilities.okDialog(TagDataListActivity.this, "unsupported", null);
|
||||
}
|
||||
});
|
||||
|
||||
((ImageButton) findViewById(R.id.extendedAddButton)).setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
DialogUtilities.okDialog(TagDataListActivity.this, "unsupported", null);
|
||||
}
|
||||
});
|
||||
|
||||
((ImageView) findViewById(R.id.goals)).setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(TagDataListActivity.this, TaskListActivity.class);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create options menu (displayed when user presses menu key)
|
||||
*
|
||||
* @return true if menu should be displayed
|
||||
*/
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
if(menu.size() > 0)
|
||||
return true;
|
||||
|
||||
MenuItem item;
|
||||
|
||||
item = menu.add(Menu.NONE, MENU_REFRESH_ID, Menu.NONE,
|
||||
R.string.PLA_menu_refresh);
|
||||
item.setIcon(R.drawable.ic_menu_refresh);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ======================================================================
|
||||
* ============================================================ lifecycle
|
||||
* ====================================================================== */
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
StatisticsService.sessionStart(this);
|
||||
StatisticsService.reportEvent("goal-list"); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
StatisticsService.sessionStop(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if(requestCode == REQUEST_LOG_IN) {
|
||||
if(resultCode == RESULT_CANCELED)
|
||||
finish();
|
||||
else
|
||||
refreshList(true);
|
||||
} else
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
/* ======================================================================
|
||||
* ====================================================== populating list
|
||||
* ====================================================================== */
|
||||
|
||||
/** Sets up the coach list adapter */
|
||||
protected void setUpList() {
|
||||
queryTemplate.set(new QueryTemplate().where(TagDataCriteria.isTeam()).toString());
|
||||
TodorooCursor<TagData> currentCursor = tagDataService.fetchFiltered(queryTemplate.get(),
|
||||
null, TagData.PROPERTIES);
|
||||
startManagingCursor(currentCursor);
|
||||
|
||||
adapter = new TagDataAdapter(this, R.layout.tagData_adapter_row,
|
||||
currentCursor, queryTemplate, false, null);
|
||||
setListAdapter(adapter);
|
||||
|
||||
getListView().setOnItemClickListener(this);
|
||||
}
|
||||
|
||||
/** refresh the list with latest data from the web */
|
||||
private void refreshList(boolean manual) {
|
||||
actFmSyncService.fetchTagDataDashboard(manual, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Cursor cursor = adapter.getCursor();
|
||||
cursor.requery();
|
||||
startManagingCursor(cursor);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ======================================================================
|
||||
* ============================================================== actions
|
||||
* ====================================================================== */
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
|
||||
Intent intent = new Intent(this, TagDataViewActivity.class);
|
||||
intent.putExtra(TagDataViewActivity.EXTRA_PROJECT_ID, id);
|
||||
startActivityForResult(intent, REQUEST_SHOW_GOAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemSelected(int featureId, final MenuItem item) {
|
||||
|
||||
// handle my own menus
|
||||
switch (item.getItemId()) {
|
||||
case MENU_REFRESH_ID: {
|
||||
refreshList(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package com.todoroo.astrid.actfm;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
import com.todoroo.astrid.actfm.sync.ActFmPreferenceService;
|
||||
import com.todoroo.astrid.api.AstridApiConstants;
|
||||
import com.todoroo.astrid.api.TaskAction;
|
||||
import com.todoroo.astrid.core.PluginServices;
|
||||
import com.todoroo.astrid.data.TagData;
|
||||
|
||||
public class ShowTagDataExposer extends BroadcastReceiver {
|
||||
|
||||
private static final String FILTER_ACTION = "com.todoroo.astrid.SHOW_PROJECT"; //$NON-NLS-1$
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
ContextManager.setContext(context);
|
||||
long taskId = intent.getLongExtra(AstridApiConstants.EXTRAS_TASK_ID, -1);
|
||||
if(taskId == -1)
|
||||
return;
|
||||
|
||||
TagData tagData = PluginServices.getTagDataService().getTagData(taskId,
|
||||
TagData.ID, TagData.TITLE);
|
||||
if(tagData == null)
|
||||
return;
|
||||
|
||||
if(AstridApiConstants.BROADCAST_REQUEST_ACTIONS.equals(intent.getAction())) {
|
||||
final String label = tagData.getValue(TagData.TITLE);
|
||||
final Drawable drawable = context.getResources().getDrawable(R.drawable.tango_users);
|
||||
Intent newIntent = new Intent(FILTER_ACTION);
|
||||
newIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId);
|
||||
Bitmap icon = ((BitmapDrawable)drawable).getBitmap();
|
||||
TaskAction action = new TaskAction(label,
|
||||
PendingIntent.getBroadcast(context, (int)taskId, newIntent, 0), icon);
|
||||
|
||||
// transmit
|
||||
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_ACTIONS);
|
||||
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_ADDON, ActFmPreferenceService.IDENTIFIER);
|
||||
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, action);
|
||||
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId);
|
||||
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
|
||||
} else if(FILTER_ACTION.equals(intent.getAction())) {
|
||||
Intent launchIntent = new Intent(context, TagDataViewActivity.class);
|
||||
launchIntent.putExtra(TagDataViewActivity.EXTRA_PROJECT_ID, tagData.getId());
|
||||
ContextManager.getContext().startActivity(launchIntent);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,633 @@
|
||||
package com.todoroo.astrid.actfm;
|
||||
|
||||
import greendroid.widget.AsyncImageView;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.provider.MediaStore;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.View.OnTouchListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TabHost;
|
||||
import android.widget.TabHost.OnTabChangeListener;
|
||||
import android.widget.TabWidget;
|
||||
import android.widget.TextView;
|
||||
import android.widget.TextView.OnEditorActionListener;
|
||||
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.andlib.data.TodorooCursor;
|
||||
import com.todoroo.andlib.service.Autowired;
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
import com.todoroo.andlib.service.NotificationManager;
|
||||
import com.todoroo.andlib.service.NotificationManager.AndroidNotificationManager;
|
||||
import com.todoroo.andlib.sql.Criterion;
|
||||
import com.todoroo.andlib.sql.Query;
|
||||
import com.todoroo.andlib.utility.DateUtilities;
|
||||
import com.todoroo.andlib.utility.DialogUtilities;
|
||||
import com.todoroo.andlib.utility.Preferences;
|
||||
import com.todoroo.astrid.actfm.sync.ActFmPreferenceService;
|
||||
import com.todoroo.astrid.actfm.sync.ActFmSyncService;
|
||||
import com.todoroo.astrid.activity.TaskListActivity;
|
||||
import com.todoroo.astrid.adapter.UpdateAdapter;
|
||||
import com.todoroo.astrid.api.AstridApiConstants;
|
||||
import com.todoroo.astrid.core.SortHelper;
|
||||
import com.todoroo.astrid.dao.UpdateDao;
|
||||
import com.todoroo.astrid.data.TagData;
|
||||
import com.todoroo.astrid.data.Update;
|
||||
import com.todoroo.astrid.service.TagDataService;
|
||||
import com.todoroo.astrid.tags.TagService;
|
||||
import com.todoroo.astrid.ui.PeopleContainer;
|
||||
import com.todoroo.astrid.utility.Flags;
|
||||
|
||||
public class TagViewActivity extends TaskListActivity implements OnTabChangeListener {
|
||||
|
||||
private static final String LAST_FETCH_KEY = "tag-fetch-"; //$NON-NLS-1$
|
||||
|
||||
public static final String BROADCAST_TAG_ACTIVITY = AstridApiConstants.PACKAGE + ".TAG_ACTIVITY"; //$NON-NLS-1$
|
||||
|
||||
public static final String EXTRA_TAG_NAME = "tag"; //$NON-NLS-1$
|
||||
public static final String EXTRA_TAG_REMOTE_ID = "remoteId"; //$NON-NLS-1$
|
||||
public static final String EXTRA_START_TAB = "tab"; //$NON-NLS-1$
|
||||
|
||||
protected static final int MENU_REFRESH_ID = MENU_SYNC_ID;
|
||||
|
||||
protected static final int REQUEST_CODE_CAMERA = 1;
|
||||
protected static final int REQUEST_CODE_PICTURE = 2;
|
||||
|
||||
|
||||
private TagData tagData;
|
||||
|
||||
@Autowired TagDataService tagDataService;
|
||||
|
||||
@Autowired ActFmSyncService actFmSyncService;
|
||||
|
||||
@Autowired UpdateDao updateDao;
|
||||
|
||||
private TabHost tabHost;
|
||||
private UpdateAdapter updateAdapter;
|
||||
private PeopleContainer tagMembers;
|
||||
private EditText addCommentField;
|
||||
private AsyncImageView picture;
|
||||
private EditText tagName;
|
||||
private boolean dataLoaded = false;
|
||||
|
||||
// --- UI initialization
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
getListView().setOnKeyListener(null);
|
||||
|
||||
// allow for text field entry, needed for android bug #2516
|
||||
OnTouchListener onTouch = new OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
v.requestFocusFromTouch();
|
||||
return false;
|
||||
}
|
||||
};
|
||||
((EditText) findViewById(R.id.quickAddText)).setOnTouchListener(onTouch);
|
||||
((EditText) findViewById(R.id.commentField)).setOnTouchListener(onTouch);
|
||||
|
||||
if(getIntent().hasExtra(EXTRA_START_TAB))
|
||||
tabHost.setCurrentTab(getIntent().getIntExtra(EXTRA_START_TAB, 0));
|
||||
}
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
@Override
|
||||
protected View getListBody(ViewGroup root) {
|
||||
ViewGroup parent = (ViewGroup) getLayoutInflater().inflate(R.layout.task_list_body_tag, root, false);
|
||||
ViewGroup tabContent = (ViewGroup) parent.findViewById(android.R.id.tabcontent);
|
||||
|
||||
String[] tabLabels = getResources().getStringArray(R.array.TVA_tabs);
|
||||
tabHost = (TabHost) parent.findViewById(android.R.id.tabhost);
|
||||
TabWidget tabWidget = (TabWidget) parent.findViewById(android.R.id.tabs);
|
||||
tabHost.setup();
|
||||
|
||||
// set up tabs
|
||||
View taskList = super.getListBody(parent);
|
||||
tabContent.addView(taskList);
|
||||
addTab(tabWidget, taskList.getId(), "tasks", tabLabels[0]);
|
||||
addTab(tabWidget, R.id.tab_updates, "updates", tabLabels[1]);
|
||||
addTab(tabWidget, R.id.tab_settings, "members", tabLabels[2]);
|
||||
|
||||
tabHost.setOnTabChangedListener(this);
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
||||
private void addTab(TabWidget tabWidget, int contentId, String id, String label) {
|
||||
TabHost.TabSpec spec = tabHost.newTabSpec(id);
|
||||
spec.setContent(contentId);
|
||||
TextView textIndicator = (TextView) getLayoutInflater().inflate(R.layout.gd_tab_indicator, tabWidget, false);
|
||||
textIndicator.setText(label);
|
||||
spec.setIndicator(textIndicator);
|
||||
tabHost.addTab(spec);
|
||||
}
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
@Override
|
||||
public void onTabChanged(String tabId) {
|
||||
if(tabId.equals("tasks"))
|
||||
findViewById(R.id.taskListFooter).setVisibility(View.VISIBLE);
|
||||
else
|
||||
findViewById(R.id.taskListFooter).setVisibility(View.GONE);
|
||||
|
||||
if(tabId.equals("updates"))
|
||||
findViewById(R.id.updatesFooter).setVisibility(View.VISIBLE);
|
||||
else
|
||||
findViewById(R.id.updatesFooter).setVisibility(View.GONE);
|
||||
|
||||
if(tabId.equals("members"))
|
||||
findViewById(R.id.membersFooter).setVisibility(View.VISIBLE);
|
||||
else
|
||||
findViewById(R.id.membersFooter).setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create options menu (displayed when user presses menu key)
|
||||
*
|
||||
* @return true if menu should be displayed
|
||||
*/
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
if(menu.size() > 0)
|
||||
menu.clear();
|
||||
|
||||
MenuItem item;
|
||||
|
||||
item = menu.add(Menu.NONE, MENU_ADDONS_ID, Menu.NONE,
|
||||
R.string.TLA_menu_addons);
|
||||
item.setIcon(android.R.drawable.ic_menu_set_as);
|
||||
|
||||
item = menu.add(Menu.NONE, MENU_SETTINGS_ID, Menu.NONE,
|
||||
R.string.TLA_menu_settings);
|
||||
item.setIcon(android.R.drawable.ic_menu_preferences);
|
||||
|
||||
item = menu.add(Menu.NONE, MENU_SORT_ID, Menu.NONE,
|
||||
R.string.TLA_menu_sort);
|
||||
item.setIcon(android.R.drawable.ic_menu_sort_by_size);
|
||||
|
||||
item = menu.add(Menu.NONE, MENU_REFRESH_ID, Menu.NONE,
|
||||
R.string.actfm_TVA_menu_refresh);
|
||||
item.setIcon(R.drawable.ic_menu_refresh);
|
||||
|
||||
item = menu.add(Menu.NONE, MENU_HELP_ID, Menu.NONE,
|
||||
R.string.TLA_menu_help);
|
||||
item.setIcon(android.R.drawable.ic_menu_help);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void setUpMemberPage() {
|
||||
tagMembers = (PeopleContainer) findViewById(R.id.members_container);
|
||||
tagName = (EditText) findViewById(R.id.tag_name);
|
||||
picture = (AsyncImageView) findViewById(R.id.picture);
|
||||
|
||||
picture.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View arg0) {
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<String>(TagViewActivity.this,
|
||||
android.R.layout.simple_spinner_dropdown_item, new String[] {
|
||||
getString(R.string.actfm_picture_camera),
|
||||
getString(R.string.actfm_picture_gallery),
|
||||
});
|
||||
|
||||
DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
|
||||
@SuppressWarnings("nls")
|
||||
@Override
|
||||
public void onClick(DialogInterface d, int which) {
|
||||
if(which == 0) {
|
||||
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||
startActivityForResult(intent, REQUEST_CODE_CAMERA);
|
||||
} else {
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.setType("image/*");
|
||||
startActivityForResult(Intent.createChooser(intent,
|
||||
getString(R.string.actfm_TVA_tag_picture)), REQUEST_CODE_PICTURE);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// show a menu of available options
|
||||
new AlertDialog.Builder(TagViewActivity.this)
|
||||
.setAdapter(adapter, listener)
|
||||
.show().setOwnerActivity(TagViewActivity.this);
|
||||
}
|
||||
});
|
||||
|
||||
findViewById(R.id.saveMembers).setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View arg0) {
|
||||
saveSettings();
|
||||
}
|
||||
});
|
||||
|
||||
refreshMembersPage();
|
||||
}
|
||||
|
||||
protected void setUpUpdateList() {
|
||||
TodorooCursor<Update> currentCursor = tagDataService.getUpdates(tagData);
|
||||
startManagingCursor(currentCursor);
|
||||
|
||||
updateAdapter = new UpdateAdapter(this, R.layout.update_adapter_row,
|
||||
currentCursor, false, null);
|
||||
((ListView)findViewById(R.id.tab_updates)).setAdapter(updateAdapter);
|
||||
|
||||
final ImageButton quickAddButton = (ImageButton) findViewById(R.id.commentButton);
|
||||
addCommentField = (EditText) findViewById(R.id.commentField);
|
||||
addCommentField.setOnEditorActionListener(new OnEditorActionListener() {
|
||||
@Override
|
||||
public boolean onEditorAction(TextView view, int actionId, KeyEvent event) {
|
||||
if(actionId == EditorInfo.IME_NULL && addCommentField.getText().length() > 0) {
|
||||
addComment();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
addCommentField.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
quickAddButton.setVisibility((s.length() > 0) ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
//
|
||||
}
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
//
|
||||
}
|
||||
});
|
||||
quickAddButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
addComment();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// --- data loading
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
|
||||
synchronized(this) {
|
||||
if(dataLoaded)
|
||||
return;
|
||||
dataLoaded = true;
|
||||
}
|
||||
|
||||
String tag = getIntent().getStringExtra(EXTRA_TAG_NAME);
|
||||
long remoteId = getIntent().getLongExtra(EXTRA_TAG_REMOTE_ID, 0);
|
||||
|
||||
if(tag == null && remoteId == 0)
|
||||
return;
|
||||
|
||||
TodorooCursor<TagData> cursor = tagDataService.query(Query.select(TagData.PROPERTIES).where(Criterion.or(TagData.NAME.eq(tag),
|
||||
Criterion.and(TagData.REMOTE_ID.gt(0), TagData.REMOTE_ID.eq(remoteId)))));
|
||||
try {
|
||||
tagData = new TagData();
|
||||
if(cursor.getCount() == 0) {
|
||||
tagData.setValue(TagData.NAME, tag);
|
||||
tagData.setValue(TagData.REMOTE_ID, remoteId);
|
||||
tagDataService.save(tagData);
|
||||
} else {
|
||||
cursor.moveToFirst();
|
||||
tagData.readFromCursor(cursor);
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
String fetchKey = LAST_FETCH_KEY + tagData.getId();
|
||||
long lastFetchDate = Preferences.getLong(fetchKey, 0);
|
||||
if(DateUtilities.now() > lastFetchDate + 300000L) {
|
||||
refreshData(false, false);
|
||||
Preferences.setLong(fetchKey, DateUtilities.now());
|
||||
}
|
||||
|
||||
setUpUpdateList();
|
||||
setUpMemberPage();
|
||||
}
|
||||
|
||||
private void refreshUpdatesList() {
|
||||
Cursor cursor = updateAdapter.getCursor();
|
||||
cursor.requery();
|
||||
startManagingCursor(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadTaskListContent(boolean requery) {
|
||||
super.loadTaskListContent(requery);
|
||||
int count = taskAdapter.getCursor().getCount();
|
||||
|
||||
if(tagData != null && sortFlags <= SortHelper.FLAG_REVERSE_SORT &&
|
||||
count != tagData.getValue(TagData.TASK_COUNT)) {
|
||||
tagData.setValue(TagData.TASK_COUNT, count);
|
||||
tagDataService.save(tagData);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
private void refreshMembersPage() {
|
||||
tagName.setText(tagData.getValue(TagData.NAME));
|
||||
picture.setUrl(tagData.getValue(TagData.PICTURE));
|
||||
|
||||
TextView ownerLabel = (TextView) findViewById(R.id.tag_owner);
|
||||
try {
|
||||
if(tagData.getFlag(TagData.FLAGS, TagData.FLAG_EMERGENT)) {
|
||||
ownerLabel.setText(String.format("<%s>", getString(R.string.actfm_TVA_tag_owner_none)));
|
||||
} else if(tagData.getValue(TagData.USER_ID) == 0) {
|
||||
ownerLabel.setText(Preferences.getStringValue(ActFmPreferenceService.PREF_NAME));
|
||||
} else {
|
||||
JSONObject owner = new JSONObject(tagData.getValue(TagData.USER));
|
||||
ownerLabel.setText(owner.getString("name"));
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e("tag-view-activity", "json error refresh owner", e);
|
||||
ownerLabel.setText("<error>");
|
||||
System.err.println(tagData.getValue(TagData.USER));
|
||||
}
|
||||
|
||||
tagMembers.removeAllViews();
|
||||
String peopleJson = tagData.getValue(TagData.MEMBERS);
|
||||
if(!TextUtils.isEmpty(peopleJson)) {
|
||||
try {
|
||||
JSONArray people = new JSONArray(peopleJson);
|
||||
for(int i = 0; i < people.length(); i++) {
|
||||
JSONObject person = people.getJSONObject(i);
|
||||
TextView textView = null;
|
||||
|
||||
if(person.has("id") && person.getLong("id") == ActFmPreferenceService.userId())
|
||||
textView = tagMembers.addPerson(Preferences.getStringValue(ActFmPreferenceService.PREF_NAME));
|
||||
else if(!TextUtils.isEmpty(person.optString("name")))
|
||||
textView = tagMembers.addPerson(person.getString("name"));
|
||||
else if(!TextUtils.isEmpty(person.optString("email")))
|
||||
textView = tagMembers.addPerson(person.getString("email"));
|
||||
|
||||
if(textView != null) {
|
||||
textView.setTag(person);
|
||||
textView.setEnabled(false);
|
||||
}
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
System.err.println(peopleJson);
|
||||
Log.e("tag-view-activity", "json error refresh members", e);
|
||||
}
|
||||
}
|
||||
|
||||
tagMembers.addPerson(""); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
/** refresh the list with latest data from the web */
|
||||
private void refreshData(final boolean manual, boolean bypassTagShow) {
|
||||
final boolean noRemoteId = tagData.getValue(TagData.REMOTE_ID) == 0;
|
||||
|
||||
final ProgressDialog progressDialog;
|
||||
if(manual && !noRemoteId)
|
||||
progressDialog = DialogUtilities.progressDialog(this, getString(R.string.DLG_please_wait));
|
||||
else
|
||||
progressDialog = null;
|
||||
|
||||
Thread tagShowThread = new Thread(new Runnable() {
|
||||
@SuppressWarnings("nls")
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
String oldName = tagData.getValue(TagData.NAME);
|
||||
actFmSyncService.fetchTag(tagData);
|
||||
if(noRemoteId && tagData.getValue(TagData.REMOTE_ID) > 0) {
|
||||
refreshData(manual, true);
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
refreshUpdatesList();
|
||||
refreshMembersPage();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if(!oldName.equals(tagData.getValue(TagData.NAME))) {
|
||||
TagService.getInstance().rename(oldName,
|
||||
tagData.getValue(TagData.NAME));
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
Log.e("tag-view-activity", "error-fetching-task-io", e);
|
||||
} catch (JSONException e) {
|
||||
Log.e("tag-view-activity", "error-fetching-task", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
if(!bypassTagShow)
|
||||
tagShowThread.start();
|
||||
|
||||
if(noRemoteId)
|
||||
return;
|
||||
|
||||
actFmSyncService.fetchTasksForTag(tagData, manual, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
loadTaskListContent(true);
|
||||
DialogUtilities.dismissDialog(TagViewActivity.this, progressDialog);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
actFmSyncService.fetchUpdatesForTag(tagData, manual, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
refreshUpdatesList();
|
||||
DialogUtilities.dismissDialog(TagViewActivity.this, progressDialog);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// --- receivers
|
||||
|
||||
private final BroadcastReceiver notifyReceiver = new BroadcastReceiver() {
|
||||
@SuppressWarnings("nls")
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
System.err.println("TVA thug hustlin - ");
|
||||
|
||||
if(!intent.hasExtra("tag_id"))
|
||||
return;
|
||||
System.err.println(Long.toString(tagData.getValue(TagData.REMOTE_ID)) +
|
||||
" VS " + intent.getStringExtra("tag_id"));
|
||||
if(!Long.toString(tagData.getValue(TagData.REMOTE_ID)).equals(intent.getStringExtra("tag_id")))
|
||||
return;
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
System.err.println("REFRESH updates list pa-pa-pa");
|
||||
refreshUpdatesList();
|
||||
}
|
||||
});
|
||||
refreshData(false, true);
|
||||
|
||||
NotificationManager nm = new AndroidNotificationManager(ContextManager.getContext());
|
||||
nm.cancel(tagData.getValue(TagData.REMOTE_ID).intValue());
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
IntentFilter intentFilter = new IntentFilter(BROADCAST_TAG_ACTIVITY);
|
||||
registerReceiver(notifyReceiver, intentFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
unregisterReceiver(notifyReceiver);
|
||||
}
|
||||
|
||||
// --- events
|
||||
|
||||
private void saveSettings() {
|
||||
String oldName = tagData.getValue(TagData.NAME);
|
||||
String newName = tagName.getText().toString();
|
||||
|
||||
if(!oldName.equals(newName)) {
|
||||
tagData.setValue(TagData.NAME, newName);
|
||||
TagService.getInstance().rename(oldName, newName);
|
||||
tagData.setFlag(TagData.FLAGS, TagData.FLAG_EMERGENT, false);
|
||||
}
|
||||
|
||||
JSONArray members = tagMembers.toJSONArray();
|
||||
tagData.setValue(TagData.MEMBERS, members.toString());
|
||||
tagData.setValue(TagData.MEMBER_COUNT, members.length());
|
||||
Flags.set(Flags.TOAST_ON_SAVE);
|
||||
tagDataService.save(tagData);
|
||||
|
||||
refreshMembersPage();
|
||||
}
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if(requestCode == REQUEST_CODE_CAMERA && resultCode == RESULT_OK) {
|
||||
Bitmap bitmap = data.getParcelableExtra("data");
|
||||
if(bitmap != null) {
|
||||
picture.setImageBitmap(bitmap);
|
||||
uploadTagPicture(bitmap);
|
||||
}
|
||||
} else if(requestCode == REQUEST_CODE_PICTURE && resultCode == RESULT_OK) {
|
||||
Uri uri = data.getData();
|
||||
String[] projection = { MediaStore.Images.Media.DATA };
|
||||
Cursor cursor = managedQuery(uri, projection, null, null, null);
|
||||
String path;
|
||||
|
||||
if(cursor != null) {
|
||||
try {
|
||||
int column_index = cursor
|
||||
.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
|
||||
cursor.moveToFirst();
|
||||
path = cursor.getString(column_index);
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
} else {
|
||||
path = uri.getPath();
|
||||
}
|
||||
|
||||
Bitmap bitmap = BitmapFactory.decodeFile(path);
|
||||
if(bitmap != null) {
|
||||
picture.setImageBitmap(bitmap);
|
||||
uploadTagPicture(bitmap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadTagPicture(final Bitmap bitmap) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
String url = actFmSyncService.setTagPicture(tagData.getValue(TagData.REMOTE_ID), bitmap);
|
||||
tagData.setValue(TagData.PICTURE, url);
|
||||
Flags.set(Flags.SUPPRESS_SYNC);
|
||||
tagDataService.save(tagData);
|
||||
} catch (IOException e) {
|
||||
DialogUtilities.okDialog(TagViewActivity.this, e.toString(), null);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void addComment() {
|
||||
Update update = new Update();
|
||||
update.setValue(Update.MESSAGE, addCommentField.getText().toString());
|
||||
update.setValue(Update.ACTION_CODE, "tag_comment"); //$NON-NLS-1$
|
||||
update.setValue(Update.USER_ID, 0L);
|
||||
update.setValue(Update.TAG, tagData.getId());
|
||||
update.setValue(Update.CREATION_DATE, DateUtilities.now());
|
||||
updateDao.createNew(update);
|
||||
|
||||
addCommentField.setText(""); //$NON-NLS-1$
|
||||
refreshUpdatesList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemSelected(int featureId, final MenuItem item) {
|
||||
// handle my own menus
|
||||
switch (item.getItemId()) {
|
||||
case MENU_REFRESH_ID:
|
||||
refreshData(true, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onMenuItemSelected(featureId, item);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package com.todoroo.astrid.actfm;
|
||||
|
||||
import com.todoroo.andlib.data.Property.IntegerProperty;
|
||||
import com.todoroo.andlib.data.Property.LongProperty;
|
||||
import com.todoroo.andlib.data.Property.StringProperty;
|
||||
import com.todoroo.astrid.data.Metadata;
|
||||
|
||||
/**
|
||||
* Metadata entry for a task shared with astrid.com
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class TaskFields {
|
||||
|
||||
/** metadata key */
|
||||
public static final String METADATA_KEY = "actfm"; //$NON-NLS-1$
|
||||
|
||||
/** remote id*/
|
||||
public static final LongProperty REMOTE_ID = new LongProperty(Metadata.TABLE, Metadata.VALUE2.name);
|
||||
|
||||
/** goal id */
|
||||
public static final LongProperty GOAL_ID = new LongProperty(Metadata.TABLE, Metadata.VALUE2.name);
|
||||
|
||||
/** user id */
|
||||
public static final LongProperty USER_ID = new LongProperty(Metadata.TABLE, Metadata.VALUE3.name);
|
||||
|
||||
/** user */
|
||||
public static final StringProperty USER = Metadata.VALUE4;
|
||||
|
||||
/** comment count */
|
||||
public static final IntegerProperty COMMENT_COUNT = new IntegerProperty(Metadata.TABLE,
|
||||
Metadata.VALUE4.name);
|
||||
|
||||
}
|
@ -0,0 +1,203 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.actfm.sync;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
|
||||
import com.todoroo.andlib.data.Property;
|
||||
import com.todoroo.andlib.data.TodorooCursor;
|
||||
import com.todoroo.andlib.service.Autowired;
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
import com.todoroo.andlib.service.DependencyInjectionService;
|
||||
import com.todoroo.andlib.sql.Criterion;
|
||||
import com.todoroo.andlib.sql.Join;
|
||||
import com.todoroo.andlib.sql.Query;
|
||||
import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria;
|
||||
import com.todoroo.astrid.dao.TaskDao;
|
||||
import com.todoroo.astrid.dao.TaskDao.TaskCriteria;
|
||||
import com.todoroo.astrid.data.Metadata;
|
||||
import com.todoroo.astrid.data.TagData;
|
||||
import com.todoroo.astrid.data.Task;
|
||||
import com.todoroo.astrid.notes.NoteMetadata;
|
||||
import com.todoroo.astrid.service.MetadataService;
|
||||
import com.todoroo.astrid.service.StartupService;
|
||||
import com.todoroo.astrid.service.TagDataService;
|
||||
import com.todoroo.astrid.tags.TagService;
|
||||
|
||||
public final class ActFmDataService {
|
||||
|
||||
// --- constants
|
||||
|
||||
/** Utility for joining tasks with metadata */
|
||||
public static final Join METADATA_JOIN = Join.left(Metadata.TABLE, Task.ID.eq(Metadata.TASK));
|
||||
|
||||
/** NoteMetadata provider string */
|
||||
public static final String NOTE_PROVIDER = "actfm-comment"; //$NON-NLS-1$
|
||||
|
||||
// --- instance variables
|
||||
|
||||
protected final Context context;
|
||||
|
||||
@Autowired TaskDao taskDao;
|
||||
|
||||
@Autowired MetadataService metadataService;
|
||||
|
||||
@Autowired ActFmPreferenceService actFmPreferenceService;
|
||||
|
||||
@Autowired TagDataService tagDataService;
|
||||
|
||||
public ActFmDataService() {
|
||||
this.context = ContextManager.getContext();
|
||||
DependencyInjectionService.getInstance().inject(this);
|
||||
}
|
||||
|
||||
// --- task and metadata methods
|
||||
|
||||
/**
|
||||
* Clears metadata information. Used when user logs out of service
|
||||
*/
|
||||
public void clearMetadata() {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(Task.REMOTE_ID.name, 0);
|
||||
taskDao.updateMultiple(values, Criterion.all);
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently, this method does nothing, there is an alternate method to create tasks
|
||||
* @param properties
|
||||
* @return
|
||||
*/
|
||||
public TodorooCursor<Task> getLocallyCreated(Property<?>[] properties) {
|
||||
return taskDao.query(Query.select(properties).where(Criterion.and(TaskCriteria.isActive(),
|
||||
Task.ID.gt(StartupService.INTRO_TASK_SIZE),
|
||||
Task.REMOTE_ID.eq(0))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets tasks that were modified since last sync
|
||||
* @param properties
|
||||
* @return null if never sync'd
|
||||
*/
|
||||
public TodorooCursor<Task> getLocallyUpdated(Property<?>[] properties) {
|
||||
long lastSyncDate = actFmPreferenceService.getLastSyncDate();
|
||||
if(lastSyncDate == 0)
|
||||
return taskDao.query(Query.select(properties).where(Criterion.none));
|
||||
return
|
||||
taskDao.query(Query.select(properties).
|
||||
where(Criterion.and(Task.REMOTE_ID.gt(0),
|
||||
Task.MODIFICATION_DATE.gt(lastSyncDate),
|
||||
Task.MODIFICATION_DATE.gt(Task.LAST_SYNC))).groupBy(Task.ID));
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for a local task with same remote id, updates this task's id
|
||||
* @param remoteTask
|
||||
* @return true if found local match
|
||||
*/
|
||||
public boolean findLocalMatch(ActFmTaskContainer remoteTask) {
|
||||
if(remoteTask.task.getId() != Task.NO_ID)
|
||||
return true;
|
||||
TodorooCursor<Task> cursor = taskDao.query(Query.select(Task.ID).
|
||||
where(Task.REMOTE_ID.eq(remoteTask.task.getValue(Task.REMOTE_ID))));
|
||||
try {
|
||||
if(cursor.getCount() == 0)
|
||||
return false;
|
||||
cursor.moveToFirst();
|
||||
remoteTask.task.setId(cursor.get(Task.ID));
|
||||
return true;
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a task and its metadata
|
||||
* @param task
|
||||
*/
|
||||
public void saveTaskAndMetadata(ActFmTaskContainer task) {
|
||||
taskDao.save(task.task);
|
||||
|
||||
metadataService.synchronizeMetadata(task.task.getId(), task.metadata,
|
||||
Criterion.or(Criterion.and(MetadataCriteria.withKey(NoteMetadata.METADATA_KEY),
|
||||
NoteMetadata.EXT_PROVIDER.eq(NOTE_PROVIDER)),
|
||||
MetadataCriteria.withKey(TagService.KEY)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a task and its metadata
|
||||
* @param task
|
||||
* @return
|
||||
*/
|
||||
public ActFmTaskContainer readTaskAndMetadata(TodorooCursor<Task> taskCursor) {
|
||||
Task task = new Task(taskCursor);
|
||||
|
||||
// read tags, notes, etc
|
||||
ArrayList<Metadata> metadata = new ArrayList<Metadata>();
|
||||
TodorooCursor<Metadata> metadataCursor = metadataService.query(Query.select(Metadata.PROPERTIES).
|
||||
where(Criterion.and(MetadataCriteria.byTask(task.getId()),
|
||||
Criterion.or(MetadataCriteria.withKey(TagService.KEY),
|
||||
MetadataCriteria.withKey(NoteMetadata.METADATA_KEY)))));
|
||||
try {
|
||||
for(metadataCursor.moveToFirst(); !metadataCursor.isAfterLast(); metadataCursor.moveToNext()) {
|
||||
metadata.add(new Metadata(metadataCursor));
|
||||
}
|
||||
} finally {
|
||||
metadataCursor.close();
|
||||
}
|
||||
|
||||
return new ActFmTaskContainer(task, metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads task notes out of a task
|
||||
*/
|
||||
public TodorooCursor<Metadata> getTaskNotesCursor(long taskId) {
|
||||
TodorooCursor<Metadata> cursor = metadataService.query(Query.select(Metadata.PROPERTIES).
|
||||
where(MetadataCriteria.byTaskAndwithKey(taskId, NoteMetadata.METADATA_KEY)));
|
||||
return cursor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save / Merge JSON tagData
|
||||
* @param tagObject
|
||||
* @throws JSONException
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public void saveTagData(JSONObject tagObject) throws JSONException {
|
||||
TodorooCursor<TagData> cursor = tagDataService.query(Query.select(TagData.PROPERTIES).where(
|
||||
Criterion.or(TagData.REMOTE_ID.eq(tagObject.get("id")),
|
||||
Criterion.and(TagData.REMOTE_ID.eq(0),
|
||||
TagData.NAME.eq(tagObject.getString("name"))))));
|
||||
try {
|
||||
cursor.moveToNext();
|
||||
TagData tagData = new TagData();
|
||||
if(!cursor.isAfterLast()) {
|
||||
tagData.readFromCursor(cursor);
|
||||
if(!tagData.getValue(TagData.NAME).equals(tagObject.getString("name")))
|
||||
TagService.getInstance().rename(tagData.getValue(TagData.NAME), tagObject.getString("name"));
|
||||
cursor.moveToNext();
|
||||
}
|
||||
ActFmSyncService.JsonHelper.tagFromJson(tagObject, tagData);
|
||||
tagDataService.save(tagData);
|
||||
|
||||
// delete the rest
|
||||
|
||||
for(; !cursor.isAfterLast(); cursor.moveToNext()) {
|
||||
tagData.readFromCursor(cursor);
|
||||
if(!tagData.getValue(TagData.NAME).equals(tagObject.getString("name")))
|
||||
TagService.getInstance().rename(tagData.getValue(TagData.NAME), tagObject.getString("name"));
|
||||
tagDataService.delete(tagData.getId());
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
package com.todoroo.astrid.actfm.sync;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.andlib.utility.Preferences;
|
||||
import com.todoroo.astrid.data.RemoteModel;
|
||||
import com.todoroo.astrid.sync.SyncProviderUtilities;
|
||||
|
||||
/**
|
||||
* Methods for working with GTasks preferences
|
||||
*
|
||||
* @author timsu
|
||||
*
|
||||
*/
|
||||
public class ActFmPreferenceService extends SyncProviderUtilities {
|
||||
|
||||
/** add-on identifier */
|
||||
public static final String IDENTIFIER = "actfm"; //$NON-NLS-1$
|
||||
|
||||
@Override
|
||||
public String getIdentifier() {
|
||||
return IDENTIFIER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSyncIntervalKey() {
|
||||
return R.string.actfm_APr_interval_key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearLastSyncDate() {
|
||||
super.clearLastSyncDate();
|
||||
Preferences.setInt(ActFmPreferenceService.PREF_SERVER_TIME, 0);
|
||||
}
|
||||
|
||||
// --- user management
|
||||
|
||||
/**
|
||||
* @return get user id
|
||||
*/
|
||||
public static long userId() {
|
||||
return Preferences.getLong(PREF_USER_ID, -1L);
|
||||
}
|
||||
|
||||
/** Act.fm current user id */
|
||||
public static final String PREF_USER_ID = IDENTIFIER + "_user"; //$NON-NLS-1$
|
||||
|
||||
/** Act.fm current user name */
|
||||
public static final String PREF_NAME = IDENTIFIER + "_name"; //$NON-NLS-1$
|
||||
|
||||
/** Act.fm current user picture */
|
||||
public static final String PREF_PICTURE = IDENTIFIER + "_picture"; //$NON-NLS-1$
|
||||
|
||||
/** Act.fm current user email */
|
||||
public static final String PREF_EMAIL = IDENTIFIER + "_email"; //$NON-NLS-1$
|
||||
|
||||
/** Act.fm last sync server time */
|
||||
public static final String PREF_SERVER_TIME = IDENTIFIER + "_time"; //$NON-NLS-1$
|
||||
|
||||
private static JSONObject user = null;
|
||||
|
||||
/**
|
||||
* Return JSON object user, either yourself or the user of the model
|
||||
* @param update
|
||||
* @return
|
||||
*/
|
||||
public static JSONObject userFromModel(RemoteModel model) {
|
||||
if(model.getValue(RemoteModel.USER_ID_PROPERTY) == 0)
|
||||
return thisUser();
|
||||
else {
|
||||
try {
|
||||
return new JSONObject(model.getValue(RemoteModel.USER_JSON_PROPERTY));
|
||||
} catch (JSONException e) {
|
||||
return new JSONObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
private synchronized static JSONObject thisUser() {
|
||||
if(user == null) {
|
||||
user = new JSONObject();
|
||||
try {
|
||||
user.put("name", Preferences.getStringValue(PREF_NAME));
|
||||
user.put("email", Preferences.getStringValue(PREF_EMAIL));
|
||||
user.put("picture", Preferences.getStringValue(PREF_PICTURE));
|
||||
user.put("id", Preferences.getLong(PREF_USER_ID, 0));
|
||||
System.err.println(user);
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.todoroo.astrid.sharing;
|
||||
package com.todoroo.astrid.actfm.sync;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -0,0 +1,316 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.actfm.sync;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.timsu.astrid.C2DMReceiver;
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.andlib.data.TodorooCursor;
|
||||
import com.todoroo.andlib.service.Autowired;
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
import com.todoroo.andlib.utility.DateUtilities;
|
||||
import com.todoroo.andlib.utility.Preferences;
|
||||
import com.todoroo.astrid.actfm.ActFmBackgroundService;
|
||||
import com.todoroo.astrid.actfm.ActFmLoginActivity;
|
||||
import com.todoroo.astrid.actfm.ActFmPreferences;
|
||||
import com.todoroo.astrid.actfm.sync.ActFmSyncService.JsonHelper;
|
||||
import com.todoroo.astrid.api.AstridApiConstants;
|
||||
import com.todoroo.astrid.data.Metadata;
|
||||
import com.todoroo.astrid.data.Task;
|
||||
import com.todoroo.astrid.notes.NoteMetadata;
|
||||
import com.todoroo.astrid.service.AstridDependencyInjector;
|
||||
import com.todoroo.astrid.service.StatisticsService;
|
||||
import com.todoroo.astrid.sync.SyncProvider;
|
||||
import com.todoroo.astrid.sync.SyncProviderUtilities;
|
||||
import com.todoroo.astrid.utility.Constants;
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
public class ActFmSyncProvider extends SyncProvider<ActFmTaskContainer> {
|
||||
|
||||
private ActFmInvoker invoker = null;
|
||||
|
||||
@Autowired ActFmDataService actFmDataService;
|
||||
@Autowired ActFmSyncService actFmSyncService;
|
||||
@Autowired ActFmPreferenceService actFmPreferenceService;
|
||||
|
||||
static {
|
||||
AstridDependencyInjector.initialize();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// ------------------------------------------------------ utility methods
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected SyncProviderUtilities getUtilities() {
|
||||
return actFmPreferenceService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign out of service, deleting all synchronization metadata
|
||||
*/
|
||||
public void signOut() {
|
||||
actFmPreferenceService.setToken(null);
|
||||
actFmPreferenceService.clearLastSyncDate();
|
||||
C2DMReceiver.unregister();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// ------------------------------------------------------ initiating sync
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* initiate sync in background
|
||||
*/
|
||||
@Override
|
||||
protected void initiateBackground() {
|
||||
try {
|
||||
C2DMReceiver.register();
|
||||
String authToken = actFmPreferenceService.getToken();
|
||||
invoker = new ActFmInvoker(authToken);
|
||||
|
||||
// check if we have a token & it works
|
||||
if(authToken != null) {
|
||||
performSync();
|
||||
}
|
||||
} catch (IllegalStateException e) {
|
||||
// occurs when application was closed
|
||||
} catch (Exception e) {
|
||||
handleException("actfm-authenticate", e, true);
|
||||
} finally {
|
||||
actFmPreferenceService.stopOngoing();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If user isn't already signed in, show sign in dialog. Else perform sync.
|
||||
*/
|
||||
@Override
|
||||
protected void initiateManual(Activity activity) {
|
||||
String authToken = actFmPreferenceService.getToken();
|
||||
actFmPreferenceService.stopOngoing();
|
||||
|
||||
// check if we have a token & it works
|
||||
if(authToken == null) {
|
||||
// display login-activity
|
||||
Intent intent = new Intent(activity, ActFmLoginActivity.class);
|
||||
activity.startActivityForResult(intent, 0);
|
||||
} else {
|
||||
activity.startService(new Intent(null, null,
|
||||
activity, ActFmBackgroundService.class));
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// ----------------------------------------------------- synchronization!
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
protected void performSync() {
|
||||
actFmPreferenceService.recordSyncStart();
|
||||
|
||||
try {
|
||||
int serverTime = Preferences.getInt(ActFmPreferenceService.PREF_SERVER_TIME, 0);
|
||||
ArrayList<ActFmTaskContainer> remoteTasks = new ArrayList<ActFmTaskContainer>();
|
||||
|
||||
serverTime = (int)(fetchRemoteTasks(serverTime, remoteTasks) - DateUtilities.now()/1000L);
|
||||
|
||||
fetchRemoteTagData(serverTime);
|
||||
|
||||
SyncData<ActFmTaskContainer> syncData = populateSyncData(remoteTasks);
|
||||
|
||||
try {
|
||||
synchronizeTasks(syncData);
|
||||
} finally {
|
||||
syncData.localCreated.close();
|
||||
syncData.localUpdated.close();
|
||||
}
|
||||
|
||||
serverTime += DateUtilities.now()/1000L;
|
||||
Preferences.setInt(ActFmPreferenceService.PREF_SERVER_TIME, serverTime);
|
||||
actFmPreferenceService.recordSuccessfulSync();
|
||||
|
||||
StatisticsService.reportEvent("actfm-sync-finished"); //$NON-NLS-1$
|
||||
|
||||
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_EVENT_REFRESH);
|
||||
ContextManager.getContext().sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
|
||||
|
||||
} catch (IllegalStateException e) {
|
||||
// occurs when application was closed
|
||||
} catch (Exception e) {
|
||||
handleException("actfm-sync", e, true); //$NON-NLS-1$
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read remote tag data and merge with local
|
||||
* @param serverTime last sync time
|
||||
*/
|
||||
private void fetchRemoteTagData(int serverTime) throws ActFmServiceException, IOException, JSONException {
|
||||
actFmSyncService.fetchTags();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read remote task data into remote task array
|
||||
* @param serverTime last sync time
|
||||
*/
|
||||
private int fetchRemoteTasks(int serverTime,
|
||||
ArrayList<ActFmTaskContainer> remoteTasks) throws IOException,
|
||||
ActFmServiceException, JSONException {
|
||||
JSONObject result;
|
||||
if(serverTime == 0)
|
||||
result = invoker.invoke("task_list", "active", 1);
|
||||
else
|
||||
result = invoker.invoke("task_list", "modified_after", serverTime);
|
||||
|
||||
JSONArray taskList = result.getJSONArray("list");
|
||||
for(int i = 0; i < taskList.length(); i++) {
|
||||
ActFmTaskContainer remote = parseRemoteTask(taskList.getJSONObject(i));
|
||||
|
||||
// update reminder flags for incoming remote tasks to prevent annoying
|
||||
if(remote.task.hasDueDate() && remote.task.getValue(Task.DUE_DATE) < DateUtilities.now())
|
||||
remote.task.setFlag(Task.REMINDER_FLAGS, Task.NOTIFY_AFTER_DEADLINE, false);
|
||||
|
||||
actFmDataService.findLocalMatch(remote);
|
||||
|
||||
remoteTasks.add(remote);
|
||||
}
|
||||
return result.optInt("time", 0);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// ------------------------------------------------------------ sync data
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Populate SyncData data structure
|
||||
* @throws JSONException
|
||||
*/
|
||||
private SyncData<ActFmTaskContainer> populateSyncData(ArrayList<ActFmTaskContainer> remoteTasks) throws JSONException {
|
||||
// fetch locally created tasks
|
||||
TodorooCursor<Task> localCreated = actFmDataService.getLocallyCreated(Task.PROPERTIES);
|
||||
|
||||
// fetch locally updated tasks
|
||||
TodorooCursor<Task> localUpdated = actFmDataService.getLocallyUpdated(Task.PROPERTIES);
|
||||
|
||||
return new SyncData<ActFmTaskContainer>(remoteTasks, localCreated, localUpdated);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// ------------------------------------------------- create / push / pull
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected ActFmTaskContainer create(ActFmTaskContainer local) throws IOException {
|
||||
return push(local, null);
|
||||
}
|
||||
|
||||
/** Create a task container for the given remote task
|
||||
* @throws JSONException */
|
||||
private ActFmTaskContainer parseRemoteTask(JSONObject remoteTask) throws JSONException {
|
||||
Task task = new Task();
|
||||
ArrayList<Metadata> metadata = new ArrayList<Metadata>();
|
||||
|
||||
JsonHelper.taskFromJson(remoteTask, task, metadata);
|
||||
ActFmTaskContainer container = new ActFmTaskContainer(task, metadata, remoteTask);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ActFmTaskContainer pull(ActFmTaskContainer task) throws IOException {
|
||||
if(task.task.getValue(Task.REMOTE_ID) == 0)
|
||||
throw new ActFmServiceException("Tried to read an invalid task"); //$NON-NLS-1$
|
||||
|
||||
JSONObject remote = invoker.invoke("task_show", "id", task.task.getValue(Task.REMOTE_ID));
|
||||
try {
|
||||
return parseRemoteTask(remote);
|
||||
} catch (JSONException e) {
|
||||
throw new ActFmServiceException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send changes for the given Task across the wire.
|
||||
*/
|
||||
@Override
|
||||
protected ActFmTaskContainer push(ActFmTaskContainer local, ActFmTaskContainer remote) throws IOException {
|
||||
long id = local.task.getValue(Task.REMOTE_ID);
|
||||
|
||||
actFmSyncService.pushTaskOnSave(local.task, local.task.getDatabaseValues());
|
||||
|
||||
// push unsaved comments
|
||||
for(Metadata item : local.metadata) {
|
||||
if(NoteMetadata.METADATA_KEY.equals(item.getValue(Metadata.KEY)))
|
||||
if(TextUtils.isEmpty(item.getValue(NoteMetadata.EXT_ID))) {
|
||||
JSONObject comment = invoker.invoke("comment_add",
|
||||
"task_id", id,
|
||||
"message", item.getValue(NoteMetadata.BODY));
|
||||
item.setValue(NoteMetadata.EXT_ID, comment.optString("id"));
|
||||
}
|
||||
}
|
||||
|
||||
return local;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// --------------------------------------------------------- read / write
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected ActFmTaskContainer read(TodorooCursor<Task> cursor) throws IOException {
|
||||
return actFmDataService.readTaskAndMetadata(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void write(ActFmTaskContainer task) throws IOException {
|
||||
actFmDataService.saveTaskAndMetadata(task);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// --------------------------------------------------------- misc helpers
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected int matchTask(ArrayList<ActFmTaskContainer> tasks, ActFmTaskContainer target) {
|
||||
int length = tasks.size();
|
||||
for(int i = 0; i < length; i++) {
|
||||
ActFmTaskContainer task = tasks.get(i);
|
||||
if (task.task.getValue(Task.REMOTE_ID) == target.task.getValue(Task.REMOTE_ID))
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int updateNotification(Context context, Notification notification) {
|
||||
String notificationTitle = context.getString(R.string.actfm_notification_title);
|
||||
Intent intent = new Intent(context, ActFmPreferences.class);
|
||||
PendingIntent notificationIntent = PendingIntent.getActivity(context, 0,
|
||||
intent, 0);
|
||||
notification.setLatestEventInfo(context,
|
||||
notificationTitle, context.getString(R.string.SyP_progress),
|
||||
notificationIntent);
|
||||
return Constants.NOTIFICATION_SYNC;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void transferIdentifiers(ActFmTaskContainer source,
|
||||
ActFmTaskContainer destination) {
|
||||
destination.task.setValue(Task.REMOTE_ID, source.task.getValue(Task.REMOTE_ID));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,764 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.actfm.sync;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.apache.http.entity.mime.MultipartEntity;
|
||||
import org.apache.http.entity.mime.content.ByteArrayBody;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.andlib.data.AbstractModel;
|
||||
import com.todoroo.andlib.data.DatabaseDao;
|
||||
import com.todoroo.andlib.data.DatabaseDao.ModelUpdateListener;
|
||||
import com.todoroo.andlib.data.Property.LongProperty;
|
||||
import com.todoroo.andlib.data.Property.StringProperty;
|
||||
import com.todoroo.andlib.data.TodorooCursor;
|
||||
import com.todoroo.andlib.service.Autowired;
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
import com.todoroo.andlib.service.DependencyInjectionService;
|
||||
import com.todoroo.andlib.sql.Order;
|
||||
import com.todoroo.andlib.sql.Query;
|
||||
import com.todoroo.andlib.utility.AndroidUtilities;
|
||||
import com.todoroo.andlib.utility.DateUtilities;
|
||||
import com.todoroo.andlib.utility.Preferences;
|
||||
import com.todoroo.astrid.dao.MetadataDao;
|
||||
import com.todoroo.astrid.dao.TagDataDao;
|
||||
import com.todoroo.astrid.dao.TaskDao;
|
||||
import com.todoroo.astrid.dao.UpdateDao;
|
||||
import com.todoroo.astrid.data.Metadata;
|
||||
import com.todoroo.astrid.data.MetadataApiDao.MetadataCriteria;
|
||||
import com.todoroo.astrid.data.RemoteModel;
|
||||
import com.todoroo.astrid.data.TagData;
|
||||
import com.todoroo.astrid.data.Task;
|
||||
import com.todoroo.astrid.data.Update;
|
||||
import com.todoroo.astrid.service.MetadataService;
|
||||
import com.todoroo.astrid.service.TagDataService;
|
||||
import com.todoroo.astrid.service.TaskService;
|
||||
import com.todoroo.astrid.tags.TagService;
|
||||
import com.todoroo.astrid.utility.Flags;
|
||||
|
||||
/**
|
||||
* Service for synchronizing data on Astrid.com server with local.
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public final class ActFmSyncService {
|
||||
|
||||
// --- instance variables
|
||||
|
||||
@Autowired TagDataService tagDataService;
|
||||
@Autowired MetadataService metadataService;
|
||||
@Autowired TaskService taskService;
|
||||
@Autowired ActFmPreferenceService actFmPreferenceService;
|
||||
@Autowired ActFmInvoker actFmInvoker;
|
||||
@Autowired ActFmDataService actFmDataService;
|
||||
@Autowired TaskDao taskDao;
|
||||
@Autowired TagDataDao tagDataDao;
|
||||
@Autowired UpdateDao updateDao;
|
||||
@Autowired MetadataDao metadataDao;
|
||||
|
||||
private String token;
|
||||
|
||||
public ActFmSyncService() {
|
||||
DependencyInjectionService.getInstance().inject(this);
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
taskDao.addListener(new ModelUpdateListener<Task>() {
|
||||
@Override
|
||||
public void onModelUpdated(final Task model) {
|
||||
if(Flags.checkAndClear(Flags.SUPPRESS_SYNC))
|
||||
return;
|
||||
final ContentValues setValues = model.getSetValues();
|
||||
if(setValues == null || !checkForToken() || setValues.containsKey(RemoteModel.REMOTE_ID_PROPERTY_NAME))
|
||||
return;
|
||||
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// sleep so metadata associated with task is saved
|
||||
AndroidUtilities.sleepDeep(1000L);
|
||||
pushTaskOnSave(model, setValues);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
});
|
||||
|
||||
updateDao.addListener(new ModelUpdateListener<Update>() {
|
||||
@Override
|
||||
public void onModelUpdated(final Update model) {
|
||||
if(Flags.checkAndClear(Flags.SUPPRESS_SYNC))
|
||||
return;
|
||||
final ContentValues setValues = model.getSetValues();
|
||||
if(setValues == null || !checkForToken() || model.getValue(Update.REMOTE_ID) > 0)
|
||||
return;
|
||||
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
pushUpdateOnSave(model, setValues);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
});
|
||||
|
||||
tagDataDao.addListener(new ModelUpdateListener<TagData>() {
|
||||
@Override
|
||||
public void onModelUpdated(final TagData model) {
|
||||
if(Flags.checkAndClear(Flags.SUPPRESS_SYNC))
|
||||
return;
|
||||
final ContentValues setValues = model.getSetValues();
|
||||
if(setValues == null || !checkForToken() || setValues.containsKey(RemoteModel.REMOTE_ID_PROPERTY_NAME))
|
||||
return;
|
||||
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
pushTagDataOnSave(model, setValues);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// --- data push methods
|
||||
|
||||
/**
|
||||
* Synchronize with server when data changes
|
||||
*/
|
||||
public void pushUpdateOnSave(Update update, ContentValues values) {
|
||||
if(!values.containsKey(Update.MESSAGE.name))
|
||||
return;
|
||||
|
||||
ArrayList<Object> params = new ArrayList<Object>();
|
||||
params.add("message"); params.add(update.getValue(Update.MESSAGE));
|
||||
|
||||
if(update.getValue(Update.TAG) > 0) {
|
||||
TagData tagData = tagDataService.fetchById(update.getValue(Update.TAG), TagData.REMOTE_ID);
|
||||
if(tagData == null || tagData.getValue(TagData.REMOTE_ID) == 0)
|
||||
return;
|
||||
params.add("tag_id"); params.add(tagData.getValue(TagData.REMOTE_ID));
|
||||
}
|
||||
|
||||
if(update.getValue(Update.TASK) > 0) {
|
||||
Task task = taskService.fetchById(update.getValue(Update.TASK), Task.REMOTE_ID);
|
||||
if(task == null || task.getValue(Task.REMOTE_ID) == 0)
|
||||
return;
|
||||
params.add("task"); params.add(task.getValue(Task.REMOTE_ID));
|
||||
}
|
||||
if(!checkForToken())
|
||||
return;
|
||||
|
||||
try {
|
||||
params.add("token"); params.add(token);
|
||||
JSONObject result = actFmInvoker.invoke("comment_add", params.toArray(new Object[params.size()]));
|
||||
update.setValue(Update.REMOTE_ID, result.optLong("id"));
|
||||
updateDao.saveExisting(update);
|
||||
} catch (IOException e) {
|
||||
handleException("task-save", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronize with server when data changes
|
||||
*/
|
||||
public void pushTaskOnSave(Task task, ContentValues values) {
|
||||
long remoteId;
|
||||
if(task.containsValue(Task.REMOTE_ID))
|
||||
remoteId = task.getValue(Task.REMOTE_ID);
|
||||
else {
|
||||
Task taskForRemote = taskService.fetchById(task.getId(), Task.REMOTE_ID);
|
||||
if(taskForRemote == null)
|
||||
return;
|
||||
remoteId = taskForRemote.getValue(Task.REMOTE_ID);
|
||||
}
|
||||
boolean newlyCreated = remoteId == 0;
|
||||
|
||||
ArrayList<Object> params = new ArrayList<Object>();
|
||||
|
||||
if(values.containsKey(Task.TITLE.name)) {
|
||||
params.add("title"); params.add(task.getValue(Task.TITLE));
|
||||
}
|
||||
if(values.containsKey(Task.DUE_DATE.name)) {
|
||||
params.add("due"); params.add(task.getValue(Task.DUE_DATE) / 1000L);
|
||||
params.add("has_due_time"); params.add(task.hasDueTime() ? 1 : 0);
|
||||
}
|
||||
if(values.containsKey(Task.NOTES.name)) {
|
||||
params.add("notes"); params.add(task.getValue(Task.NOTES));
|
||||
}
|
||||
if(values.containsKey(Task.DELETION_DATE.name)) {
|
||||
params.add("deleted_at"); params.add(task.getValue(Task.DELETION_DATE) / 1000L);
|
||||
}
|
||||
if(values.containsKey(Task.COMPLETION_DATE.name)) {
|
||||
params.add("completed"); params.add(task.getValue(Task.COMPLETION_DATE) / 1000L);
|
||||
}
|
||||
if(values.containsKey(Task.IMPORTANCE.name)) {
|
||||
params.add("importance"); params.add(task.getValue(Task.IMPORTANCE));
|
||||
}
|
||||
if(values.containsKey(Task.RECURRENCE.name)) {
|
||||
params.add("repeat"); params.add(task.getValue(Task.RECURRENCE));
|
||||
}
|
||||
if(values.containsKey(Task.USER_ID.name) && task.getValue(Task.USER_ID) >= 0) {
|
||||
params.add("user_id");
|
||||
if(task.getValue(Task.USER_ID) == 0)
|
||||
params.add(ActFmPreferenceService.userId());
|
||||
else
|
||||
params.add(task.getValue(Task.USER_ID));
|
||||
}
|
||||
if(Flags.checkAndClear(Flags.TAGS_CHANGED) || newlyCreated) {
|
||||
TodorooCursor<Metadata> cursor = TagService.getInstance().getTags(task.getId());
|
||||
try {
|
||||
if(cursor.getCount() == 0) {
|
||||
params.add("tags");
|
||||
params.add("");
|
||||
} else {
|
||||
Metadata metadata = new Metadata();
|
||||
for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
|
||||
metadata.readFromCursor(cursor);
|
||||
if(metadata.containsNonNullValue(TagService.REMOTE_ID) &&
|
||||
metadata.getValue(TagService.REMOTE_ID) > 0) {
|
||||
params.add("tag_ids[]");
|
||||
params.add(metadata.getValue(TagService.REMOTE_ID));
|
||||
} else {
|
||||
params.add("tags[]");
|
||||
params.add(metadata.getValue(TagService.TAG));
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
if(params.size() == 0 || !checkForToken())
|
||||
return;
|
||||
|
||||
System.err.println("PUSHN ON SAVE: " + task.getMergedValues());
|
||||
System.err.println("SETVALUES: " + values);
|
||||
|
||||
if(!newlyCreated) {
|
||||
params.add("id"); params.add(remoteId);
|
||||
} else if(!params.contains(Task.TITLE.name))
|
||||
return;
|
||||
|
||||
try {
|
||||
params.add("token"); params.add(token);
|
||||
JSONObject result = actFmInvoker.invoke("task_save", params.toArray(new Object[params.size()]));
|
||||
ArrayList<Metadata> metadata = new ArrayList<Metadata>();
|
||||
JsonHelper.taskFromJson(result, task, metadata);
|
||||
task.setValue(Task.MODIFICATION_DATE, DateUtilities.now());
|
||||
task.setValue(Task.LAST_SYNC, DateUtilities.now());
|
||||
Flags.set(Flags.SUPPRESS_SYNC);
|
||||
taskDao.saveExisting(task);
|
||||
} catch (JSONException e) {
|
||||
handleException("task-save-json", e);
|
||||
} catch (IOException e) {
|
||||
handleException("task-save-io", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronize complete task with server
|
||||
* @param task
|
||||
*/
|
||||
public void pushTask(long taskId) {
|
||||
Task task = taskService.fetchById(taskId, Task.PROPERTIES);
|
||||
pushTaskOnSave(task, task.getMergedValues());
|
||||
}
|
||||
|
||||
/**
|
||||
* Send tagData changes to server
|
||||
* @param setValues
|
||||
*/
|
||||
public void pushTagDataOnSave(TagData tagData, ContentValues values) {
|
||||
long remoteId;
|
||||
if(tagData.containsValue(TagData.REMOTE_ID))
|
||||
remoteId = tagData.getValue(TagData.REMOTE_ID);
|
||||
else {
|
||||
TagData forRemote = tagDataService.fetchById(tagData.getId(), TagData.REMOTE_ID);
|
||||
if(forRemote == null)
|
||||
return;
|
||||
remoteId = forRemote.getValue(TagData.REMOTE_ID);
|
||||
}
|
||||
boolean newlyCreated = remoteId == 0;
|
||||
|
||||
ArrayList<Object> params = new ArrayList<Object>();
|
||||
|
||||
if(values.containsKey(TagData.NAME.name)) {
|
||||
params.add("name"); params.add(tagData.getValue(TagData.NAME));
|
||||
}
|
||||
|
||||
if(values.containsKey(TagData.MEMBERS.name)) {
|
||||
params.add("members");
|
||||
try {
|
||||
JSONArray members = new JSONArray(tagData.getValue(TagData.MEMBERS));
|
||||
if(members.length() == 0)
|
||||
params.add("");
|
||||
else {
|
||||
ArrayList<Object> array = new ArrayList<Object>(members.length());
|
||||
for(int i = 0; i < members.length(); i++) {
|
||||
JSONObject person = members.getJSONObject(i);
|
||||
if(person.has("id"))
|
||||
array.add(person.getLong("id"));
|
||||
else {
|
||||
if(person.has("name"))
|
||||
array.add(person.getString("name") + " <" +
|
||||
person.getString("email") + ">");
|
||||
else
|
||||
array.add(person.getString("email"));
|
||||
}
|
||||
}
|
||||
params.add(array);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
if(params.size() == 0 || !checkForToken())
|
||||
return;
|
||||
|
||||
if(!newlyCreated) {
|
||||
params.add("id"); params.add(remoteId);
|
||||
}
|
||||
|
||||
boolean success;
|
||||
try {
|
||||
params.add("token"); params.add(token);
|
||||
JSONObject result = actFmInvoker.invoke("tag_save", params.toArray(new Object[params.size()]));
|
||||
if(newlyCreated) {
|
||||
tagData.setValue(TagData.REMOTE_ID, result.optLong("id"));
|
||||
Flags.set(Flags.SUPPRESS_SYNC);
|
||||
tagDataDao.saveExisting(tagData);
|
||||
}
|
||||
success = true;
|
||||
} catch (IOException e) {
|
||||
handleException("tag-save", e);
|
||||
success = false;
|
||||
}
|
||||
if(!Flags.checkAndClear(Flags.TOAST_ON_SAVE))
|
||||
return;
|
||||
|
||||
final boolean finalSuccess = success;
|
||||
Handler handler = new Handler(Looper.getMainLooper());
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if(finalSuccess)
|
||||
Toast.makeText(ContextManager.getContext(),
|
||||
R.string.actfm_toast_success, Toast.LENGTH_LONG).show();
|
||||
else
|
||||
Toast.makeText(ContextManager.getContext(),
|
||||
R.string.actfm_toast_error, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// --- data fetch methods
|
||||
|
||||
/**
|
||||
* Fetch tagData listing asynchronously
|
||||
*/
|
||||
public void fetchTagDataDashboard(boolean manual, final Runnable done) {
|
||||
invokeFetchList("goal", manual, new ListItemProcessor<TagData>() {
|
||||
@Override
|
||||
protected void mergeAndSave(JSONArray list, HashMap<Long,Long> locals) throws JSONException {
|
||||
TagData remote = new TagData();
|
||||
for(int i = 0; i < list.length(); i++) {
|
||||
JSONObject item = list.getJSONObject(i);
|
||||
readIds(locals, item, remote);
|
||||
JsonHelper.tagFromJson(item, remote);
|
||||
Flags.set(Flags.SUPPRESS_SYNC);
|
||||
tagDataService.save(remote);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HashMap<Long, Long> getLocalModels() {
|
||||
TodorooCursor<TagData> cursor = tagDataService.query(Query.select(TagData.ID,
|
||||
TagData.REMOTE_ID).where(TagData.REMOTE_ID.in(remoteIds)).orderBy(
|
||||
Order.asc(TagData.REMOTE_ID)));
|
||||
return cursorToMap(cursor, taskDao, TagData.REMOTE_ID, TagData.ID);
|
||||
}
|
||||
}, done, "goals");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get details for this tag
|
||||
* @param tagData
|
||||
* @throws IOException
|
||||
* @throws JSONException
|
||||
*/
|
||||
public void fetchTag(final TagData tagData) throws IOException, JSONException {
|
||||
JSONObject result;
|
||||
if(!checkForToken())
|
||||
return;
|
||||
|
||||
if(tagData.getValue(TagData.REMOTE_ID) == 0)
|
||||
result = actFmInvoker.invoke("tag_show", "name", tagData.getValue(TagData.NAME),
|
||||
"token", token);
|
||||
else
|
||||
result = actFmInvoker.invoke("tag_show", "id", tagData.getValue(TagData.REMOTE_ID),
|
||||
"token", token);
|
||||
|
||||
JsonHelper.tagFromJson(result, tagData);
|
||||
Flags.set(Flags.SUPPRESS_SYNC);
|
||||
tagDataService.save(tagData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all tags
|
||||
*/
|
||||
public void fetchTags() throws JSONException, IOException {
|
||||
if(!checkForToken())
|
||||
return;
|
||||
|
||||
JSONObject result = actFmInvoker.invoke("tag_list", "token", token);
|
||||
JSONArray tags = result.getJSONArray("list");
|
||||
for(int i = 0; i < tags.length(); i++) {
|
||||
JSONObject tagObject = tags.getJSONObject(i);
|
||||
actFmDataService.saveTagData(tagObject);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch tasks for the given tagData asynchronously
|
||||
* @param tagData
|
||||
* @param manual
|
||||
* @param done
|
||||
*/
|
||||
public void fetchTasksForTag(final TagData tagData, final boolean manual, Runnable done) {
|
||||
invokeFetchList("task", manual, new ListItemProcessor<Task>() {
|
||||
@Override
|
||||
protected void mergeAndSave(JSONArray list, HashMap<Long,Long> locals) throws JSONException {
|
||||
Task remote = new Task();
|
||||
|
||||
ArrayList<Metadata> metadata = new ArrayList<Metadata>();
|
||||
for(int i = 0; i < list.length(); i++) {
|
||||
|
||||
JSONObject item = list.getJSONObject(i);
|
||||
readIds(locals, item, remote);
|
||||
JsonHelper.taskFromJson(item, remote, metadata);
|
||||
|
||||
Flags.set(Flags.SUPPRESS_SYNC);
|
||||
taskService.save(remote);
|
||||
metadataService.synchronizeMetadata(remote.getId(), metadata, MetadataCriteria.withKey(TagService.KEY));
|
||||
}
|
||||
|
||||
if(manual) {
|
||||
for(Long localId : locals.values())
|
||||
taskDao.delete(localId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HashMap<Long, Long> getLocalModels() {
|
||||
TodorooCursor<Task> cursor = taskService.query(Query.select(Task.ID,
|
||||
Task.REMOTE_ID).where(Task.REMOTE_ID.in(remoteIds)).orderBy(
|
||||
Order.asc(Task.REMOTE_ID)));
|
||||
return cursorToMap(cursor, taskDao, Task.REMOTE_ID, Task.ID);
|
||||
}
|
||||
}, done, "tasks:" + tagData.getId(), "tag_id", tagData.getValue(TagData.REMOTE_ID));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch tasks for the given tagData asynchronously
|
||||
* @param tagData
|
||||
* @param manual
|
||||
* @param done
|
||||
*/
|
||||
public void fetchUpdatesForTag(final TagData tagData, final boolean manual, Runnable done) {
|
||||
invokeFetchList("activity", manual, new ListItemProcessor<Update>() {
|
||||
@Override
|
||||
protected void mergeAndSave(JSONArray list, HashMap<Long,Long> locals) throws JSONException {
|
||||
Update remote = new Update();
|
||||
for(int i = 0; i < list.length(); i++) {
|
||||
JSONObject item = list.getJSONObject(i);
|
||||
readIds(locals, item, remote);
|
||||
JsonHelper.updateFromJson(item, tagData, remote);
|
||||
|
||||
Flags.set(Flags.SUPPRESS_SYNC);
|
||||
if(remote.getId() == AbstractModel.NO_ID)
|
||||
updateDao.createNew(remote);
|
||||
else
|
||||
updateDao.saveExisting(remote);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HashMap<Long, Long> getLocalModels() {
|
||||
TodorooCursor<Update> cursor = updateDao.query(Query.select(Update.ID,
|
||||
Update.REMOTE_ID).where(Update.REMOTE_ID.in(remoteIds)).orderBy(
|
||||
Order.asc(Update.REMOTE_ID)));
|
||||
return cursorToMap(cursor, updateDao, Update.REMOTE_ID, Update.ID);
|
||||
}
|
||||
}, done, "updates:" + tagData.getId(), "tag_id", tagData.getValue(TagData.REMOTE_ID));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update tag picture
|
||||
* @param path
|
||||
* @throws IOException
|
||||
* @throws ActFmServiceException
|
||||
*/
|
||||
public String setTagPicture(long tagId, Bitmap bitmap) throws ActFmServiceException, IOException {
|
||||
if(!checkForToken())
|
||||
return null;
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
if(bitmap.getWidth() > 512 || bitmap.getHeight() > 512) {
|
||||
float scale = Math.min(512f / bitmap.getWidth(), 512f / bitmap.getHeight());
|
||||
bitmap = Bitmap.createScaledBitmap(bitmap, (int)(scale * bitmap.getWidth()),
|
||||
(int)(scale * bitmap.getHeight()), false);
|
||||
}
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 50, baos);
|
||||
byte[] bytes = baos.toByteArray();
|
||||
MultipartEntity data = new MultipartEntity();
|
||||
data.addPart("picture", new ByteArrayBody(bytes, "image/jpg", "image.jpg"));
|
||||
JSONObject result = actFmInvoker.post("tag_set_picture", data, "id", tagId, "token", token);
|
||||
return result.optString("url");
|
||||
}
|
||||
|
||||
// --- generic invokation
|
||||
|
||||
/** invoke authenticated method against the server */
|
||||
public JSONObject invoke(String method, Object... getParameters) throws IOException,
|
||||
ActFmServiceException {
|
||||
if(!checkForToken())
|
||||
throw new ActFmServiceException("not logged in");
|
||||
Object[] parameters = new Object[getParameters.length + 2];
|
||||
parameters[0] = "token";
|
||||
parameters[1] = token;
|
||||
for(int i = 0; i < getParameters.length; i++)
|
||||
parameters[i+2] = getParameters[i];
|
||||
return actFmInvoker.invoke(method, parameters);
|
||||
}
|
||||
|
||||
// --- helpers
|
||||
|
||||
private abstract class ListItemProcessor<TYPE extends AbstractModel> {
|
||||
protected Long[] remoteIds = null;
|
||||
|
||||
abstract protected HashMap<Long, Long> getLocalModels();
|
||||
|
||||
abstract protected void mergeAndSave(JSONArray list,
|
||||
HashMap<Long,Long> locals) throws JSONException;
|
||||
|
||||
public void process(JSONArray list) throws JSONException {
|
||||
readRemoteIds(list);
|
||||
HashMap<Long, Long> locals = getLocalModels();
|
||||
mergeAndSave(list, locals);
|
||||
}
|
||||
|
||||
|
||||
protected void readRemoteIds(JSONArray list) throws JSONException {
|
||||
remoteIds = new Long[list.length()];
|
||||
for(int i = 0; i < list.length(); i++)
|
||||
remoteIds[i] = list.getJSONObject(i).getLong("id");
|
||||
}
|
||||
|
||||
protected void readIds(HashMap<Long, Long> locals, JSONObject json, RemoteModel model) throws JSONException {
|
||||
long remoteId = json.getLong("id");
|
||||
model.setValue(RemoteModel.REMOTE_ID_PROPERTY, remoteId);
|
||||
if(locals.containsKey(remoteId)) {
|
||||
model.setId(locals.remove(remoteId));
|
||||
} else {
|
||||
model.clearValue(AbstractModel.ID_PROPERTY);
|
||||
}
|
||||
}
|
||||
|
||||
protected HashMap<Long, Long> cursorToMap(TodorooCursor<TYPE> cursor, DatabaseDao<?> dao,
|
||||
LongProperty remoteIdProperty, LongProperty localIdProperty) {
|
||||
try {
|
||||
HashMap<Long, Long> map = new HashMap<Long, Long>(cursor.getCount());
|
||||
for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
|
||||
long remoteId = cursor.get(remoteIdProperty);
|
||||
long localId = cursor.get(localIdProperty);
|
||||
|
||||
if(map.containsKey(remoteId))
|
||||
dao.delete(map.get(remoteId));
|
||||
map.put(remoteId, localId);
|
||||
}
|
||||
return map;
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Call sync method */
|
||||
private void invokeFetchList(final String model, final boolean manual,
|
||||
final ListItemProcessor<?> processor, final Runnable done, final String lastSyncKey,
|
||||
Object... params) {
|
||||
if(!checkForToken())
|
||||
return;
|
||||
|
||||
long serverFetchTime = manual ? 0 : Preferences.getLong("actfm_time_" + lastSyncKey, 0);
|
||||
final Object[] getParams = AndroidUtilities.concat(new Object[params.length + 4], params, "token", token,
|
||||
"modified_after", serverFetchTime);
|
||||
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
JSONObject result = null;
|
||||
try {
|
||||
result = actFmInvoker.invoke(model + "_list", getParams);
|
||||
JSONArray list = result.getJSONArray("list");
|
||||
processor.process(list);
|
||||
Preferences.setLong("actfm_time_" + lastSyncKey, result.optLong("time", 0));
|
||||
Preferences.setLong("actfm_last_" + lastSyncKey, DateUtilities.now());
|
||||
|
||||
if(done != null)
|
||||
done.run();
|
||||
} catch (IOException e) {
|
||||
handleException("io-exception-list-" + model, e);
|
||||
} catch (JSONException e) {
|
||||
handleException("json: " + result.toString(), e);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
protected void handleException(String message, Exception exception) {
|
||||
Log.w("actfm-sync", message, exception);
|
||||
}
|
||||
|
||||
private boolean checkForToken() {
|
||||
if(!actFmPreferenceService.isLoggedIn())
|
||||
return false;
|
||||
token = actFmPreferenceService.getToken();
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- json reader helper
|
||||
|
||||
/**
|
||||
* Read data models from JSON
|
||||
*/
|
||||
public static class JsonHelper {
|
||||
|
||||
protected static long readDate(JSONObject item, String key) {
|
||||
return item.optLong(key, 0) * 1000L;
|
||||
}
|
||||
|
||||
public static void updateFromJson(JSONObject json, TagData tagData,
|
||||
Update model) throws JSONException {
|
||||
model.setValue(Update.REMOTE_ID, json.getLong("id"));
|
||||
readUser(json.getJSONObject("user"), model, Update.USER_ID, Update.USER);
|
||||
model.setValue(Update.ACTION, json.getString("action"));
|
||||
model.setValue(Update.ACTION_CODE, json.getString("action_code"));
|
||||
model.setValue(Update.TARGET_NAME, json.getString("target_name"));
|
||||
if(json.isNull("message"))
|
||||
model.setValue(Update.MESSAGE, "");
|
||||
else
|
||||
model.setValue(Update.MESSAGE, json.getString("message"));
|
||||
model.setValue(Update.PICTURE, json.getString("picture"));
|
||||
model.setValue(Update.CREATION_DATE, readDate(json, "created_at"));
|
||||
model.setValue(Update.TAG, tagData.getId());
|
||||
}
|
||||
|
||||
public static void readUser(JSONObject user, AbstractModel model, LongProperty idProperty,
|
||||
StringProperty userProperty) throws JSONException {
|
||||
long id = user.getLong("id");
|
||||
if(id == ActFmPreferenceService.userId()) {
|
||||
model.setValue(idProperty, 0L);
|
||||
if(userProperty != null)
|
||||
model.setValue(userProperty, "");
|
||||
} else {
|
||||
model.setValue(idProperty, id);
|
||||
if(userProperty != null)
|
||||
model.setValue(userProperty, user.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read tagData from JSON
|
||||
* @param model
|
||||
* @param json
|
||||
* @throws JSONException
|
||||
*/
|
||||
public static void tagFromJson(JSONObject json, TagData model) throws JSONException {
|
||||
model.clearValue(TagData.REMOTE_ID);
|
||||
model.setValue(TagData.REMOTE_ID, json.getLong("id"));
|
||||
model.setValue(TagData.NAME, json.getString("name"));
|
||||
readUser(json.getJSONObject("user"), model, TagData.USER_ID, TagData.USER);
|
||||
|
||||
if(json.has("picture"))
|
||||
model.setValue(TagData.PICTURE, json.optString("picture", ""));
|
||||
if(json.has("thumb"))
|
||||
model.setValue(TagData.THUMB, json.optString("thumb", ""));
|
||||
|
||||
if(json.has("is_silent"))
|
||||
model.setFlag(TagData.FLAGS, TagData.FLAG_SILENT,json.getBoolean("is_silent"));
|
||||
|
||||
if(json.has("emergent"))
|
||||
model.setFlag(TagData.FLAGS, TagData.FLAG_EMERGENT,json.getBoolean("emergent"));
|
||||
|
||||
if(json.has("members")) {
|
||||
JSONArray members = json.getJSONArray("members");
|
||||
model.setValue(TagData.MEMBERS, members.toString());
|
||||
model.setValue(TagData.MEMBER_COUNT, members.length());
|
||||
}
|
||||
|
||||
if(json.has("tasks"))
|
||||
model.setValue(TagData.TASK_COUNT, json.getInt("tasks"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Read task from json
|
||||
* @param json
|
||||
* @param model
|
||||
* @param metadata
|
||||
* @throws JSONException
|
||||
*/
|
||||
public static void taskFromJson(JSONObject json, Task model, ArrayList<Metadata> metadata) throws JSONException {
|
||||
metadata.clear();
|
||||
model.clearValue(Task.REMOTE_ID);
|
||||
model.setValue(Task.REMOTE_ID, json.getLong("id"));
|
||||
model.setValue(Task.FLAGS, 0);
|
||||
readUser(json.getJSONObject("user"), model, Task.USER_ID, Task.USER);
|
||||
readUser(json.getJSONObject("creator"), model, Task.CREATOR_ID, null);
|
||||
model.setValue(Task.COMMENT_COUNT, json.getInt("comment_count"));
|
||||
model.setValue(Task.TITLE, json.getString("title"));
|
||||
model.setValue(Task.IMPORTANCE, json.getInt("importance"));
|
||||
model.setValue(Task.DUE_DATE,
|
||||
model.createDueDate(Task.URGENCY_SPECIFIC_DAY, readDate(json, "due")));
|
||||
model.setValue(Task.COMPLETION_DATE, readDate(json, "completed_at"));
|
||||
model.setValue(Task.CREATION_DATE, readDate(json, "created_at"));
|
||||
model.setValue(Task.DELETION_DATE, readDate(json, "deleted_at"));
|
||||
model.setValue(Task.RECURRENCE, json.optString("repeat", ""));
|
||||
model.setValue(Task.NOTES, json.optString("notes", ""));
|
||||
model.setValue(Task.DETAILS_DATE, 0L);
|
||||
|
||||
JSONArray tags = json.getJSONArray("tags");
|
||||
for(int i = 0; i < tags.length(); i++) {
|
||||
JSONObject tag = tags.getJSONObject(i);
|
||||
String name = tag.getString("name");
|
||||
Metadata tagMetadata = new Metadata();
|
||||
tagMetadata.setValue(Metadata.KEY, TagService.KEY);
|
||||
tagMetadata.setValue(TagService.TAG, name);
|
||||
tagMetadata.setValue(TagService.REMOTE_ID, tag.getLong("id"));
|
||||
metadata.add(tagMetadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package com.todoroo.astrid.actfm.sync;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
import com.todoroo.andlib.utility.DateUtilities;
|
||||
import com.todoroo.astrid.data.Metadata;
|
||||
import com.todoroo.astrid.data.Task;
|
||||
import com.todoroo.astrid.notes.NoteMetadata;
|
||||
import com.todoroo.astrid.sync.SyncContainer;
|
||||
|
||||
/**
|
||||
* RTM Task Container
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class ActFmTaskContainer extends SyncContainer {
|
||||
|
||||
public ActFmTaskContainer(Task task, ArrayList<Metadata> metadata) {
|
||||
this.task = task;
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
public ActFmTaskContainer(Task task, ArrayList<Metadata> metadata, JSONObject remoteTask) {
|
||||
this(task, metadata);
|
||||
task.setValue(Task.REMOTE_ID, remoteTask.optLong("id"));
|
||||
}
|
||||
|
||||
/** create note metadata from comment json object */
|
||||
@SuppressWarnings("nls")
|
||||
public static Metadata newNoteMetadata(JSONObject comment) {
|
||||
Metadata metadata = new Metadata();
|
||||
metadata.setValue(Metadata.KEY, NoteMetadata.METADATA_KEY);
|
||||
metadata.setValue(NoteMetadata.EXT_ID, comment.optString("id"));
|
||||
metadata.setValue(NoteMetadata.EXT_PROVIDER,
|
||||
ActFmDataService.NOTE_PROVIDER);
|
||||
|
||||
Date creationDate = new Date(comment.optInt("date") * 1000L);
|
||||
metadata.setValue(Metadata.CREATION_DATE, creationDate.getTime());
|
||||
metadata.setValue(NoteMetadata.BODY, comment.optString("message"));
|
||||
|
||||
JSONObject owner = comment.optJSONObject("owner");
|
||||
metadata.setValue(NoteMetadata.THUMBNAIL, owner.optString("picture"));
|
||||
String title = String.format("%s on %s",
|
||||
owner.optString("name"),
|
||||
DateUtilities.getDateString(ContextManager.getContext(), creationDate));
|
||||
metadata.setValue(NoteMetadata.TITLE, title);
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
/**
|
||||
* See the file "LICENSE" for the full license governing this code.
|
||||
*/
|
||||
package com.todoroo.astrid.sharing;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
import com.todoroo.astrid.api.AstridApiConstants;
|
||||
import com.todoroo.astrid.api.TaskAction;
|
||||
import com.todoroo.astrid.api.TaskDecoration;
|
||||
|
||||
/**
|
||||
* Exposes {@link TaskDecoration} for timers
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class SharingActionExposer extends BroadcastReceiver {
|
||||
|
||||
static final String EXTRA_TASK = "task"; //$NON-NLS-1$
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
ContextManager.setContext(context);
|
||||
long taskId = intent.getLongExtra(AstridApiConstants.EXTRAS_TASK_ID, -1);
|
||||
if(taskId == -1)
|
||||
return;
|
||||
|
||||
if(AstridApiConstants.BROADCAST_REQUEST_ACTIONS.equals(intent.getAction())) {
|
||||
sendAction(context, taskId);
|
||||
} else {
|
||||
performAction(context, taskId);
|
||||
}
|
||||
}
|
||||
|
||||
private void performAction(Context context, long taskId) {
|
||||
Intent intent = new Intent(context, SharingLoginActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
private void sendAction(Context context, long taskId) {
|
||||
final String label = context.getString(R.string.sharing_action);
|
||||
final Drawable drawable = context.getResources().getDrawable(R.drawable.tango_share);
|
||||
|
||||
Bitmap icon = ((BitmapDrawable)drawable).getBitmap();
|
||||
Intent newIntent = new Intent(context, getClass());
|
||||
newIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId);
|
||||
TaskAction action = new TaskAction(label,
|
||||
PendingIntent.getBroadcast(context, (int)taskId, newIntent, 0), icon);
|
||||
|
||||
// transmit
|
||||
Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_SEND_ACTIONS);
|
||||
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, action);
|
||||
broadcastIntent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, taskId);
|
||||
context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ);
|
||||
}
|
||||
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
package com.todoroo.astrid.sharing;
|
||||
|
||||
import com.todoroo.andlib.data.Property.IntegerProperty;
|
||||
import com.todoroo.andlib.data.Property.StringProperty;
|
||||
import com.todoroo.astrid.data.Metadata;
|
||||
|
||||
/**
|
||||
* Metadata entry for a task alarm
|
||||
*
|
||||
* @author Tim Su <tim@todoroo.com>
|
||||
*
|
||||
*/
|
||||
public class SharingFields {
|
||||
|
||||
/** metadata key */
|
||||
public static final String METADATA_KEY = "sharing"; //$NON-NLS-1$
|
||||
|
||||
/** online url */
|
||||
public static final StringProperty URL = Metadata.VALUE1;
|
||||
|
||||
/** sharing privacy */
|
||||
public static final IntegerProperty PRIVACY = new IntegerProperty(Metadata.TABLE,
|
||||
Metadata.VALUE2.name);
|
||||
|
||||
// --- constants
|
||||
|
||||
/** this task is shared publicly */
|
||||
public static final int PRIVACY_PUBLIC = 2;
|
||||
|
||||
/** this task is shared with a limited group */
|
||||
public static final int PRIVACY_LIMITED = 2;
|
||||
|
||||
/** this task is private */
|
||||
public static final int PRIVACY_PRIVATE = 1;
|
||||
|
||||
/** this alarm repeats itself until turned off */
|
||||
public static final int TYPE_REPEATING = 2;
|
||||
|
||||
}
|
@ -1,124 +0,0 @@
|
||||
/*
|
||||
* ASTRID: Android's Simple Task Recording Dashboard
|
||||
*
|
||||
* Copyright (c) 2009 Tim Su
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
package com.todoroo.astrid.sharing;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.facebook.android.AuthListener;
|
||||
import com.facebook.android.Facebook;
|
||||
import com.facebook.android.LoginButton;
|
||||
import com.timsu.astrid.R;
|
||||
import com.todoroo.andlib.service.Autowired;
|
||||
import com.todoroo.andlib.service.ContextManager;
|
||||
import com.todoroo.andlib.service.DependencyInjectionService;
|
||||
import com.todoroo.andlib.utility.DialogUtilities;
|
||||
import com.todoroo.astrid.data.Task;
|
||||
import com.todoroo.astrid.service.AstridDependencyInjector;
|
||||
import com.todoroo.astrid.service.TaskService;
|
||||
|
||||
/**
|
||||
* This activity allows users to sign in or log in to Producteev
|
||||
*
|
||||
* @author arne.jans
|
||||
*
|
||||
*/
|
||||
public class SharingLoginActivity extends Activity implements AuthListener {
|
||||
|
||||
public static final String APP_ID = "169904866369148"; //$NON-NLS-1$
|
||||
|
||||
@Autowired TaskService taskService;
|
||||
|
||||
private Facebook facebook;
|
||||
private TextView errors;
|
||||
|
||||
// --- ui initialization
|
||||
|
||||
static {
|
||||
AstridDependencyInjector.initialize();
|
||||
}
|
||||
|
||||
public String EXTRA_TASK_ID = "task"; //$NON-NLS-1$
|
||||
|
||||
public SharingLoginActivity() {
|
||||
super();
|
||||
DependencyInjectionService.getInstance().inject(this);
|
||||
}
|
||||
|
||||
@SuppressWarnings("nls")
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
ContextManager.setContext(this);
|
||||
|
||||
setContentView(R.layout.sharing_login_activity);
|
||||
setTitle(R.string.sharing_SLA_title);
|
||||
|
||||
long taskId = getIntent().getLongExtra(EXTRA_TASK_ID, 4L);
|
||||
Task task = taskService.fetchById(taskId, Task.TITLE);
|
||||
|
||||
TextView taskInfo = (TextView) findViewById(R.id.taskInfo);
|
||||
taskInfo.setText(taskInfo.getText() + "\n\n" + task.getValue(Task.TITLE));
|
||||
|
||||
facebook = new Facebook(APP_ID);
|
||||
|
||||
errors = (TextView) findViewById(R.id.error);
|
||||
LoginButton loginButton = (LoginButton) findViewById(R.id.fb_login);
|
||||
loginButton.init(this, facebook, this, new String[] {
|
||||
"email",
|
||||
"offline_access",
|
||||
"publish_stream"
|
||||
});
|
||||
}
|
||||
|
||||
// --- facebook handler
|
||||
|
||||
public void onFBAuthSucceed() {
|
||||
System.err.println("GOTCHA SUCCESS! " + facebook.getAccessToken());
|
||||
errors.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
public void onFBAuthFail(String error) {
|
||||
System.err.println("GOTCHA ERROR: " + error);
|
||||
DialogUtilities.okDialog(this, getString(R.string.sharing_SLA_title),
|
||||
android.R.drawable.ic_dialog_alert, error, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFBAuthCancel() {
|
||||
System.err.println("GOTCHA CANCEL");
|
||||
// do nothing
|
||||
}
|
||||
|
||||
// --- my astrid handler
|
||||
|
||||
/**
|
||||
* Create user account via FB
|
||||
*/
|
||||
public void createUserAccountFB() {
|
||||
String accessToken = facebook.getAccessToken();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
After Width: | Height: | Size: 1.8 KiB |
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2008 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_pressed="false" android:state_focused="false" android:drawable="@drawable/sharing_button_normal" />
|
||||
<item android:state_pressed="true" android:state_focused="false" android:drawable="@drawable/sharing_button_pressed" />
|
||||
|
||||
<item android:state_pressed="false" android:state_focused="true" android:drawable="@drawable/sharing_button_focused" />
|
||||
<item android:state_pressed="true" android:state_focused="true" android:drawable="@drawable/sharing_button_pressed" />
|
||||
|
||||
</selector>
|
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.6 KiB |
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<gradient
|
||||
android:type="radial"
|
||||
android:startColor="#0663be"
|
||||
android:endColor="#003471"
|
||||
android:gradientRadius="300"
|
||||
android:centerX="0.5"
|
||||
android:centerY="0.5" />
|
||||
</shape>
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 753 B |
After Width: | Height: | Size: 783 B |
After Width: | Height: | Size: 1.9 KiB |
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- See the file "LICENSE" for the full license governing this code. -->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:drawable/list_selector_background"
|
||||
android:padding="4dip"
|
||||
android:paddingRight="6dip"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<!-- picture thumbnail -->
|
||||
<ImageView android:id="@android:id/icon"
|
||||
android:layout_width="40dip"
|
||||
android:layout_height="40dip"
|
||||
android:gravity="center"
|
||||
android:layout_marginRight="5dip"
|
||||
android:scaleType="fitCenter" />
|
||||
|
||||
<!-- person or tag name -->
|
||||
<TextView android:id="@android:id/text1"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@android:attr/dropDownItemStyle"
|
||||
android:textAppearance="@android:attr/textAppearanceLargeInverse"
|
||||
android:textSize="20sp"/>
|
||||
|
||||
</LinearLayout>
|
@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2008 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.todoroo.astrid.ui.ContactsAutoComplete
|
||||
android:id="@+id/text1"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:hint="@string/actfm_person_hint" />
|
||||
|
||||
<ImageButton android:id="@+id/button1"
|
||||
style="?android:attr/buttonStyleInset"
|
||||
android:src="@android:drawable/ic_delete"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_marginTop="2dip"
|
||||
android:layout_marginRight="2dip"
|
||||
android:layout_marginBottom="2dip"
|
||||
android:gravity="center_vertical"
|
||||
/>
|
||||
</LinearLayout>
|
@ -0,0 +1,187 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:astrid="http://schemas.android.com/apk/res/com.timsu.astrid"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/background_gradient">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_weight="100">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="5dip"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
style="@style/TextAppearance.TAd_ItemTitle"
|
||||
android:paddingBottom="15dip"
|
||||
android:paddingTop="10dip"
|
||||
android:textSize="22sp" />
|
||||
|
||||
<View
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="1dip"
|
||||
android:background="@android:drawable/divider_horizontal_dark" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="15dip"
|
||||
android:paddingBottom="5dip"
|
||||
android:text="@string/actfm_EPA_assign_label" />
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/assigned_spinner"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="45dip"
|
||||
android:layout_weight="1"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:hint="@string/actfm_person_hint" />
|
||||
|
||||
<com.todoroo.astrid.ui.ContactsAutoComplete
|
||||
android:id="@+id/assigned_custom"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:visibility="gone"
|
||||
android:hint="@string/actfm_person_hint" />
|
||||
|
||||
<ImageButton android:id="@+id/assigned_clear"
|
||||
style="?android:attr/buttonStyleInset"
|
||||
android:src="@android:drawable/ic_delete"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_marginTop="2dip"
|
||||
android:layout_marginRight="2dip"
|
||||
android:layout_marginBottom="2dip"
|
||||
android:visibility="gone"
|
||||
android:gravity="center_vertical" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="5dip"
|
||||
android:text="@string/actfm_EPA_share_with" />
|
||||
|
||||
<com.todoroo.astrid.ui.PeopleContainer
|
||||
android:id="@+id/share_container"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
astrid:completeTags="true" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/share_additional"
|
||||
android:orientation="vertical"
|
||||
android:padding="5dip"
|
||||
android:visibility="gone"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<View
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="1dip"
|
||||
android:background="@android:drawable/divider_horizontal_dark" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="5dip"
|
||||
android:paddingBottom="5dip"
|
||||
android:text="@string/actfm_EPA_message_text" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/message"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:scrollbars="vertical"
|
||||
android:text="@string/actfm_EPA_message_body"
|
||||
android:autoText="true"
|
||||
android:capitalize="sentences"
|
||||
android:singleLine="false" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tag_label"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="5dip"
|
||||
android:visibility="gone"
|
||||
android:text="@string/actfm_EPA_tag_label" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/tag_name"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
android:hint="@string/actfm_EPA_tag_hint" />
|
||||
|
||||
<View
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="1dip"
|
||||
android:background="@android:drawable/divider_horizontal_dark" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/checkbox_facebook"
|
||||
android:text="@string/actfm_EPA_facebook"
|
||||
android:paddingLeft="45dip"
|
||||
android:visibility="gone"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/checkbox_twitter"
|
||||
android:text="@string/actfm_EPA_twitter"
|
||||
android:paddingLeft="45dip"
|
||||
android:visibility="gone"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginTop="10dip"
|
||||
android:padding="5dip"
|
||||
android:orientation="horizontal"
|
||||
android:background="@drawable/edit_header"
|
||||
android:baselineAligned="false">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/save"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:src="@drawable/tango_save" />
|
||||
<ImageButton
|
||||
android:id="@+id/discard"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:src="@drawable/tango_stop" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
@ -1,95 +1,69 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:background="#ffffff">
|
||||
<LinearLayout
|
||||
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="5px"
|
||||
android:paddingRight="5px"
|
||||
android:orientation="vertical">
|
||||
<ImageView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="3dip"
|
||||
android:paddingBottom="3dip"
|
||||
android:background="#2d1110"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/sharing_logo" />
|
||||
android:layout_height="fill_parent"
|
||||
android:paddingLeft="4px"
|
||||
android:paddingRight="4px"
|
||||
android:background="@drawable/sharing_gradient">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/intro"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:paddingTop="5dip"
|
||||
android:paddingBottom="10dip"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:textSize="16sp"
|
||||
android:textColor="#333333"
|
||||
android:paddingTop="20dip"
|
||||
android:textColor="#ffffff"
|
||||
android:text="@string/sharing_SLA_body" />
|
||||
<TextView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:paddingBottom="20dip"
|
||||
android:textSize="20sp"
|
||||
android:textColor="#333333"
|
||||
android:text="@string/sharing_SLA_login"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/error"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:paddingBottom="20dip"
|
||||
android:layout_below="@id/intro"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:gravity="center"
|
||||
android:paddingTop="10dip"
|
||||
android:textColor="#ff0000"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone" />
|
||||
android:textStyle="bold" />
|
||||
|
||||
<LinearLayout
|
||||
<ImageView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<com.facebook.android.LoginButton
|
||||
android:id="@+id/fb_login"
|
||||
android:src="@drawable/facebook_64"
|
||||
android:background="#00000000"
|
||||
android:gravity="right"
|
||||
android:paddingLeft="10px"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1" />
|
||||
<ImageButton
|
||||
android:id="@+id/google_login"
|
||||
android:src="@drawable/google_64"
|
||||
android:background="#00000000"
|
||||
android:gravity="left"
|
||||
android:paddingRight="10px"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"/>
|
||||
</LinearLayout>
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:scaleType="fitCenter"
|
||||
android:paddingLeft="20dip"
|
||||
android:paddingRight="20dip"
|
||||
android:src="@drawable/sharing_logo" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/notice"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:gravity="center"
|
||||
android:paddingTop="10dip"
|
||||
android:paddingBottom="20dip"
|
||||
android:paddingTop="30dip"
|
||||
android:paddingBottom="30dip"
|
||||
android:textSize="14sp"
|
||||
android:textColor="#333333"
|
||||
android:text="@string/sharing_SLA_next_step" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/taskInfo"
|
||||
android:textColor="#cccccc"
|
||||
android:text="@string/sharing_SLA_notice" />
|
||||
|
||||
<com.facebook.android.LoginButton
|
||||
android:id="@+id/fb_login"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:textSize="20sp"
|
||||
android:textColor="#333333"
|
||||
android:text="@string/sharing_SLA_task"
|
||||
android:textStyle="bold" />
|
||||
android:layout_height="50dip"
|
||||
android:layout_above="@id/notice"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:paddingLeft="20dip"
|
||||
android:paddingRight="20dip"
|
||||
android:textSize="18sp"
|
||||
android:text="@string/sharing_SLA_login" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</RelativeLayout>
|
||||
|
@ -0,0 +1,176 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- See the file "LICENSE" for the full license governing this code. -->
|
||||
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:astrid="http://schemas.android.com/apk/res/com.timsu.astrid"
|
||||
android:id="@android:id/tabhost"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="100">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
|
||||
<TabWidget
|
||||
android:id="@android:id/tabs"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="-2dp"
|
||||
android:layout_marginRight="-2dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@null" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@android:id/tabcontent"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="100">
|
||||
|
||||
<!-- task list body automatically inserted -->
|
||||
|
||||
<!-- updates tab -->
|
||||
<ListView
|
||||
android:id="@+id/tab_updates"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent" />
|
||||
|
||||
<!-- settings tab -->
|
||||
<ScrollView
|
||||
android:id="@+id/tab_settings"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:padding="5dip"
|
||||
android:orientation="vertical">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="10dip">
|
||||
|
||||
<greendroid.widget.AsyncImageView
|
||||
android:id="@+id/picture"
|
||||
android:layout_width="80dip"
|
||||
android:layout_height="80dip"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:scaleType="fitCenter"
|
||||
android:paddingRight="10dip"
|
||||
astrid:defaultSrc="@android:drawable/ic_menu_gallery" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tag_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toRightOf="@id/picture"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginTop="10dip"
|
||||
android:text="@string/actfm_TVA_tag_label" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/tag_name"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toRightOf="@id/picture"
|
||||
android:layout_below="@id/tag_label"
|
||||
android:layout_marginTop="10dip" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dip"
|
||||
android:text="@string/actfm_TVA_tag_owner_label" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tag_owner"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dip"
|
||||
android:textSize="20sp"/>
|
||||
|
||||
<View
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="1dip"
|
||||
android:background="@android:drawable/divider_horizontal_dark" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="10dip"
|
||||
android:paddingBottom="5dip"
|
||||
android:text="@string/actfm_TVA_members_label" />
|
||||
|
||||
<com.todoroo.astrid.ui.PeopleContainer
|
||||
android:id="@+id/members_container"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<!-- Footer -->
|
||||
<LinearLayout
|
||||
android:id="@+id/updatesFooter"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:visibility="gone"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<!-- Add Button -->
|
||||
<ImageButton android:id="@+id/commentButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:visibility="gone"
|
||||
android:src="@drawable/tango_chat"
|
||||
android:scaleType="fitCenter"/>
|
||||
|
||||
<!-- Comment Field -->
|
||||
<EditText android:id="@+id/commentField"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_weight="100"
|
||||
android:hint="@string/TVA_add_comment"
|
||||
android:singleLine="true"
|
||||
android:autoText="true"
|
||||
android:capitalize="sentences"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/membersFooter"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:visibility="gone"
|
||||
android:layout_marginTop="10dip"
|
||||
android:padding="5dip"
|
||||
android:background="@drawable/edit_header"
|
||||
android:orientation="horizontal"
|
||||
android:baselineAligned="false">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/saveMembers"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:src="@drawable/tango_save" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</TabHost>
|
||||
|
||||
|
@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- See the file "LICENSE" for the full license governing this code. -->
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:astrid="http://schemas.android.com/apk/res/com.timsu.astrid"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:drawable/list_selector_background"
|
||||
android:paddingTop="4dip"
|
||||
android:paddingBottom="4dip"
|
||||
android:paddingLeft="4dip"
|
||||
android:paddingRight="6dip">
|
||||
|
||||
<!-- picture -->
|
||||
<greendroid.widget.AsyncImageView android:id="@+id/picture"
|
||||
android:layout_width="40dip"
|
||||
android:layout_height="40dip"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:paddingTop="5dip"
|
||||
astrid:defaultSrc="@drawable/ic_contact_picture_2"
|
||||
android:scaleType="fitCenter" />
|
||||
|
||||
<!-- title -->
|
||||
<TextView android:id="@+id/title"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:paddingLeft="50dip"
|
||||
android:paddingRight="75dip"
|
||||
style="@style/TextAppearance.TAd_ItemTitle"
|
||||
android:textSize="16sp"/>
|
||||
|
||||
<!-- action description -->
|
||||
<TextView android:id="@+id/description"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/title"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:paddingLeft="50dip"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<!-- activity date -->
|
||||
<TextView android:id="@+id/date"
|
||||
android:layout_width="75dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:paddingTop="3dip"
|
||||
style="@style/TextAppearance.TAd_ItemDueDate"
|
||||
android:gravity="right"
|
||||
android:ellipsize="end"
|
||||
android:textSize="12sp"
|
||||
android:singleLine="true"/>
|
||||
|
||||
</RelativeLayout>
|
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<declare-styleable name="ContactsAutoComplete">
|
||||
<attr name="allowMultiple" format="boolean"/>
|
||||
<attr name="completeTags" format="boolean"/>
|
||||
</declare-styleable>
|
||||
</resources>
|
@ -0,0 +1,149 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- See the file "LICENSE" for the full license governing this code. -->
|
||||
<resources xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!-- ================================================== general terms == -->
|
||||
|
||||
<!-- People Editing Activity -->
|
||||
<string name="EPE_action">Share</string>
|
||||
|
||||
<!-- task sharing dialog: assigned hint -->
|
||||
<string name="actfm_person_hint">Contact Name</string>
|
||||
|
||||
<!-- task sharing dialog: shared with hint -->
|
||||
<string name="actfm_person_or_tag_hint">Contact or Shared Tag</string>
|
||||
|
||||
<!-- toast on transmit success -->
|
||||
<string name="actfm_toast_success">Saved on Server</string>
|
||||
|
||||
<!-- toast on transmit error -->
|
||||
<string name="actfm_toast_error">Save Unsuccessful</string>
|
||||
|
||||
<!-- can't rename or delete shared tag message -->
|
||||
<string name="actfm_tag_operation_disabled">Please view shared tags to rename or delete them.</string>
|
||||
|
||||
<!-- menu item to take a picture -->
|
||||
<string name="actfm_picture_camera">Take a Picture</string>
|
||||
|
||||
<!-- menu item to select from gallery -->
|
||||
<string name="actfm_picture_gallery">Pick from Gallery</string>
|
||||
|
||||
<!-- filter list activity: refresh tags -->
|
||||
<string name="actfm_FLA_menu_refresh">Refresh Tags</string>
|
||||
|
||||
<!-- =============================================== ProjectViewActivity == -->
|
||||
|
||||
<!-- Tag View Activity: Add Comment hint -->
|
||||
<string name="TVA_add_comment">Add a comment...</string>
|
||||
|
||||
<!-- Tag View Activity: task comment ($1 - user name, $2 - task title) -->
|
||||
<string name="UAd_title_comment">%1$s re: %2$s</string>
|
||||
|
||||
<!-- Tabs for Tag view -->
|
||||
<string-array name="TVA_tabs">
|
||||
<!-- Tab for showing tasks -->
|
||||
<item>Tasks</item>
|
||||
<!-- Tab for showing comments & updates -->
|
||||
<item>Activity</item>
|
||||
<!-- Tab for showing setting -->
|
||||
<item>Members</item>
|
||||
</string-array>
|
||||
|
||||
<!-- Tag View Menu: refresh -->
|
||||
<string name="actfm_TVA_menu_refresh">Refresh</string>
|
||||
|
||||
<!-- Tag Members: tag name label -->
|
||||
<string name="actfm_TVA_tag_label">Tag Name:</string>
|
||||
|
||||
<!-- Tag Members: tag owner label -->
|
||||
<string name="actfm_TVA_tag_owner_label">Tag Owner:</string>
|
||||
|
||||
<!-- Tag Members: tag owner value when there is no owner -->
|
||||
<string name="actfm_TVA_tag_owner_none">none</string>
|
||||
|
||||
<!-- Tag Members: team members label -->
|
||||
<string name="actfm_TVA_members_label">Team Members:</string>
|
||||
|
||||
<!-- Tag Members: tag picture -->
|
||||
<string name="actfm_TVA_tag_picture">Tag Picture</string>
|
||||
|
||||
<!-- ============================================ edit people dialog == -->
|
||||
|
||||
<!-- task sharing dialog: window title -->
|
||||
<string name="actfm_EPA_title">Share / Assign</string>
|
||||
|
||||
<!-- task sharing dialog: assigned label -->
|
||||
<string name="actfm_EPA_assign_label">Assigned to:</string>
|
||||
|
||||
<!-- task sharing dialog: assigned to me -->
|
||||
<string name="actfm_EPA_assign_me">Me</string>
|
||||
|
||||
<!-- task sharing dialog: custom email assignment -->
|
||||
<string name="actfm_EPA_assign_custom">Custom...</string>
|
||||
|
||||
<!-- task sharing dialog: shared with label -->
|
||||
<string name="actfm_EPA_share_with">Shared With:</string>
|
||||
|
||||
<!-- task sharing dialog: assigned hint -->
|
||||
<string name="actfm_EPA_assigned_hint">Contact Name</string>
|
||||
|
||||
<!-- task sharing dialog: message label text -->
|
||||
<string name="actfm_EPA_message_text">Invitation Message:</string>
|
||||
|
||||
<!-- task sharing dialog: message body -->
|
||||
<string name="actfm_EPA_message_body">Help me get this done!</string>
|
||||
|
||||
<!-- task sharing dialog: message hint -->
|
||||
<string name="actfm_EPA_tag_label">Create a shared tag?</string>
|
||||
|
||||
<!-- task sharing dialog: message hint -->
|
||||
<string name="actfm_EPA_tag_hint">(i.e. Silly Hats Club)</string>
|
||||
|
||||
<!-- task sharing dialog: share with Facebook -->
|
||||
<string name="actfm_EPA_facebook">Facebook</string>
|
||||
|
||||
<!-- task sharing dialog: share with Twitter -->
|
||||
<string name="actfm_EPA_twitter">Twitter</string>
|
||||
|
||||
<!-- task sharing dialog: # of e-mails sent (%s => # people plural string) -->
|
||||
<string name="actfm_EPA_emailed_toast">Task shared with %s</string>
|
||||
|
||||
<!-- task sharing dialog: edit people settings saved -->
|
||||
<string name="actfm_EPA_saved_toast">People Settings Saved</string>
|
||||
|
||||
<!-- task sharing dialog: invalid email (%s => email) -->
|
||||
<string name="actfm_EPA_invalid_email">Invalid E-mail: %s</string>
|
||||
|
||||
<!-- task sharing dialog: tag not found (%s => tag) -->
|
||||
<string name="actfm_EPA_invalid_tag">Tag Not Found: %s</string>
|
||||
|
||||
<!-- ========================================= sharing login activity == -->
|
||||
|
||||
<!-- share login: Title -->
|
||||
<string name="sharing_SLA_title">Welcome to Astrid.com!</string>
|
||||
|
||||
<!-- share login: Sharing Description -->
|
||||
<string name="sharing_SLA_body">Astrid.com lets you access your tasks online,
|
||||
and share & delegate with others. Perfect for personal use, friends & family,
|
||||
or your work groups!</string>
|
||||
|
||||
<!-- share login: Sharing Login Prompt -->
|
||||
<string name="sharing_SLA_login">Sign in using Facebook</string>
|
||||
|
||||
<!-- share login: Sharing notice -->
|
||||
<string name="sharing_SLA_notice">We won\'t post messages or send
|
||||
e-mails without your permission.</string>
|
||||
|
||||
<!-- ================================================ Synchronization == -->
|
||||
|
||||
<!-- Preferences Title: Act.fm -->
|
||||
<string name="actfm_APr_header">Astrid.com (Beta!)</string>
|
||||
|
||||
<!-- title for notification tray after synchronizing -->
|
||||
<string name="actfm_notification_title">Astrid.com Sync</string>
|
||||
|
||||
<!-- text for notification when comments are received -->
|
||||
<string name="actfm_notification_comments">New comments received / click for more details</string>
|
||||
|
||||
</resources>
|
||||
|
@ -1,28 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- See the file "LICENSE" for the full license governing this code. -->
|
||||
<resources xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!-- ============================================================= UI == -->
|
||||
|
||||
<!-- task action: Share -->
|
||||
<string name="sharing_action">Share</string>
|
||||
|
||||
<!-- share login: Title -->
|
||||
<string name="sharing_SLA_title">Share This Task</string>
|
||||
|
||||
<!-- share login: Sharing Description -->
|
||||
<string name="sharing_SLA_body">My Astrid lets you post tasks to the web to share with
|
||||
others. Let your friends encourage and keep you accountable!</string>
|
||||
|
||||
<!-- share login: Sharing Login Prompt -->
|
||||
<string name="sharing_SLA_login">Sign in using your Facebook or Google account:</string>
|
||||
|
||||
<!-- share login: Next Step information -->
|
||||
<string name="sharing_SLA_next_step">On the next page,
|
||||
you can choose how to share this task and choose recipients.</string>
|
||||
|
||||
<!-- share login: Sharing Task Information -->
|
||||
<string name="sharing_SLA_task">Task to Share:</string>
|
||||
|
||||
</resources>
|
||||
|
@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/sync_SPr_group_status">
|
||||
|
||||
<Preference
|
||||
android:layout="@layout/status_preference"
|
||||
android:key="@string/sync_SPr_status_key"
|
||||
android:textSize="24sp"
|
||||
android:gravity="center"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/sync_SPr_group_options">
|
||||
|
||||
<ListPreference
|
||||
android:key="@string/actfm_APr_interval_key"
|
||||
android:entries="@array/sync_SPr_interval_entries"
|
||||
android:entryValues="@array/sync_SPr_interval_values"
|
||||
android:title="@string/sync_SPr_interval_title" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/sync_SPr_group_actions">
|
||||
|
||||
<Preference
|
||||
android:key="@string/sync_SPr_sync_key"
|
||||
android:title="@string/sync_SPr_sync" />
|
||||
|
||||
<Preference
|
||||
android:key="@string/sync_SPr_forget_key"
|
||||
android:title="@string/sync_SPr_forget"
|
||||
android:summary="@string/sync_SPr_forget_description" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|