1
0
mirror of https://github.com/TwidereProject/Twidere-Android synced 2025-02-17 04:00:48 +01:00

improved drafts, now unsent tweet wont lost even killed by system

This commit is contained in:
Mariotaku Lee 2015-01-15 00:35:37 +08:00
parent 79e80bfaad
commit 793b6bf735
15 changed files with 425 additions and 461 deletions

View File

@ -179,6 +179,7 @@ public interface TwidereConstants extends SharedPreferenceConstants, IntentConst
public static final int VIRTUAL_TABLE_ID_UNREAD_COUNTS_BY_TYPE = 109;
public static final int VIRTUAL_TABLE_ID_CACHED_USERS_WITH_RELATIONSHIP = 121;
public static final int VIRTUAL_TABLE_ID_CACHED_USERS_WITH_SCORE = 122;
public static final int VIRTUAL_TABLE_ID_DRAFTS_UNSENT = 131;
public static final int NOTIFICATION_ID_HOME_TIMELINE = 1;
public static final int NOTIFICATION_ID_MENTIONS_TIMELINE = 2;

View File

@ -484,8 +484,10 @@ public interface TwidereDataStore {
public static final String TABLE_NAME = "drafts";
public static final String CONTENT_PATH = TABLE_NAME;
public static final String CONTENT_PATH_UNSENT = TABLE_NAME + "/unsent";
public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, CONTENT_PATH);
public static final Uri CONTENT_URI_UNSENT = Uri.withAppendedPath(BASE_CONTENT_URI, CONTENT_PATH_UNSENT);
/**
* Status content.<br>

View File

@ -23,6 +23,8 @@ import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
import android.content.ContentValues;
@ -47,6 +49,8 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationCompat.Action;
import android.support.v4.util.LongSparseArray;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
@ -92,6 +96,7 @@ import org.mariotaku.twidere.model.ParcelableStatusUpdate;
import org.mariotaku.twidere.model.ParcelableUser;
import org.mariotaku.twidere.preference.ServicePickerPreference;
import org.mariotaku.twidere.provider.TwidereDataStore.Drafts;
import org.mariotaku.twidere.service.BackgroundOperationService;
import org.mariotaku.twidere.task.TwidereAsyncTask;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
import org.mariotaku.twidere.util.ContentValuesCreator;
@ -511,7 +516,8 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa
builder.media(getMedia());
}
final ContentValues values = ContentValuesCreator.createStatusDraft(builder.build());
mResolver.insert(Drafts.CONTENT_URI, values);
final Uri draftUri = mResolver.insert(Drafts.CONTENT_URI, values);
displayNewDraftNotification(text, draftUri);
}
@Override
@ -641,6 +647,25 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa
return Uri.fromFile(file);
}
private void displayNewDraftNotification(String text, Uri draftUri) {
final NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
final NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setTicker(getString(R.string.draft_saved));
builder.setContentTitle(getString(R.string.draft_saved));
builder.setContentText(text);
builder.setSmallIcon(R.drawable.ic_stat_info);
builder.setAutoCancel(true);
final Intent draftsIntent = new Intent(this, DraftsActivity.class);
builder.setContentIntent(PendingIntent.getActivity(this, 0, draftsIntent, PendingIntent.FLAG_UPDATE_CURRENT));
final Intent serviceIntent = new Intent(this, BackgroundOperationService.class);
serviceIntent.setAction(INTENT_ACTION_DISCARD_DRAFT);
serviceIntent.setData(draftUri);
final Action.Builder actionBuilder = new Action.Builder(R.drawable.ic_action_delete, getString(R.string.discard),
PendingIntent.getService(this, 0, serviceIntent, PendingIntent.FLAG_UPDATE_CURRENT));
builder.addAction(actionBuilder.build());
nm.notify(draftUri.toString(), NOTIFICATION_ID_DRAFTS, builder.build());
}
/**
* The Location Manager manages location providers. This code searches for
* the best provider of data (GPS, WiFi/cell phone tower lookup, some other
@ -880,6 +905,13 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa
return true;
}
private void saveAccountSelection() {
if (!mShouldSaveAccounts) return;
final SharedPreferences.Editor editor = mPreferences.edit();
editor.putString(KEY_COMPOSE_ACCOUNTS, TwidereArrayUtils.toString(mAccountsAdapter.getSelectedAccounts(), ',', false));
editor.apply();
}
private boolean setComposeTitle(final Intent intent) {
final String action = intent.getAction();
if (INTENT_ACTION_REPLY.equals(action)) {
@ -977,13 +1009,6 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa
return true;
}
private void saveAccountSelection() {
if (!mShouldSaveAccounts) return;
final SharedPreferences.Editor editor = mPreferences.edit();
editor.putString(KEY_COMPOSE_ACCOUNTS, TwidereArrayUtils.toString(mAccountsAdapter.getSelectedAccounts(), ',', false));
editor.apply();
}
private void updateMediaPreview() {
final int count = mMediaPreviewAdapter.getCount();
final Resources res = getResources();
@ -1416,6 +1441,13 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa
return inflater.inflate(R.layout.dialog_scrollable_status, parent, false);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mStatusContainer = view.findViewById(R.id.status_container);
mHolder = new StatusViewHolder(view);
}
@Override
public void onActivityCreated(final Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
@ -1441,13 +1473,6 @@ public class ComposeActivity extends BaseSupportDialogActivity implements TextWa
mStatusContainer.findViewById(R.id.reply_retweet_status).setVisibility(View.GONE);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mStatusContainer = view.findViewById(R.id.status_container);
mHolder = new StatusViewHolder(view);
}
}
}

View File

@ -22,6 +22,7 @@ package org.mariotaku.twidere.activity.support;
import android.app.ActionBar;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.NotificationManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
@ -31,6 +32,7 @@ import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
@ -128,7 +130,7 @@ public class DraftsActivity extends BaseSupportActivity implements LoaderCallbac
@Override
public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
final Uri uri = Drafts.CONTENT_URI;
final Uri uri = Drafts.CONTENT_URI_UNSENT;
final String[] cols = Drafts.COLUMNS;
final String orderBy = Drafts.TIMESTAMP + " DESC";
return new CursorLoader(this, uri, cols, null, null, orderBy);
@ -195,8 +197,6 @@ public class DraftsActivity extends BaseSupportActivity implements LoaderCallbac
}
mAdapter = new DraftsAdapter(this);
mListView = (ListView) findViewById(android.R.id.list);
mListView.setDivider(null);
mListView.setSelector(android.R.color.transparent);
mListView.setAdapter(mAdapter);
mListView.setOnItemClickListener(this);
mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
@ -281,6 +281,7 @@ public class DraftsActivity extends BaseSupportActivity implements LoaderCallbac
}
}
@NonNull
@Override
public Dialog onCreateDialog(final Bundle savedInstanceState) {
final Context context = ThemeUtils.getDialogThemedContext(getActivity());
@ -298,9 +299,11 @@ public class DraftsActivity extends BaseSupportActivity implements LoaderCallbac
private static final String FRAGMENT_TAG_DELETING_DRAFTS = "deleting_drafts";
private final FragmentActivity mActivity;
private final long[] mIds;
private final NotificationManager mNotificationManager;
private DeleteDraftsTask(final FragmentActivity activity, final long[] ids) {
mActivity = activity;
mNotificationManager = (NotificationManager) activity.getSystemService(NOTIFICATION_SERVICE);
mIds = ids;
}
@ -338,6 +341,10 @@ public class DraftsActivity extends BaseSupportActivity implements LoaderCallbac
if (f instanceof DialogFragment) {
((DialogFragment) f).dismiss();
}
for (long id : mIds) {
final String tag = Uri.withAppendedPath(Drafts.CONTENT_URI, String.valueOf(id)).toString();
mNotificationManager.cancel(tag, NOTIFICATION_ID_DRAFTS);
}
}
@Override

View File

@ -49,7 +49,7 @@ public class DraftsAdapter extends SimpleCursorAdapter {
private DraftItem.CursorIndices mIndices;
public DraftsAdapter(final Context context) {
super(context, R.layout.card_item_draft, null, new String[0], new int[0], 0);
super(context, R.layout.list_item_draft, null, new String[0], new int[0], 0);
mImageLoader = TwidereApplication.getInstance(context).getImageLoaderWrapper();
mImageLoadingHandler = new ImageLoadingHandler(R.id.media_preview_progress);
}

View File

@ -47,7 +47,6 @@ import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.support.annotation.NonNull;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationCompat.Action;
import android.text.Html;
import android.util.Log;
@ -55,7 +54,9 @@ import com.squareup.otto.Bus;
import org.apache.commons.lang3.ArrayUtils;
import org.mariotaku.jsonserializer.JSONFileIO;
import org.mariotaku.querybuilder.Columns.Column;
import org.mariotaku.querybuilder.Expression;
import org.mariotaku.querybuilder.RawItemArray;
import org.mariotaku.querybuilder.query.SQLSelectQuery;
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.R;
@ -75,7 +76,7 @@ import org.mariotaku.twidere.provider.TwidereDataStore.Preferences;
import org.mariotaku.twidere.provider.TwidereDataStore.SearchHistory;
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses;
import org.mariotaku.twidere.provider.TwidereDataStore.UnreadCounts;
import org.mariotaku.twidere.service.BackgroundOperationService;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
import org.mariotaku.twidere.util.CustomTabUtils;
import org.mariotaku.twidere.util.HtmlEscapeHelper;
import org.mariotaku.twidere.util.ImagePreloader;
@ -176,6 +177,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
return 0;
}
int result = 0;
final long[] newIds = new long[valuesArray.length];
if (table != null) {
mDatabaseWrapper.beginTransaction();
if (tableId == TABLE_ID_CACHED_USERS) {
@ -183,9 +185,8 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
final Expression where = Expression.equals(CachedUsers.USER_ID,
values.getAsLong(CachedUsers.USER_ID));
mDatabaseWrapper.update(table, values, where.getSQL(), null);
mDatabaseWrapper.insertWithOnConflict(table, null, values,
SQLiteDatabase.CONFLICT_IGNORE);
result++;
newIds[result++] = mDatabaseWrapper.insertWithOnConflict(table, null,
values, SQLiteDatabase.CONFLICT_IGNORE);
}
} else if (tableId == TABLE_ID_SEARCH_HISTORY) {
for (final ContentValues values : valuesArray) {
@ -193,20 +194,17 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
final Expression where = Expression.equalsArgs(SearchHistory.QUERY);
final String[] args = {values.getAsString(SearchHistory.QUERY)};
mDatabaseWrapper.update(table, values, where.getSQL(), args);
mDatabaseWrapper.insertWithOnConflict(table, null, values,
SQLiteDatabase.CONFLICT_IGNORE);
result++;
newIds[result++] = mDatabaseWrapper.insertWithOnConflict(table, null,
values, SQLiteDatabase.CONFLICT_IGNORE);
}
} else if (shouldReplaceOnConflict(tableId)) {
for (final ContentValues values : valuesArray) {
mDatabaseWrapper.insertWithOnConflict(table, null, values,
SQLiteDatabase.CONFLICT_REPLACE);
result++;
newIds[result++] = mDatabaseWrapper.insertWithOnConflict(table, null,
values, SQLiteDatabase.CONFLICT_REPLACE);
}
} else {
for (final ContentValues values : valuesArray) {
mDatabaseWrapper.insert(table, null, values);
result++;
newIds[result++] = mDatabaseWrapper.insert(table, null, values);
}
}
mDatabaseWrapper.setTransactionSuccessful();
@ -215,7 +213,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
if (result > 0) {
onDatabaseUpdated(tableId, uri);
}
onNewItemsInserted(uri, tableId, valuesArray);
onNewItemsInserted(uri, tableId, valuesArray, newIds);
return result;
} catch (final SQLException e) {
throw new IllegalStateException(e);
@ -331,7 +329,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
rowId = mDatabaseWrapper.insert(table, null, values);
}
onDatabaseUpdated(tableId, uri);
onNewItemsInserted(uri, tableId, values);
onNewItemsInserted(uri, tableId, values, rowId);
return Uri.withAppendedPath(uri, String.valueOf(rowId));
} catch (final SQLException e) {
throw new IllegalStateException(e);
@ -494,6 +492,22 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
setNotificationUri(c, CachedUsers.CONTENT_URI);
return c;
}
case VIRTUAL_TABLE_ID_DRAFTS_UNSENT: {
final TwidereApplication app = TwidereApplication.getInstance(getContext());
final AsyncTwitterWrapper twitter = app.getTwitterWrapper();
final RawItemArray sendingIds = new RawItemArray(twitter.getSendingDraftIds());
final Expression where;
if (selection != null) {
where = Expression.and(new Expression(selection),
Expression.notIn(new Column(Drafts._ID), sendingIds));
} else {
where = Expression.and(Expression.notIn(new Column(Drafts._ID), sendingIds));
}
final Cursor c = mDatabaseWrapper.query(Drafts.TABLE_NAME, projection,
where.getSQL(), selectionArgs, null, null, sortOrder);
setNotificationUri(c, getNotificationUri(tableId, uri));
return c;
}
}
if (table == null) return null;
final Cursor c = mDatabaseWrapper.query(table, projection, selection, selectionArgs, null, null, sortOrder);
@ -1174,7 +1188,13 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
notifyContentObserver(getNotificationUri(tableId, uri));
}
private void onNewItemsInserted(final Uri uri, final int tableId, final ContentValues... valuesArray) {
private void onNewItemsInserted(final Uri uri, final int tableId, final ContentValues values, final long newId) {
onNewItemsInserted(uri, tableId, new ContentValues[]{values}, new long[]{newId});
}
private void onNewItemsInserted(final Uri uri, final int tableId, final ContentValues[] valuesArray, final long[] newIds) {
if (uri == null || valuesArray == null || valuesArray.length == 0) return;
preloadImages(valuesArray);
if (!uri.getBooleanQueryParameter(QUERY_PARAM_NOTIFY, true)) return;
@ -1232,31 +1252,11 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
break;
}
case TABLE_ID_DRAFTS: {
for (ContentValues values : valuesArray) {
displayNewDraftNotification(values);
}
break;
}
}
}
private void displayNewDraftNotification(ContentValues values) {
final Context context = getContext();
final NotificationManager nm = getNotificationManager();
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
builder.setTicker(context.getString(R.string.draft_saved));
builder.setContentTitle(context.getString(R.string.draft_saved));
builder.setContentText(values.getAsString(Drafts.TEXT));
builder.setSmallIcon(R.drawable.ic_stat_info);
final Intent service = new Intent(context, BackgroundOperationService.class);
service.setAction(INTENT_ACTION_DISCARD_DRAFT);
final PendingIntent discardIntent = PendingIntent.getService(context, 0, service, 0);
final Action.Builder actionBuilder = new Action.Builder(R.drawable.ic_action_delete,
context.getString(R.string.discard), discardIntent);
builder.addAction(actionBuilder.build());
nm.notify(16, builder.build());
}
private void preloadImages(final ContentValues... values) {
if (values == null) return;
for (final ContentValues v : values) {

View File

@ -41,6 +41,7 @@ import android.widget.Toast;
import com.twitter.Extractor;
import org.mariotaku.querybuilder.Expression;
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.activity.MainActivity;
@ -60,12 +61,12 @@ import org.mariotaku.twidere.preference.ServicePickerPreference;
import org.mariotaku.twidere.provider.TwidereDataStore.CachedHashtags;
import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages;
import org.mariotaku.twidere.provider.TwidereDataStore.Drafts;
import org.mariotaku.twidere.util.TwidereArrayUtils;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
import org.mariotaku.twidere.util.ContentValuesCreator;
import org.mariotaku.twidere.util.ListUtils;
import org.mariotaku.twidere.util.MediaUploaderInterface;
import org.mariotaku.twidere.util.MessagesManager;
import org.mariotaku.twidere.util.ParseUtils;
import org.mariotaku.twidere.util.StatusCodeMessageUtils;
import org.mariotaku.twidere.util.StatusShortenerInterface;
import org.mariotaku.twidere.util.TwidereValidator;
@ -190,9 +191,21 @@ public class BackgroundOperationService extends IntentService implements Constan
handleUpdateStatusIntent(intent);
} else if (INTENT_ACTION_SEND_DIRECT_MESSAGE.equals(action)) {
handleSendDirectMessageIntent(intent);
} else if (INTENT_ACTION_DISCARD_DRAFT.equals(action)) {
handleDiscardDraftIntent(intent);
}
}
private void handleDiscardDraftIntent(Intent intent) {
final Uri data = intent.getData();
if (data == null) return;
mNotificationManager.cancel(data.toString(), NOTIFICATION_ID_DRAFTS);
final ContentResolver contentResolver = getContentResolver();
final long id = ParseUtils.parseLong(data.getLastPathSegment(), -1);
final Expression where = Expression.equals(Drafts._ID, id);
contentResolver.delete(Drafts.CONTENT_URI, where.getSQL(), null);
}
private Notification buildNotification(final String title, final String message, final int icon,
final Intent content_intent, final Intent delete_intent) {
final NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
@ -275,10 +288,22 @@ public class BackgroundOperationService extends IntentService implements Constan
for (final ParcelableStatusUpdate item : statuses) {
mNotificationManager.notify(NOTIFICATION_ID_UPDATE_STATUS,
updateUpdateStatusNotificaion(this, builder, 0, item));
final ContentValues draftValues = ContentValuesCreator.createStatusDraft(item,
ParcelableAccount.getAccountIds(item.accounts));
final Uri draftUri = mResolver.insert(Drafts.CONTENT_URI, draftValues);
final long draftId = ParseUtils.parseLong(draftUri.getLastPathSegment(), -1);
mTwitter.addSendingDraftId(draftId);
try {
Thread.sleep(15000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
final List<SingleResponse<ParcelableStatus>> result = updateStatus(builder, item);
boolean failed = false;
Exception exception = null;
final List<Long> failed_account_ids = ListUtils.fromArray(ParcelableAccount.getAccountIds(item.accounts));
final Expression where = Expression.equals(Drafts._ID, draftId);
final List<Long> failedAccountIds = ListUtils.fromArray(ParcelableAccount.getAccountIds(item.accounts));
for (final SingleResponse<ParcelableStatus> response : result) {
if (response.getData() == null) {
@ -287,11 +312,10 @@ public class BackgroundOperationService extends IntentService implements Constan
exception = response.getException();
}
} else if (response.getData().account_id > 0) {
failed_account_ids.remove(response.getData().account_id);
failedAccountIds.remove(response.getData().account_id);
}
}
if (result.isEmpty()) {
saveDrafts(item, failed_account_ids);
showErrorMessage(R.string.action_updating_status, getString(R.string.no_account_selected), false);
} else if (failed) {
// If the status is a duplicate, there's no need to save it to
@ -300,11 +324,15 @@ public class BackgroundOperationService extends IntentService implements Constan
&& ((TwitterException) exception).getErrorCode() == StatusCodeMessageUtils.STATUS_IS_DUPLICATE) {
showErrorMessage(getString(R.string.status_is_duplicate), false);
} else {
saveDrafts(item, failed_account_ids);
final ContentValues accountIdsValues = new ContentValues();
accountIdsValues.put(Drafts.ACCOUNT_IDS, ListUtils.toString(failedAccountIds, ',', false));
mResolver.update(Drafts.CONTENT_URI, accountIdsValues, where.getSQL(), null);
showErrorMessage(R.string.action_updating_status, exception, true);
displayTweetNotSendNotification();
}
} else {
showOkMessage(R.string.status_updated, false);
mResolver.delete(Drafts.CONTENT_URI, where.getSQL(), null);
if (item.media != null) {
for (final ParcelableMediaUpdate media : item.media) {
final String path = getImagePathFromUri(this, Uri.parse(media.uri));
@ -316,6 +344,7 @@ public class BackgroundOperationService extends IntentService implements Constan
}
}
}
mTwitter.removeSendingDraftId(draftId);
if (mPreferences.getBoolean(KEY_REFRESH_AFTER_TWEET, false)) {
mTwitter.refreshAll();
}
@ -324,10 +353,7 @@ public class BackgroundOperationService extends IntentService implements Constan
mNotificationManager.cancel(NOTIFICATION_ID_UPDATE_STATUS);
}
private void saveDrafts(final ParcelableStatusUpdate status, final List<Long> account_ids) {
final ContentValues values = ContentValuesCreator.createStatusDraft(status,
TwidereArrayUtils.fromList(account_ids));
mResolver.insert(Drafts.CONTENT_URI, values);
private void displayTweetNotSendNotification() {
final String title = getString(R.string.status_not_updated);
final String message = getString(R.string.status_not_updated_summary);
final Intent intent = new Intent(INTENT_ACTION_DRAFTS);
@ -396,6 +422,9 @@ public class BackgroundOperationService extends IntentService implements Constan
if (statusUpdate.accounts.length == 0) return Collections.emptyList();
try {
if (true) {
throw new UpdateStatusException("Test");
}
if (mUseUploader && mUploader == null) throw new UploaderNotFoundException(this);
if (mUseShortener && mShortener == null) throw new ShortenerNotFoundException(this);

View File

@ -53,6 +53,7 @@ import org.mariotaku.twidere.provider.TwidereDataStore;
import org.mariotaku.twidere.provider.TwidereDataStore.CachedHashtags;
import org.mariotaku.twidere.provider.TwidereDataStore.CachedTrends;
import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages;
import org.mariotaku.twidere.provider.TwidereDataStore.Drafts;
import org.mariotaku.twidere.provider.TwidereDataStore.Mentions;
import org.mariotaku.twidere.provider.TwidereDataStore.SavedSearches;
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses;
@ -76,6 +77,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import edu.ucdavis.earlybird.ProfilingUtil;
import twitter4j.DirectMessage;
@ -125,6 +127,8 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
private LongSparseMap<Long> mCreatingRetweetIds = new LongSparseMap<>();
private LongSparseMap<Long> mDestroyingStatusIds = new LongSparseMap<>();
private CopyOnWriteArraySet<Long> mSendingDraftIds = new CopyOnWriteArraySet<>();
public AsyncTwitterWrapper(final Context context) {
mContext = context;
final TwidereApplication app = TwidereApplication.getInstance(context);
@ -139,33 +143,26 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
return mAsyncTaskManager.add(task, true);
}
public void addSendingDraftId(long id) {
mSendingDraftIds.add(id);
mResolver.notifyChange(Drafts.CONTENT_URI_UNSENT, null);
}
public int addUserListMembersAsync(final long accountId, final long listId, final ParcelableUser... users) {
final AddUserListMembersTask task = new AddUserListMembersTask(accountId, listId, users);
return mAsyncTaskManager.add(task, true);
}
public Context getContext() {
return mContext;
public int cancelRetweetAsync(long account_id, long status_id, long my_retweet_id) {
if (my_retweet_id > 0)
return destroyStatusAsync(account_id, my_retweet_id);
else if (status_id > 0)
return destroyStatusAsync(account_id, status_id);
return -1;
}
public AsyncTaskManager getTaskManager() {
return mAsyncTaskManager;
}
public boolean isCreatingFavorite(final long accountId, final long statusId) {
return mCreatingFavoriteIds.has(accountId, statusId);
}
public boolean isDestroyingFavorite(final long accountId, final long statusId) {
return mDestroyingFavoriteIds.has(accountId, statusId);
}
public boolean isCreatingRetweet(final long accountId, final long statusId) {
return mCreatingRetweetIds.has(accountId, statusId);
}
public boolean isDestroyingStatus(final long accountId, final long statusId) {
return mDestroyingStatusIds.has(accountId, statusId);
public int cancelRetweetAsync(@NonNull final ParcelableStatus status) {
return cancelRetweetAsync(status.account_id, status.id, status.my_retweet_id);
}
public void clearNotificationAsync(final int notificationType) {
@ -187,11 +184,6 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
return mAsyncTaskManager.add(task, true);
}
public int createMuteAsync(final long accountId, final long user_id) {
final CreateMuteTask task = new CreateMuteTask(accountId, user_id);
return mAsyncTaskManager.add(task, true);
}
public int createFavoriteAsync(final long accountId, final long status_id) {
final CreateFavoriteTask task = new CreateFavoriteTask(accountId, status_id);
return mAsyncTaskManager.add(task, true);
@ -207,6 +199,11 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
return mAsyncTaskManager.add(task, true);
}
public int createMuteAsync(final long accountId, final long user_id) {
final CreateMuteTask task = new CreateMuteTask(accountId, user_id);
return mAsyncTaskManager.add(task, true);
}
public int createSavedSearchAsync(final long accountId, final String query) {
final CreateSavedSearchTask task = new CreateSavedSearchTask(accountId, query);
return mAsyncTaskManager.add(task, true);
@ -233,29 +230,11 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
return mAsyncTaskManager.add(task, true);
}
public int cancelRetweetAsync(long account_id, long status_id, long my_retweet_id) {
if (my_retweet_id > 0)
return destroyStatusAsync(account_id, my_retweet_id);
else if (status_id > 0)
return destroyStatusAsync(account_id, status_id);
return -1;
}
public int cancelRetweetAsync(@NonNull final ParcelableStatus status) {
return cancelRetweetAsync(status.account_id, status.id, status.my_retweet_id);
}
public int destroyBlockAsync(final long accountId, final long user_id) {
final DestroyBlockTask task = new DestroyBlockTask(accountId, user_id);
return mAsyncTaskManager.add(task, true);
}
public int destroyMuteAsync(final long accountId, final long user_id) {
final DestroyMuteTask task = new DestroyMuteTask(accountId, user_id);
return mAsyncTaskManager.add(task, true);
}
public int destroyDirectMessageAsync(final long accountId, final long message_id) {
final DestroyDirectMessageTask task = new DestroyDirectMessageTask(accountId, message_id);
return mAsyncTaskManager.add(task, true);
@ -271,6 +250,11 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
return mAsyncTaskManager.add(task, true);
}
public int destroyMuteAsync(final long accountId, final long user_id) {
final DestroyMuteTask task = new DestroyMuteTask(accountId, user_id);
return mAsyncTaskManager.add(task, true);
}
public int destroySavedSearchAsync(final long accountId, final int searchId) {
final DestroySavedSearchTask task = new DestroySavedSearchTask(accountId, searchId);
return mAsyncTaskManager.add(task, true);
@ -291,12 +275,21 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
return mAsyncTaskManager.add(task, true);
}
public Context getContext() {
return mContext;
}
public int getHomeTimelineAsync(final long[] accountIds, final long[] max_ids, final long[] since_ids) {
mAsyncTaskManager.cancel(mGetHomeTimelineTaskId);
final GetHomeTimelineTask task = new GetHomeTimelineTask(accountIds, max_ids, since_ids);
return mGetHomeTimelineTaskId = mAsyncTaskManager.add(task, true);
}
public static AsyncTwitterWrapper getInstance(final Context context) {
if (sInstance != null) return sInstance;
return sInstance = new AsyncTwitterWrapper(context);
}
public int getLocalTrendsAsync(final long accountId, final int woeid) {
mAsyncTaskManager.cancel(mGetLocalTrendsTaskId);
final GetLocalTrendsTask task = new GetLocalTrendsTask(accountId, woeid);
@ -315,16 +308,38 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
return mGetReceivedDirectMessagesTaskId = mAsyncTaskManager.add(task, true);
}
public int getSavedSearchesAsync(long[] accountIds) {
final GetSavedSearchesTask task = new GetSavedSearchesTask(this);
final Long[] ids = new Long[accountIds.length];
for (int i = 0, j = accountIds.length; i < j; i++) {
ids[i] = accountIds[i];
}
return mAsyncTaskManager.add(task, true, ids);
}
@NonNull
public long[] getSendingDraftIds() {
return ArrayUtils.toPrimitive(mSendingDraftIds.toArray(new Long[mSendingDraftIds.size()]));
}
public int getSentDirectMessagesAsync(final long[] accountIds, final long[] max_ids, final long[] since_ids) {
mAsyncTaskManager.cancel(mGetSentDirectMessagesTaskId);
final GetSentDirectMessagesTask task = new GetSentDirectMessagesTask(accountIds, max_ids, since_ids);
return mGetSentDirectMessagesTaskId = mAsyncTaskManager.add(task, true);
}
public AsyncTaskManager getTaskManager() {
return mAsyncTaskManager;
}
public boolean hasActivatedTask() {
return mAsyncTaskManager.hasRunningTask();
}
public boolean isCreatingFavorite(final long accountId, final long statusId) {
return mCreatingFavoriteIds.has(accountId, statusId);
}
public boolean isCreatingFriendship(final long accountId, final long userId) {
for (final ManagedAsyncTask<?, ?, ?> task : mAsyncTaskManager.getTaskSpecList()) {
if (task instanceof CreateFriendshipTask) {
@ -338,6 +353,14 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
return false;
}
public boolean isCreatingRetweet(final long accountId, final long statusId) {
return mCreatingRetweetIds.has(accountId, statusId);
}
public boolean isDestroyingFavorite(final long accountId, final long statusId) {
return mDestroyingFavoriteIds.has(accountId, statusId);
}
public boolean isDestroyingFriendship(final long accountId, final long userId) {
for (final ManagedAsyncTask<?, ?, ?> task : mAsyncTaskManager.getTaskSpecList()) {
if (task instanceof DestroyFriendshipTask) {
@ -351,6 +374,10 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
return false;
}
public boolean isDestroyingStatus(final long accountId, final long statusId) {
return mDestroyingStatusIds.has(accountId, statusId);
}
public boolean isHomeTimelineRefreshing() {
return mAsyncTaskManager.hasRunningTasksForTag(TASK_TAG_GET_HOME_TIMELINE)
|| mAsyncTaskManager.hasRunningTasksForTag(TASK_TAG_STORE_HOME_TIMELINE);
@ -403,6 +430,11 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
return getHomeTimelineAsync(accountIds, null, statusSinceIds);
}
public void removeSendingDraftId(long id) {
mSendingDraftIds.remove(id);
mResolver.notifyChange(Drafts.CONTENT_URI_UNSENT, null);
}
public void removeUnreadCountsAsync(final int position, final LongSparseArray<Set<Long>> counts) {
final RemoveUnreadCountsTask task = new RemoveUnreadCountsTask(position, counts);
task.executeTask();
@ -482,9 +514,32 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
return mAsyncTaskManager.add(task, true);
}
public static AsyncTwitterWrapper getInstance(final Context context) {
if (sInstance != null) return sInstance;
return sInstance = new AsyncTwitterWrapper(context);
static class GetSavedSearchesTask extends ManagedAsyncTask<Long, Void, SingleResponse<Void>> {
private final Context mContext;
GetSavedSearchesTask(AsyncTwitterWrapper twitter) {
super(twitter.getContext(), twitter.getTaskManager());
this.mContext = twitter.getContext();
}
@Override
protected SingleResponse<Void> doInBackground(Long... params) {
final ContentResolver cr = mContext.getContentResolver();
for (long accountId : params) {
final Twitter twitter = Utils.getTwitterInstance(mContext, accountId, true);
try {
final ResponseList<SavedSearch> searches = twitter.getSavedSearches();
final ContentValues[] values = ContentValuesCreator.createSavedSearches(searches, accountId);
final Expression where = Expression.equals(SavedSearches.ACCOUNT_ID, accountId);
cr.delete(SavedSearches.CONTENT_URI, where.getSQL(), null);
ContentResolverUtils.bulkInsert(cr, SavedSearches.CONTENT_URI, values);
} catch (TwitterException e) {
e.printStackTrace();
}
}
return SingleResponse.getInstance();
}
}
public static class UpdateProfileBannerImageTask extends ManagedAsyncTask<Void, Void, SingleResponse<ParcelableUser>> {
@ -810,49 +865,6 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
}
class CreateMuteTask extends ManagedAsyncTask<Void, Void, SingleResponse<ParcelableUser>> {
private final long mAccountId, mUserId;
public CreateMuteTask(final long accountId, final long userId) {
super(mContext, mAsyncTaskManager);
this.mAccountId = accountId;
this.mUserId = userId;
}
@Override
protected SingleResponse<ParcelableUser> doInBackground(final Void... params) {
final Twitter twitter = getTwitterInstance(mContext, mAccountId, false);
if (twitter == null) return SingleResponse.getInstance();
try {
final User user = twitter.createMute(mUserId);
Utils.setLastSeen(mContext, user.getId(), -1);
final Expression where = Expression.and(Expression.equals(Statuses.ACCOUNT_ID, mAccountId),
Expression.equals(Statuses.USER_ID, mUserId));
mResolver.delete(Statuses.CONTENT_URI, where.getSQL(), null);
return SingleResponse.getInstance(new ParcelableUser(user, mAccountId), null);
} catch (final TwitterException e) {
return SingleResponse.getInstance(null, e);
}
}
@Override
protected void onPostExecute(final SingleResponse<ParcelableUser> result) {
if (result.hasData()) {
final String message = mContext.getString(R.string.muted_user,
getUserName(mContext, result.getData()));
mMessagesManager.showInfoMessage(message, false);
final Bus bus = TwidereApplication.getInstance(mContext).getMessageBus();
bus.post(new FriendshipUpdatedEvent(result.getData()));
} else {
mMessagesManager.showErrorMessage(R.string.action_muting, result.getException(), true);
}
super.onPostExecute(result);
}
}
class CreateFavoriteTask extends ManagedAsyncTask<Void, Void, SingleResponse<ParcelableStatus>> {
private final long account_id, status_id;
@ -973,6 +985,15 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
this.user_ids = user_ids;
}
private void deleteCaches(final List<Long> list) {
for (final Uri uri : STATUSES_URIS) {
bulkDelete(mResolver, uri, Statuses.USER_ID, list, Statuses.ACCOUNT_ID + " = " + account_id, false);
}
// I bet you don't want to see these users in your auto complete list.
//TODO insert to blocked users data
// bulkDelete(mResolver, CachedUsers.CONTENT_URI, CachedUsers.USER_ID, list, null, false);
}
@Override
protected ListResponse<Long> doInBackground(final Void... params) {
final List<Long> blocked_users = new ArrayList<>();
@ -1008,14 +1029,50 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
super.onPostExecute(result);
}
private void deleteCaches(final List<Long> list) {
for (final Uri uri : STATUSES_URIS) {
bulkDelete(mResolver, uri, Statuses.USER_ID, list, Statuses.ACCOUNT_ID + " = " + account_id, false);
}
// I bet you don't want to see these users in your auto complete list.
//TODO insert to blocked users data
// bulkDelete(mResolver, CachedUsers.CONTENT_URI, CachedUsers.USER_ID, list, null, false);
}
class CreateMuteTask extends ManagedAsyncTask<Void, Void, SingleResponse<ParcelableUser>> {
private final long mAccountId, mUserId;
public CreateMuteTask(final long accountId, final long userId) {
super(mContext, mAsyncTaskManager);
this.mAccountId = accountId;
this.mUserId = userId;
}
@Override
protected SingleResponse<ParcelableUser> doInBackground(final Void... params) {
final Twitter twitter = getTwitterInstance(mContext, mAccountId, false);
if (twitter == null) return SingleResponse.getInstance();
try {
final User user = twitter.createMute(mUserId);
Utils.setLastSeen(mContext, user.getId(), -1);
final Expression where = Expression.and(Expression.equals(Statuses.ACCOUNT_ID, mAccountId),
Expression.equals(Statuses.USER_ID, mUserId));
mResolver.delete(Statuses.CONTENT_URI, where.getSQL(), null);
return SingleResponse.getInstance(new ParcelableUser(user, mAccountId), null);
} catch (final TwitterException e) {
return SingleResponse.getInstance(null, e);
}
}
@Override
protected void onPostExecute(final SingleResponse<ParcelableUser> result) {
if (result.hasData()) {
final String message = mContext.getString(R.string.muted_user,
getUserName(mContext, result.getData()));
mMessagesManager.showInfoMessage(message, false);
final Bus bus = TwidereApplication.getInstance(mContext).getMessageBus();
bus.post(new FriendshipUpdatedEvent(result.getData()));
} else {
mMessagesManager.showErrorMessage(R.string.action_muting, result.getException(), true);
}
super.onPostExecute(result);
}
}
class CreateSavedSearchTask extends ManagedAsyncTask<Void, Void, SingleResponse<SavedSearch>> {
@ -1287,47 +1344,6 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
}
class DestroyMuteTask extends ManagedAsyncTask<Void, Void, SingleResponse<ParcelableUser>> {
private final long mAccountId;
private final long mUserId;
public DestroyMuteTask(final long accountId, final long userId) {
super(mContext, mAsyncTaskManager);
mAccountId = accountId;
mUserId = userId;
}
@Override
protected SingleResponse<ParcelableUser> doInBackground(final Void... params) {
final Twitter twitter = getTwitterInstance(mContext, mAccountId, false);
if (twitter == null) return SingleResponse.getInstance();
try {
final User user = twitter.destroyMute(mUserId);
Utils.setLastSeen(mContext, user.getId(), -1);
return SingleResponse.getInstance(new ParcelableUser(user, mAccountId), null);
} catch (final TwitterException e) {
return SingleResponse.getInstance(null, e);
}
}
@Override
protected void onPostExecute(final SingleResponse<ParcelableUser> result) {
if (result.hasData()) {
final String message = mContext.getString(R.string.unmuted_user,
getUserName(mContext, result.getData()));
mMessagesManager.showInfoMessage(message, false);
final Bus bus = TwidereApplication.getInstance(mContext).getMessageBus();
bus.post(new FriendshipUpdatedEvent(result.getData()));
} else {
mMessagesManager.showErrorMessage(R.string.action_unmuting, result.getException(), true);
}
super.onPostExecute(result);
}
}
class DestroyDirectMessageTask extends ManagedAsyncTask<Void, Void, SingleResponse<DirectMessage>> {
private final long message_id;
@ -1340,6 +1356,12 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
this.message_id = message_id;
}
private void deleteMessages(final long message_id) {
final String where = DirectMessages.MESSAGE_ID + " = " + message_id;
mResolver.delete(DirectMessages.Inbox.CONTENT_URI, where, null);
mResolver.delete(DirectMessages.Outbox.CONTENT_URI, where, null);
}
@Override
protected SingleResponse<DirectMessage> doInBackground(final Void... args) {
final Twitter twitter = getTwitterInstance(mContext, account_id, false);
@ -1356,6 +1378,13 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
}
}
private boolean isMessageNotFound(final Exception e) {
if (!(e instanceof TwitterException)) return false;
final TwitterException te = (TwitterException) e;
return te.getErrorCode() == StatusCodeMessageUtils.PAGE_NOT_FOUND
|| te.getStatusCode() == HttpResponseCode.NOT_FOUND;
}
@Override
protected void onPostExecute(final SingleResponse<DirectMessage> result) {
super.onPostExecute(result);
@ -1367,18 +1396,7 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
}
}
private void deleteMessages(final long message_id) {
final String where = DirectMessages.MESSAGE_ID + " = " + message_id;
mResolver.delete(DirectMessages.Inbox.CONTENT_URI, where, null);
mResolver.delete(DirectMessages.Outbox.CONTENT_URI, where, null);
}
private boolean isMessageNotFound(final Exception e) {
if (!(e instanceof TwitterException)) return false;
final TwitterException te = (TwitterException) e;
return te.getErrorCode() == StatusCodeMessageUtils.PAGE_NOT_FOUND
|| te.getStatusCode() == HttpResponseCode.NOT_FOUND;
}
}
class DestroyFavoriteTask extends ManagedAsyncTask<Void, Void, SingleResponse<ParcelableStatus>> {
@ -1494,6 +1512,47 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
}
class DestroyMuteTask extends ManagedAsyncTask<Void, Void, SingleResponse<ParcelableUser>> {
private final long mAccountId;
private final long mUserId;
public DestroyMuteTask(final long accountId, final long userId) {
super(mContext, mAsyncTaskManager);
mAccountId = accountId;
mUserId = userId;
}
@Override
protected SingleResponse<ParcelableUser> doInBackground(final Void... params) {
final Twitter twitter = getTwitterInstance(mContext, mAccountId, false);
if (twitter == null) return SingleResponse.getInstance();
try {
final User user = twitter.destroyMute(mUserId);
Utils.setLastSeen(mContext, user.getId(), -1);
return SingleResponse.getInstance(new ParcelableUser(user, mAccountId), null);
} catch (final TwitterException e) {
return SingleResponse.getInstance(null, e);
}
}
@Override
protected void onPostExecute(final SingleResponse<ParcelableUser> result) {
if (result.hasData()) {
final String message = mContext.getString(R.string.unmuted_user,
getUserName(mContext, result.getData()));
mMessagesManager.showInfoMessage(message, false);
final Bus bus = TwidereApplication.getInstance(mContext).getMessageBus();
bus.post(new FriendshipUpdatedEvent(result.getData()));
} else {
mMessagesManager.showErrorMessage(R.string.action_unmuting, result.getException(), true);
}
super.onPostExecute(result);
}
}
class DestroySavedSearchTask extends ManagedAsyncTask<Void, Void, SingleResponse<SavedSearch>> {
private final long mAccountId;
@ -1696,6 +1755,10 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
public abstract ResponseList<DirectMessage> getDirectMessages(Twitter twitter, Paging paging)
throws TwitterException;
final boolean isMaxIdsValid() {
return max_ids != null && max_ids.length == account_ids.length;
}
@Override
protected List<MessageListResponse> doInBackground(final Void... params) {
@ -1735,6 +1798,10 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
}
final boolean isSinceIdsValid() {
return since_ids != null && since_ids.length == account_ids.length;
}
@Override
protected void onPostExecute(final List<MessageListResponse> result) {
super.onPostExecute(result);
@ -1746,13 +1813,6 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
}
}
final boolean isMaxIdsValid() {
return max_ids != null && max_ids.length == account_ids.length;
}
final boolean isSinceIdsValid() {
return since_ids != null && since_ids.length == account_ids.length;
}
}
@ -1917,6 +1977,10 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
public abstract ResponseList<twitter4j.Status> getStatuses(Twitter twitter, Paging paging)
throws TwitterException;
final boolean isMaxIdsValid() {
return mMaxIds != null && mMaxIds.length == mAccountIds.length;
}
@Override
protected List<StatusListResponse> doInBackground(final Void... params) {
@ -1958,14 +2022,11 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
return result;
}
final boolean isMaxIdsValid() {
return mMaxIds != null && mMaxIds.length == mAccountIds.length;
}
final boolean isSinceIdsValid() {
return mSinceIds != null && mSinceIds.length == mAccountIds.length;
}
}
abstract class GetTrendsTask extends ManagedAsyncTask<Void, Void, ListResponse<Trends>> {
@ -2185,6 +2246,8 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
this.notify = notify;
}
abstract boolean isOutgoing();
@Override
protected SingleResponse<Boolean> doInBackground(final Void... args) {
@ -2221,7 +2284,6 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
return SingleResponse.getInstance(succeed);
}
abstract boolean isOutgoing();
}
@ -2265,43 +2327,6 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
}
public int getSavedSearchesAsync(long[] accountIds) {
final GetSavedSearchesTask task = new GetSavedSearchesTask(this);
final Long[] ids = new Long[accountIds.length];
for (int i = 0, j = accountIds.length; i < j; i++) {
ids[i] = accountIds[i];
}
return mAsyncTaskManager.add(task, true, ids);
}
static class GetSavedSearchesTask extends ManagedAsyncTask<Long, Void, SingleResponse<Void>> {
private final Context mContext;
GetSavedSearchesTask(AsyncTwitterWrapper twitter) {
super(twitter.getContext(), twitter.getTaskManager());
this.mContext = twitter.getContext();
}
@Override
protected SingleResponse<Void> doInBackground(Long... params) {
final ContentResolver cr = mContext.getContentResolver();
for (long accountId : params) {
final Twitter twitter = Utils.getTwitterInstance(mContext, accountId, true);
try {
final ResponseList<SavedSearch> searches = twitter.getSavedSearches();
final ContentValues[] values = ContentValuesCreator.createSavedSearches(searches, accountId);
final Expression where = Expression.equals(SavedSearches.ACCOUNT_ID, accountId);
cr.delete(SavedSearches.CONTENT_URI, where.getSQL(), null);
ContentResolverUtils.bulkInsert(cr, SavedSearches.CONTENT_URI, values);
} catch (TwitterException e) {
e.printStackTrace();
}
}
return SingleResponse.getInstance();
}
}
class StoreReceivedDirectMessagesTask extends StoreDirectMessagesTask {
public StoreReceivedDirectMessagesTask(final List<MessageListResponse> result, final boolean notify) {

View File

@ -110,7 +110,6 @@ import org.apache.http.NameValuePair;
import org.json.JSONException;
import org.mariotaku.jsonserializer.JSONSerializer;
import org.mariotaku.menucomponent.internal.menu.MenuUtils;
import org.mariotaku.menucomponent.widget.PopupMenu;
import org.mariotaku.querybuilder.AllColumns;
import org.mariotaku.querybuilder.Columns;
import org.mariotaku.querybuilder.Columns.Column;
@ -348,6 +347,8 @@ public final class Utils implements Constants, TwitterConstants {
VIRTUAL_TABLE_ID_CACHED_USERS_WITH_RELATIONSHIP);
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CachedUsers.CONTENT_PATH_WITH_SCORE + "/#",
VIRTUAL_TABLE_ID_CACHED_USERS_WITH_SCORE);
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Drafts.CONTENT_PATH_UNSENT,
VIRTUAL_TABLE_ID_DRAFTS_UNSENT);
LINK_HANDLER_URI_MATCHER.addURI(AUTHORITY_STATUS, null, LINK_ID_STATUS);
LINK_HANDLER_URI_MATCHER.addURI(AUTHORITY_USER, null, LINK_ID_USER);

View File

@ -21,10 +21,13 @@ package org.mariotaku.twidere.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.support.annotation.NonNull;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.Adapter;
import android.support.v7.widget.RecyclerView.ItemDecoration;
import android.support.v7.widget.RecyclerView.Recycler;
import android.support.v7.widget.RecyclerView.State;
import android.support.v7.widget.RecyclerView.ViewHolder;
@ -34,10 +37,8 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.StackView;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.adapter.ArrayAdapter;
import org.mariotaku.twidere.app.TwidereApplication;
import org.mariotaku.twidere.model.ParcelableAccount;
import org.mariotaku.twidere.util.ImageLoaderWrapper;
@ -47,8 +48,9 @@ import org.mariotaku.twidere.view.iface.IColorLabelView.Helper;
* Created by mariotaku on 14/12/8.
*/
public class ComposeSelectAccountButton extends ViewGroup {
private final AccountIconsStackAdapter mAccountIconsAdapter;
private final AccountIconsAdapter mAccountIconsAdapter;
private final Helper mColorLabelHelper;
private final InternalRecyclerView recyclerView;
public ComposeSelectAccountButton(Context context) {
this(context, null);
@ -62,19 +64,27 @@ public class ComposeSelectAccountButton extends ViewGroup {
super(context, attrs, defStyle);
mColorLabelHelper = new Helper(this, context, attrs, defStyle);
mColorLabelHelper.setIgnorePaddings(true);
mAccountIconsAdapter = new AccountIconsStackAdapter(context);
final StackView stackView = new InternalStackView(context);
stackView.setAdapter(mAccountIconsAdapter);
addView(stackView);
// final RecyclerView recyclerView = new InternalRecyclerView(context);
// final LinearLayoutManager linearLayoutManager = new MyLinearLayoutManager(context);
// linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
// linearLayoutManager.setStackFromEnd(true);
//// linearLayoutManager.setReverseLayout(true);
// recyclerView.setLayoutManager(linearLayoutManager);
// recyclerView.setAdapter(mAccountIconsAdapter);
// ViewCompat.setOverScrollMode(recyclerView, ViewCompat.OVER_SCROLL_NEVER);
// addView(recyclerView);
mAccountIconsAdapter = new AccountIconsAdapter(context);
recyclerView = new InternalRecyclerView(context);
final LinearLayoutManager linearLayoutManager = new MyLinearLayoutManager(context);
linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setAdapter(mAccountIconsAdapter);
recyclerView.addItemDecoration(new ItemDecoration() {
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
final int pos = parent.getChildPosition(view);
if (pos == 0) {
outRect.left = 0;
} else {
final int count = state.getItemCount();
outRect.left = -(parent.getHeight() - (parent.getWidth() - parent.getHeight()) / (count - 1));
}
}
});
ViewCompat.setOverScrollMode(recyclerView, ViewCompat.OVER_SCROLL_NEVER);
addView(recyclerView);
mAccountIconsAdapter.setSelectedAccounts(ParcelableAccount.getAccounts(context, false, false));
}
public void setSelectedAccounts(long[] accountIds) {
@ -109,7 +119,7 @@ public class ComposeSelectAccountButton extends ViewGroup {
if (maxWidth == 0) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
} else {
setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec), heightMeasureSpec);
setMeasuredDimension(maxWidth, heightMeasureSpec);
}
}
@ -155,7 +165,7 @@ public class ComposeSelectAccountButton extends ViewGroup {
@Override
public AccountIconViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final View view = mInflater.inflate(R.layout.adapter_item_compose_account, parent, false);
final View view = mInflater.inflate(R.layout.adapter_item_compose_account2, parent, false);
return new AccountIconViewHolder(view);
}
@ -170,56 +180,11 @@ public class ComposeSelectAccountButton extends ViewGroup {
}
public void setSelectedAccounts(ParcelableAccount[] accounts) {
if (accounts != null) {
// ArrayUtils.reverse(accounts);
}
mAccounts = accounts;
notifyDataSetChanged();
}
}
private static class AccountIconsStackAdapter extends ArrayAdapter<ParcelableAccount> {
private final Context mContext;
private final LayoutInflater mInflater;
private final ImageLoaderWrapper mImageLoader;
public AccountIconsStackAdapter(Context context) {
super(context, R.layout.adapter_item_compose_account);
mContext = context;
mInflater = LayoutInflater.from(context);
mImageLoader = TwidereApplication.getInstance(context).getImageLoaderWrapper();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final View view = super.getView(position, convertView, parent);
final ParcelableAccount account = getItem(position);
final ImageView iconView = (ImageView) view.findViewById(android.R.id.icon);
mImageLoader.displayProfileImage(iconView, account.profile_image_url);
return view;
}
public void setSelectedAccounts(ParcelableAccount[] accounts) {
clear();
if (accounts != null) {
addAll(accounts);
}
}
}
private static class InternalStackView extends StackView {
private InternalStackView(Context context) {
super(context);
}
@Override
public boolean dispatchTouchEvent(@NonNull MotionEvent ev) {
return false;
}
}
private static class InternalRecyclerView extends RecyclerView {
public InternalRecyclerView(Context context) {
super(context);
@ -240,56 +205,15 @@ public class ComposeSelectAccountButton extends ViewGroup {
}
private static class MyLinearLayoutManager extends LinearLayoutManager {
private int mWidth, mHeight;
public MyLinearLayoutManager(Context context) {
super(context);
}
private int findChildIndex(View view) {
for (int i = 0, j = getChildCount(); i < j; i++) {
if (getChildAt(i) == view) return i;
}
return -1;
}
@Override
public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {
final RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
final int contentWidth = mWidth - getPaddingLeft() - getPaddingRight();
final int contentHeight = mHeight - getPaddingTop() - getPaddingBottom();
final int itemCount = getItemCount();
final int firstVisibleItem = findFirstVisibleItemPosition();
final int idx = findChildIndex(child);
if (firstVisibleItem < 1 && idx == 0) {
// when firstVisibleItem is 0 or -1, assume view with idx == 0 is first view
if (itemCount == 1) {
layoutParams.leftMargin = (contentWidth - contentHeight) / 2 - child.getPaddingLeft() - child.getPaddingRight();
} else {
layoutParams.leftMargin = 0;
}
} else {
layoutParams.leftMargin = -Math.min((contentHeight * itemCount - contentWidth)
/ (itemCount - 1) + child.getPaddingLeft() + child.getPaddingRight(), contentHeight);
}
super.measureChildWithMargins(child, widthUsed, heightUsed);
}
@Override
public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {
final int height = MeasureSpec.getSize(heightSpec), width;
final int itemCount = getItemCount();
if (itemCount > 1) {
width = Math.round(height * 1.5f);
} else if (itemCount > 0 && state.getItemCount() > 0) {
final View firstChild = recycler.getViewForPosition(0);
width = height + firstChild.getPaddingLeft() + firstChild.getPaddingRight();
} else {
width = height;
}
mWidth = width;
mHeight = height;
super.onMeasure(recycler, state, MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), heightSpec);
final int height = MeasureSpec.getSize(heightSpec), width = Math.round(height * 1.25f);
setMeasuredDimension(width, height);
}
}

View File

@ -27,7 +27,7 @@
android:minHeight="@dimen/compose_min_height"
android:minWidth="@dimen/compose_min_width"
android:orientation="vertical"
android:showDividers="none">
android:showDividers="middle">
<RelativeLayout

View File

@ -1,87 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Twidere - Twitter client for Android
~
~ Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
~
~ 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 3 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, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout
android:id="@+id/compose_actionbar"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="?android:actionBarSize"
android:baselineAligned="false"
android:gravity="center_vertical"
android:orientation="horizontal">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1">
<LinearLayout
android:id="@+id/close"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="?android:actionBarItemBackground"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
android:paddingLeft="0dp"
android:paddingRight="8dp">
<org.mariotaku.twidere.view.ActionBarHomeAsUpIndicator
android:layout_width="wrap_content"
android:layout_height="match_parent"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="vertical"
android:paddingLeft="4dp">
<org.mariotaku.twidere.view.ActionBarTitleView
android:id="@+id/actionbar_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"/>
<org.mariotaku.twidere.view.ActionBarSubtitleView
android:id="@+id/actionbar_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="@string/quote_protected_status_notice"
android:visibility="gone"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
<ProgressBar
android:id="@+id/actionbar_progress_indeterminate"
style="?android:progressBarStyle"
android:layout_width="?android:actionBarSize"
android:layout_height="?android:actionBarSize"
android:layout_gravity="center_vertical"
android:layout_weight="0"
android:indeterminateOnly="true"
android:padding="2dp"
android:visibility="gone"/>
<!--<include layout="@layout/action_item_compose_account"/>-->
</LinearLayout>

View File

@ -31,6 +31,12 @@
android:orientation="horizontal"
android:showDividers="middle">
<!--<org.mariotaku.twidere.view.ComposeSelectAccountButton-->
<!--android:layout_width="wrap_content"-->
<!--android:layout_height="match_parent"-->
<!--android:layout_weight="0"-->
<!--android:padding="@dimen/element_spacing_small"/>-->
<HorizontalScrollView
android:id="@+id/bottom_menu_container"
android:layout_width="0dp"

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Twidere - Twitter client for Android
~
~ Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
~
~ 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 3 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, see <http://www.gnu.org/licenses/>.
-->
<org.mariotaku.twidere.view.SquareShapedImageView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@android:id/icon"
style="?profileImageStyle"
tools:layout_height="48dp"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:sivBorder="true"
app:sivBorderWidth="1.5dp"/>

View File

@ -21,15 +21,15 @@
<org.mariotaku.twidere.view.ColorLabelLinearLayout
android:id="@+id/content"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:activatedBackgroundIndicator"
android:descendantFocusability="blocksDescendants"
android:orientation="vertical"
android:paddingBottom="@dimen/element_spacing_small"
android:paddingLeft="@dimen/element_spacing_normal"
android:paddingRight="@dimen/element_spacing_normal"
android:paddingTop="@dimen/element_spacing_small"
android:padding="@dimen/element_spacing_normal"
app:ignorePadding="true"
tools:context=".adapter.DraftsAdapter">
<org.mariotaku.twidere.view.ImagePreviewContainer
@ -84,7 +84,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right"
android:orientation="horizontal">
android:orientation="horizontal"
android:visibility="gone">
<Button
style="?android:borderlessButtonStyle"