/** * Copyright (c) 2012 Todoroo Inc * * See the file "LICENSE" for the full license governing this code. */ package com.todoroo.astrid.adapter; import java.io.IOException; import java.text.ParseException; import java.util.Arrays; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.database.Cursor; import android.graphics.Bitmap; import android.support.v4.app.Fragment; import android.text.Html; import android.text.SpannableString; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextPaint; import android.text.TextUtils; import android.text.format.DateUtils; import android.text.method.LinkMovementMethod; import android.text.style.ClickableSpan; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.widget.CursorAdapter; import android.widget.TextView; import com.timsu.astrid.R; import com.todoroo.andlib.data.Property; import com.todoroo.andlib.data.Property.StringProperty; import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.utility.AndroidUtilities; import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.Preferences; import com.todoroo.astrid.actfm.sync.ActFmPreferenceService; import com.todoroo.astrid.actfm.sync.messages.NameMaps; import com.todoroo.astrid.activity.AstridActivity; import com.todoroo.astrid.data.History; import com.todoroo.astrid.data.RemoteModel; import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.User; import com.todoroo.astrid.data.UserActivity; import com.todoroo.astrid.helper.AsyncImageView; import edu.mit.mobile.android.imagecache.ImageCache; /** * Adapter for displaying a user's activity * * @author Tim Su <tim@todoroo.com> * */ public class UpdateAdapter extends CursorAdapter { // --- instance variables protected final Fragment fragment; private final int resource; private final LayoutInflater inflater; private final ImageCache imageCache; private final String linkColor; private final String fromView; public static final String USER_TABLE_ALIAS = "users_join"; //$NON-NLS-1$ public static final StringProperty USER_PICTURE = User.PICTURE.cloneAs(USER_TABLE_ALIAS, "userPicture"); //$NON-NLS-1$ private static final StringProperty USER_FIRST_NAME = User.FIRST_NAME.cloneAs(USER_TABLE_ALIAS, "userFirstName"); //$NON-NLS-1$ private static final StringProperty USER_LAST_NAME = User.LAST_NAME.cloneAs(USER_TABLE_ALIAS, "userLastName"); //$NON-NLS-1$ private static final StringProperty USER_NAME = User.NAME.cloneAs(USER_TABLE_ALIAS, "userName"); //$NON-NLS-1$ public static final StringProperty ACTIVITY_TYPE_PROPERTY = new StringProperty(null, "'" + NameMaps.TABLE_ID_USER_ACTIVITY + "' as type"); //$NON-NLS-1$//$NON-NLS-2$ public static final StringProperty HISTORY_TYPE_PROPERTY = new StringProperty(null, "'" + NameMaps.TABLE_ID_HISTORY + "'"); //$NON-NLS-1$ //$NON-NLS-2$ public static final StringProperty PADDING_PROPERTY = new StringProperty(null, "'0'"); //$NON-NLS-1$ public static final Property<?>[] USER_PROPERTIES = { USER_PICTURE, USER_FIRST_NAME, USER_LAST_NAME, USER_NAME }; public static final Property<?>[] USER_ACTIVITY_PROPERTIES = { UserActivity.CREATED_AT, UserActivity.UUID, UserActivity.ACTION, UserActivity.MESSAGE, UserActivity.TARGET_ID, UserActivity.TARGET_NAME, UserActivity.PICTURE, UserActivity.USER_UUID, UserActivity.ID, ACTIVITY_TYPE_PROPERTY, }; public static final Property<?>[] HISTORY_PROPERTIES = { History.CREATED_AT, History.USER_UUID, History.COLUMN, History.TABLE_ID, History.OLD_VALUE, History.NEW_VALUE, History.TASK, History.USER_UUID, History.ID, HISTORY_TYPE_PROPERTY, }; public static final int TYPE_PROPERTY_INDEX = USER_ACTIVITY_PROPERTIES.length - 1; private static final String TARGET_LINK_PREFIX = "$link_"; //$NON-NLS-1$ private static final Pattern TARGET_LINK_PATTERN = Pattern.compile("\\" + TARGET_LINK_PREFIX + "(\\w*)"); //$NON-NLS-1$//$NON-NLS-2$ private static final String TASK_LINK_TYPE = "task"; //$NON-NLS-1$ public static final String FROM_TAG_VIEW = "from_tag"; //$NON-NLS-1$ public static final String FROM_TASK_VIEW = "from_task"; //$NON-NLS-1$ public static final String FROM_RECENT_ACTIVITY_VIEW = "from_recent_activity"; //$NON-NLS-1$ private final User self; private final int color; private final int grayColor; /** * Constructor * * @param activity * @param resource * layout resource to inflate * @param c * database cursor * @param autoRequery * whether cursor is automatically re-queried on changes * @param onCompletedTaskListener * goal listener. can be null */ public UpdateAdapter(Fragment fragment, int resource, Cursor c, boolean autoRequery, String fromView) { super(fragment.getActivity(), c, autoRequery); DependencyInjectionService.getInstance().inject(this); linkColor = getLinkColor(fragment); inflater = (LayoutInflater) fragment.getActivity().getSystemService( Context.LAYOUT_INFLATER_SERVICE); imageCache = AsyncImageView.getImageCache(); this.fromView = fromView; this.resource = resource; this.fragment = fragment; this.self = getSelfUser(); TypedValue tv = new TypedValue(); fragment.getActivity().getTheme().resolveAttribute(R.attr.asTextColor, tv, false); color = tv.data; fragment.getActivity().getTheme().resolveAttribute(R.attr.asDueDateColor, tv, false); grayColor = tv.data; } public static User getSelfUser() { User self = new User(); readPreferenceToUser(self, USER_FIRST_NAME, ActFmPreferenceService.PREF_FIRST_NAME); readPreferenceToUser(self, USER_LAST_NAME, ActFmPreferenceService.PREF_LAST_NAME); readPreferenceToUser(self, USER_NAME, ActFmPreferenceService.PREF_NAME); readPreferenceToUser(self, USER_PICTURE, ActFmPreferenceService.PREF_PICTURE); return self; } private static void readPreferenceToUser(User u, StringProperty prop, String prefKey) { String val = Preferences.getStringValue(prefKey); if (val == null) val = ""; //$NON-NLS-1$ u.setValue(prop, val); } public static String getLinkColor(Fragment f) { TypedValue colorType = new TypedValue(); f.getActivity().getTheme().resolveAttribute(R.attr.asDetailsColor, colorType, false); return "#" + Integer.toHexString(colorType.data).substring(2); //$NON-NLS-1$ } /* ====================================================================== * =========================================================== view setup * ====================================================================== */ private class ModelHolder { public UserActivity activity = new UserActivity(); public User user = new User(); public History history = new History(); } /** Creates a new view for use in the list view */ @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { ViewGroup view = (ViewGroup)inflater.inflate(resource, parent, false); view.setTag(new ModelHolder()); // populate view content bindView(view, context, cursor); return view; } /** Populates a view with content */ @Override public void bindView(View view, Context context, Cursor c) { TodorooCursor<UserActivity> cursor = (TodorooCursor<UserActivity>)c; ModelHolder mh = ((ModelHolder) view.getTag()); String type = cursor.getString(TYPE_PROPERTY_INDEX); UserActivity update = mh.activity; update.clear(); User user = mh.user; user.clear(); History history = mh.history; boolean isSelf; if (NameMaps.TABLE_ID_USER_ACTIVITY.equals(type)) { readUserActivityProperties(cursor, update); isSelf = Task.USER_ID_SELF.equals(update.getValue(UserActivity.USER_UUID)); } else { readHistoryProperties(cursor, history); isSelf = Task.USER_ID_SELF.equals(history.getValue(History.USER_UUID)); } readUserProperties(cursor, user, self, isSelf); setFieldContentsAndVisibility(view, update, user, history, type); } public static void readUserActivityProperties(TodorooCursor<UserActivity> unionCursor, UserActivity activity) { activity.setValue(UserActivity.CREATED_AT, unionCursor.getLong(0)); activity.setValue(UserActivity.UUID, unionCursor.getString(1)); activity.setValue(UserActivity.ACTION, unionCursor.getString(2)); activity.setValue(UserActivity.MESSAGE, unionCursor.getString(3)); activity.setValue(UserActivity.TARGET_ID, unionCursor.getString(4)); activity.setValue(UserActivity.TARGET_NAME, unionCursor.getString(5)); activity.setValue(UserActivity.PICTURE, unionCursor.getString(6)); activity.setValue(UserActivity.USER_UUID, unionCursor.getString(7)); } public static void readHistoryProperties(TodorooCursor<UserActivity> unionCursor, History history) { history.setValue(History.CREATED_AT, unionCursor.getLong(0)); history.setValue(History.USER_UUID, unionCursor.getString(1)); history.setValue(History.COLUMN, unionCursor.getString(2)); history.setValue(History.TABLE_ID, unionCursor.getString(3)); history.setValue(History.OLD_VALUE, unionCursor.getString(4)); history.setValue(History.NEW_VALUE, unionCursor.getString(5)); history.setValue(History.TASK, unionCursor.getString(6)); history.setValue(History.USER_UUID, unionCursor.getString(7)); } public static void readUserProperties(TodorooCursor<UserActivity> joinCursor, User user, User self, boolean isSelf) { if (isSelf) { user.mergeWith(self.getSetValues()); } else { user.setValue(USER_FIRST_NAME, joinCursor.get(USER_FIRST_NAME)); user.setValue(USER_LAST_NAME, joinCursor.get(USER_LAST_NAME)); user.setValue(USER_NAME, joinCursor.get(USER_NAME)); user.setValue(USER_PICTURE, joinCursor.get(USER_PICTURE)); } } /** Helper method to set the contents and visibility of each field */ public synchronized void setFieldContentsAndVisibility(View view, UserActivity activity, User user, History history, String type) { // picture if (NameMaps.TABLE_ID_USER_ACTIVITY.equals(type)) { setupUserActivityRow(view, activity, user); } else if (NameMaps.TABLE_ID_HISTORY.equals(type)) { setupHistoryRow(view, history, user); } } private void setupUserActivityRow(View view, UserActivity activity, User user) { final AsyncImageView pictureView = (AsyncImageView)view.findViewById(R.id.picture); { if (user.containsNonNullValue(USER_PICTURE)) { String pictureUrl = user.getPictureUrl(USER_PICTURE, RemoteModel.PICTURE_THUMB); pictureView.setUrl(pictureUrl); } else { pictureView.setUrl(null); } pictureView.setVisibility(View.VISIBLE); } final AsyncImageView commentPictureView = (AsyncImageView)view.findViewById(R.id.comment_picture); { String pictureThumb = activity.getPictureUrl(UserActivity.PICTURE, RemoteModel.PICTURE_MEDIUM); String pictureFull = activity.getPictureUrl(UserActivity.PICTURE, RemoteModel.PICTURE_LARGE); Bitmap updateBitmap = null; if (TextUtils.isEmpty(pictureThumb)) updateBitmap = activity.getPictureBitmap(UserActivity.PICTURE); setupImagePopupForCommentView(view, commentPictureView, pictureThumb, pictureFull, updateBitmap, activity.getValue(UserActivity.MESSAGE), fragment, imageCache); } // name final TextView nameView = (TextView)view.findViewById(R.id.title); { nameView.setText(getUpdateComment((AstridActivity)fragment.getActivity(), activity, user, linkColor, fromView)); nameView.setMovementMethod(new LinkMovementMethod()); nameView.setTextColor(color); } // date final TextView date = (TextView)view.findViewById(R.id.date); { CharSequence dateString = DateUtils.getRelativeTimeSpanString(activity.getValue(UserActivity.CREATED_AT), DateUtilities.now(), DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE); date.setText(dateString); } } private void setupHistoryRow(View view, History history, User user) { final AsyncImageView pictureView = (AsyncImageView)view.findViewById(R.id.picture); { if (user.containsNonNullValue(USER_PICTURE)) { String pictureUrl = user.getPictureUrl(USER_PICTURE, RemoteModel.PICTURE_THUMB); pictureView.setUrl(pictureUrl); } else { pictureView.setUrl(null); } pictureView.setVisibility(View.VISIBLE); } final AsyncImageView commentPictureView = (AsyncImageView)view.findViewById(R.id.comment_picture); commentPictureView.setVisibility(View.GONE); final TextView nameView = (TextView)view.findViewById(R.id.title); { nameView.setText(getHistoryComment((AstridActivity) fragment.getActivity(), history, user, linkColor, fromView)); nameView.setTextColor(grayColor); } final TextView date = (TextView)view.findViewById(R.id.date); { CharSequence dateString = DateUtils.getRelativeTimeSpanString(history.getValue(History.CREATED_AT), DateUtilities.now(), DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE); date.setText(dateString); } } @Override public boolean isEnabled(int position) { return false; } public static void setupImagePopupForCommentView(View view, AsyncImageView commentPictureView, final String pictureThumb, final String pictureFull, final Bitmap updateBitmap, final String message, final Fragment fragment, ImageCache imageCache) { if ((!TextUtils.isEmpty(pictureThumb) && !"null".equals(pictureThumb)) || updateBitmap != null) { //$NON-NLS-1$ commentPictureView.setVisibility(View.VISIBLE); if (updateBitmap != null) commentPictureView.setImageBitmap(updateBitmap); else commentPictureView.setUrl(pictureThumb); if (pictureThumb != null && imageCache.contains(pictureThumb) && updateBitmap == null) { try { commentPictureView.setDefaultImageBitmap(imageCache.get(pictureThumb)); } catch (IOException e) { e.printStackTrace(); } } else if (updateBitmap == null) { commentPictureView.setUrl(pictureThumb); } view.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { AlertDialog image = new AlertDialog.Builder(fragment.getActivity()).create(); AsyncImageView imageView = new AsyncImageView(fragment.getActivity()); imageView.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT)); imageView.setDefaultImageResource(android.R.drawable.ic_menu_gallery); if (updateBitmap != null) imageView.setImageBitmap(updateBitmap); else imageView.setUrl(pictureFull); image.setView(imageView); image.setMessage(message); image.setButton(fragment.getString(R.string.DLG_close), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { return; } }); image.show(); } }); } else { commentPictureView.setVisibility(View.GONE); } } public static String linkify (String string, String linkColor) { return String.format("<font color=%s>%s</font>", linkColor, string); //$NON-NLS-1$ } public static Spanned getUpdateComment(final AstridActivity context, UserActivity activity, User user, String linkColor, String fromView) { String userDisplay; if (activity.getValue(UserActivity.USER_UUID).equals(Task.USER_ID_SELF)) { userDisplay = context.getString(R.string.update_string_user_self); } else if (user == null) { userDisplay = context.getString(R.string.ENA_no_user); } else { userDisplay = user.getDisplayName(USER_NAME, USER_FIRST_NAME, USER_LAST_NAME); } if (TextUtils.isEmpty(userDisplay)) userDisplay = context.getString(R.string.ENA_no_user); String targetName = activity.getValue(UserActivity.TARGET_NAME); String action = activity.getValue(UserActivity.ACTION); String message = activity.getValue(UserActivity.MESSAGE); int commentResource = 0; if (UserActivity.ACTION_TASK_COMMENT.equals(action)) { if (fromView.equals(FROM_TASK_VIEW) || TextUtils.isEmpty(targetName)) commentResource = R.string.update_string_default_comment; else commentResource = R.string.update_string_task_comment; } else if (UserActivity.ACTION_TAG_COMMENT.equals(action)) { if (fromView.equals(FROM_TAG_VIEW) || TextUtils.isEmpty(targetName)) commentResource = R.string.update_string_default_comment; else commentResource = R.string.update_string_tag_comment; } if (commentResource == 0) return Html.fromHtml(String.format("%s %s", userDisplay, message)); //$NON-NLS-1$ String original = context.getString(commentResource, userDisplay, targetName, message); int taskLinkIndex = original.indexOf(TARGET_LINK_PREFIX); if (taskLinkIndex < 0) return Html.fromHtml(original); String[] components = original.split(" "); //$NON-NLS-1$ SpannableStringBuilder builder = new SpannableStringBuilder(); StringBuilder htmlStringBuilder = new StringBuilder(); for (String comp : components) { Matcher m = TARGET_LINK_PATTERN.matcher(comp); if (m.find()) { builder.append(Html.fromHtml(htmlStringBuilder.toString())); htmlStringBuilder.setLength(0); String linkType = m.group(1); CharSequence link = getLinkSpan(context, activity, targetName, linkColor, linkType); if (link != null) { builder.append(link); if (!m.hitEnd()) { builder.append(comp.substring(m.end())); } builder.append(' '); } } else { htmlStringBuilder.append(comp); htmlStringBuilder.append(' '); } } if (htmlStringBuilder.length() > 0) builder.append(Html.fromHtml(htmlStringBuilder.toString())); return builder; } @SuppressWarnings("nls") public static String getHistoryComment(final AstridActivity context, History history, User user, String linkColor, String fromView) { boolean hasTask = false; JSONArray taskAttrs = null; if (!TextUtils.isEmpty(history.getValue(History.TASK))) { try { taskAttrs = new JSONArray(history.getValue(History.TASK)); hasTask = true; } catch (JSONException e) { // } } String item; String itemPosessive; if (FROM_TASK_VIEW.equals(fromView)) { item = context.getString(R.string.history_this_task); } else if (hasTask && taskAttrs != null) { item = taskAttrs.optString(1); } else { item = context.getString(R.string.history_this_list); } itemPosessive = item + "'s"; String oldValue = history.getValue(History.OLD_VALUE); String newValue = history.getValue(History.NEW_VALUE); String result = ""; String column = history.getValue(History.COLUMN); try { if (History.COL_TAG_ADDED.equals(column) || History.COL_TAG_REMOVED.equals(column)) { JSONArray tagObj = new JSONArray(newValue); String tagName = tagObj.getString(1); if (History.COL_TAG_ADDED.equals(column)) result = context.getString(R.string.history_tag_added, item, tagName); else result = context.getString(R.string.history_tag_removed, item, tagName); } else if (History.COL_ATTACHMENT_ADDED.equals(column) || History.COL_ATTACHMENT_REMOVED.equals(column)) { JSONArray attachmentArray = new JSONArray(newValue); String attachmentName = attachmentArray.getString(0); if (History.COL_ATTACHMENT_ADDED.equals(column)) result = context.getString(R.string.history_attach_added, attachmentName, item); else result = context.getString(R.string.history_attach_removed, attachmentName, item); } else if (History.COL_ACKNOWLEDGED.equals(column)) { result = context.getString(R.string.history_acknowledged, item); } else if (History.COL_SHARED_WITH.equals(column) || History.COL_UNSHARED_WITH.equals(column)) { JSONArray members = new JSONArray(newValue); String userId = history.getValue(History.USER_UUID); StringBuilder memberList = new StringBuilder(); for (int i = 0; i < members.length(); i++) { JSONObject m = members.getJSONObject(i); memberList.append(userDisplay(context, userId, m)); if (i != members.length() - 1) memberList.append(", "); } if (History.COL_SHARED_WITH.equals(column)) result = context.getString(R.string.history_shared_with, item, memberList); else result = context.getString(R.string.history_unshared_with, item, memberList); } else if (History.COL_MEMBER_ADDED.equals(column) || History.COL_MEMBER_REMOVED.equals(column)) { JSONObject userValue = new JSONObject(newValue); if (history.getValue(History.USER_UUID).equals(userValue.optString("id")) && History.COL_MEMBER_REMOVED.equals(column)) result = context.getString(R.string.history_left_list, item); else { String userDisplay = userDisplay(context, history.getValue(History.USER_UUID), userValue); if (History.COL_MEMBER_ADDED.equals(column)) result = context.getString(R.string.history_added_user, userDisplay, item); else result = context.getString(R.string.history_removed_user, userDisplay, item); } } else if (History.COL_COMPLETED_AT.equals(column)) { if (!TextUtils.isEmpty(newValue) && !"null".equals(newValue)) { result = context.getString(R.string.history_completed, item); } else { result = context.getString(R.string.history_uncompleted, item); } } else if (History.COL_DELETED_AT.equals(column)) { if (!TextUtils.isEmpty(newValue) && !"null".equals(newValue)) { result = context.getString(R.string.history_deleted, item); } else { result = context.getString(R.string.history_undeleted, item); } } else if (History.COL_IMPORTANCE.equals(column)) { int oldPriority = AndroidUtilities.tryParseInt(oldValue, 0); int newPriority = AndroidUtilities.tryParseInt(newValue, 0); result = context.getString(R.string.history_importance_changed, itemPosessive, priorityString(oldPriority), priorityString(newPriority)); } else if (History.COL_NOTES_LENGTH.equals(column)) { int oldLength = AndroidUtilities.tryParseInt(oldValue, 0); int newLength = AndroidUtilities.tryParseInt(newValue, 0); if (oldLength > 0 && newLength > oldLength) result = context.getString(R.string.history_added_description_characters, (newLength - oldLength), itemPosessive); else if (newLength == 0) result = context.getString(R.string.history_removed_description, itemPosessive); else if (oldLength > 0 && newLength < oldLength) result = context.getString(R.string.history_removed_description_characters, (oldLength - newLength), itemPosessive); else if (oldLength > 0 && oldLength == newLength) result = context.getString(R.string.history_updated_description, itemPosessive); } else if (History.COL_PUBLIC.equals(column)) { int value = AndroidUtilities.tryParseInt(newValue, 0); if (value > 0) result = context.getString(R.string.history_made_public, item); else result = context.getString(R.string.history_made_private, item); } else if (History.COL_DUE.equals(column)) { if (!TextUtils.isEmpty(oldValue) && !TextUtils.isEmpty(newValue) && !"null".equals(oldValue) && !"null".equals(newValue)) result = context.getString(R.string.history_changed_due_date, itemPosessive, dateString(context, oldValue, newValue), dateString(context, newValue, oldValue)); else if (!TextUtils.isEmpty(newValue) && !"null".equals(newValue)) result = context.getString(R.string.history_set_due_date, itemPosessive, dateString(context, newValue, DateUtilities.timeToIso8601(DateUtilities.now(), true))); else result = context.getString(R.string.history_removed_due_date, itemPosessive); } else if (History.COL_REPEAT.equals(column)) { String repeatString = getRepeatString(context, newValue); if (!TextUtils.isEmpty(repeatString)) result = context.getString(R.string.history_changed_repeat, itemPosessive, repeatString); else result = context.getString(R.string.history_removed_repeat, itemPosessive); } else if (History.COL_TASK_REPEATED.equals(column)) { result = context.getString(R.string.history_completed_repeating_task, item, dateString(context, newValue, oldValue)); } else if (History.COL_TITLE.equals(column)) { if (!TextUtils.isEmpty(oldValue) && !"null".equals(oldValue)) result = context.getString(R.string.history_title_changed, itemPosessive, oldValue, newValue); else result = context.getString(R.string.history_title_set, itemPosessive, newValue); } else if (History.COL_NAME.equals(column)) { if (!TextUtils.isEmpty(oldValue) && !"null".equals(oldValue)) result = context.getString(R.string.history_name_changed, oldValue, newValue); else result = context.getString(R.string.history_name_set, newValue); } else if (History.COL_DESCRIPTION.equals(column)) { if (!TextUtils.isEmpty(oldValue) && !"null".equals(oldValue)) result = context.getString(R.string.history_description_changed, oldValue, newValue); else result = context.getString(R.string.history_description_set, newValue); } else if (History.COL_PICTURE_ID.equals(column) || History.COL_DEFAULT_LIST_IMAGE_ID.equals(column)) { result = context.getString(R.string.history_changed_list_picture); } else if (History.COL_IS_SILENT.equals(column)) { int value = AndroidUtilities.tryParseInt(newValue, 0); if (value > 0) result = context.getString(R.string.history_silenced, item); else result = context.getString(R.string.history_unsilenced, item); } else if (History.COL_IS_FAVORITE.equals(column)) { int value = AndroidUtilities.tryParseInt(newValue, 0); if (value > 0) result = context.getString(R.string.history_favorited, item); else result = context.getString(R.string.history_unfavorited, item); } else if (History.COL_USER_ID.equals(column)) { String userId = history.getValue(History.USER_UUID); JSONObject userValue = new JSONObject(newValue); if (FROM_TAG_VIEW.equals(fromView) && !hasTask) { if (!TextUtils.isEmpty(oldValue) && !"null".equals(oldValue)) result = context.getString(R.string.history_changed_list_owner, userDisplay(context, userId, userValue)); else result = context.getString(R.string.history_created_this_list); } else if (!TextUtils.isEmpty(oldValue) && !"null".equals(oldValue) && Task.USER_ID_UNASSIGNED.equals(userValue)) result = context.getString(R.string.history_unassigned, item); else if (Task.USER_ID_UNASSIGNED.equals(oldValue) && userValue.optString("id").equals(ActFmPreferenceService.userId())) result = context.getString(R.string.history_claimed, item); else if (!TextUtils.isEmpty(oldValue) && !"null".equals(oldValue)) result = context.getString(R.string.history_assigned_to, item, userDisplay(context, userId, userValue)); else if (!userValue.optString("id").equals(ActFmPreferenceService.userId()) && !Task.USER_ID_UNASSIGNED.equals(userValue.optString("id"))) result = context.getString(R.string.history_created_for, item, userDisplay(context, userId, userValue)); else result = context.getString(R.string.history_created, item); } else { result = context.getString(R.string.history_default, column, newValue); } } catch (Exception e) { e.printStackTrace(); result = context.getString(R.string.history_default, column, newValue); } if (TextUtils.isEmpty(result)) result = context.getString(R.string.history_default, column, newValue); String userDisplay; if (history.getValue(History.USER_UUID).equals(Task.USER_ID_SELF) || history.getValue(History.USER_UUID).equals(ActFmPreferenceService.userId())) { userDisplay = context.getString(R.string.update_string_user_self); } else if (user == null) { userDisplay = context.getString(R.string.ENA_no_user); } else { userDisplay = user.getDisplayName(USER_NAME, USER_FIRST_NAME, USER_LAST_NAME); } return userDisplay + " " + result; } private static String dateString(Context context, String value, String other) { boolean includeYear = (!TextUtils.isEmpty(other) && !value.substring(0, 4).equals(other.substring(0, 4))); boolean hasTime = DateUtilities.isoStringHasTime(value); long time = 0; try { time = DateUtilities.parseIso8601(value); Date date = new Date(time); String result = DateUtilities.getDateString(context, date, includeYear); if (hasTime) result += ", " + DateUtilities.getTimeString(context, date, false); //$NON-NLS-1$ return result; } catch (ParseException e) { return value; } } private static final HashMap<String, Integer> INTERVAL_LABELS = new HashMap<String, Integer>(); static { INTERVAL_LABELS.put("DAILY", R.string.repeat_days); //$NON-NLS-1$ INTERVAL_LABELS.put("WEEKDAYS", R.string.repeat_weekdays); //$NON-NLS-1$ INTERVAL_LABELS.put("WEEKLY", R.string.repeat_weeks); //$NON-NLS-1$ INTERVAL_LABELS.put("MONTHLY", R.string.repeat_months); //$NON-NLS-1$ INTERVAL_LABELS.put("YEARLY", R.string.repeat_years); //$NON-NLS-1$ INTERVAL_LABELS.put("HOURLY", R.string.repeat_hours); //$NON-NLS-1$ INTERVAL_LABELS.put("MINUTELY", R.string.repeat_minutes); //$NON-NLS-1$ } @SuppressWarnings("nls") private static final String[] SORTED_WEEKDAYS = { "SU", "MO", "TU", "WE", "TH", "FR", "SA" }; @SuppressWarnings("nls") private static String getRepeatString(Context context, String value) { if (TextUtils.isEmpty(value) || "null".equals(value)) return null; try { JSONObject repeat = new JSONObject(value); boolean weekdays = false; if (repeat.has("freq")) { String freq = repeat.getString("freq"); int interval = repeat.getInt("interval"); JSONArray byDay = repeat.optJSONArray("byday"); String[] byDayStrings = null; if (byDay != null) { byDayStrings = new String[byDay.length()]; for (int i = 0; i < byDay.length(); i++) { byDayStrings[i] = byDay.getString(i); } } String result = ""; if ("WEEKLY".equals(freq) && byDay != null && byDayStrings != null) { Arrays.sort(byDayStrings); StringBuilder daysString = new StringBuilder(); daysString.append("["); for (String s : byDayStrings) { daysString.append("\"").append(s).append("\"").append(","); } daysString.deleteCharAt(daysString.length() - 1); daysString.append("]"); if (daysString.toString().equals("[\"FR\",\"MO\",\"TH\",\"TU\",\"WE\"]")) { result = context.getString(R.string.repeat_weekdays); weekdays = true; } } if (!weekdays) { if (interval == 1) { result = context.getString(INTERVAL_LABELS.get(freq)); result = result.substring(0, result.length() - 1); } else { result = interval + " " + context.getString(INTERVAL_LABELS.get(freq)); } } result = context.getString(R.string.history_repeat_every, result); if ("WEEKLY".equals(freq) && !weekdays && byDay != null && byDay.length() > 0 && byDayStrings != null) { Arrays.sort(byDayStrings, new Comparator<String>() { @Override public int compare(String lhs, String rhs) { int lhIndex = AndroidUtilities.indexOf(SORTED_WEEKDAYS, lhs); int rhIndex = AndroidUtilities.indexOf(SORTED_WEEKDAYS, rhs); if (lhIndex < rhIndex) return -1; else if (lhIndex > rhIndex) return 1; else return 0; } }); StringBuilder byDayDisplay = new StringBuilder(); for (String s : byDayStrings) { byDayDisplay.append(s).append(", "); } byDayDisplay.delete(byDayDisplay.length() - 2, byDayDisplay.length()); result += (" " + context.getString(R.string.history_repeat_on, byDayDisplay.toString())); } if ("COMPLETION".equals(repeat.optString("from"))) result += (" " + context.getString(R.string.history_repeat_from_completion)); return result; } else { return null; } } catch (JSONException e) { return null; } } @SuppressWarnings("nls") private static String userDisplay(Context context, String historyUserId, JSONObject userJson) { try { String id = userJson.getString("id"); String name = userJson.getString("name"); if (historyUserId.equals(id) && ActFmPreferenceService.userId().equals(id)) return context.getString(R.string.history_yourself); else if (ActFmPreferenceService.userId().equals(id)) return context.getString(R.string.history_you); else if (RemoteModel.isValidUuid(id)) return name; else return context.getString(R.string.history_a_deleted_user); } catch (JSONException e) { return context.getString(R.string.ENA_no_user).toLowerCase(); } } @SuppressWarnings("nls") private static final String[] PRIORITY_STRINGS = { "!!!", "!!", "!", "o" }; private static String priorityString(int priority) { return PRIORITY_STRINGS[priority]; } private static CharSequence getLinkSpan(final AstridActivity activity, UserActivity update, String targetName, String linkColor, String linkType) { if (TASK_LINK_TYPE.equals(linkType)) { final String taskId = update.getValue(UserActivity.TARGET_ID); if (RemoteModel.isValidUuid(taskId)) { SpannableString taskSpan = new SpannableString(targetName); taskSpan.setSpan(new ClickableSpan() { @Override public void onClick(View widget) { if (activity != null) // TODO: This shouldn't happen, but sometimes does activity.onTaskListItemClicked(taskId); } @Override public void updateDrawState(TextPaint ds) { super.updateDrawState(ds); ds.setUnderlineText(false); } }, 0, targetName.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE); return taskSpan; } else { return Html.fromHtml(linkify(targetName, linkColor)); } } return null; } }