fixed crashes for tweets quoting yourself

This commit is contained in:
Mariotaku Lee 2015-04-12 05:16:13 +08:00
parent 5db3522433
commit 3d75fbbfd0
32 changed files with 812 additions and 321 deletions

View File

@ -151,7 +151,7 @@ public interface SharedPreferenceConstants {
String KEY_DISPLAY_PROFILE_IMAGE = "display_profile_image";
@Preference(type = BOOLEAN)
String KEY_LEFTSIDE_COMPOSE_BUTTON = "leftside_compose_button";
@Preference(type = BOOLEAN)
@Preference(type = BOOLEAN, exportable = false, hasDefault = true, defaultBoolean = false)
String KEY_ATTACH_LOCATION = "attach_location";
@Preference(type = BOOLEAN, hasDefault = true, defaultBoolean = true)
String KEY_GZIP_COMPRESSING = "gzip_compressing";

View File

@ -783,8 +783,6 @@ public interface TwidereDataStore {
String MY_RETWEET_ID = "my_retweet_id";
String MY_QUOTE_ID = "my_quote_id";
String MEDIA_LIST = "media_list";
String MENTIONS_LIST = "mentions_list";
@ -803,6 +801,7 @@ public interface TwidereDataStore {
String QUOTE_TEXT_HTML = "quote_text_html";
String QUOTE_TEXT_PLAIN = "quote_text_plain";
String QUOTE_TEXT_UNESCAPED = "quote_text_unescaped";
String QUOTE_MEDIA_JSON = "quote_media_json";
String QUOTE_TIMESTAMP = "quote_timestamp";
String QUOTE_SOURCE = "quote_source";
String QUOTED_BY_USER_ID = "quoted_by_user_id";

View File

@ -20,6 +20,7 @@
package org.mariotaku.twidere.util;
import android.content.ContentValues;
import android.support.annotation.NonNull;
import org.json.JSONException;
import org.json.JSONObject;
@ -231,7 +232,7 @@ public final class ContentValuesCreator implements TwidereConstants {
values.put(DirectMessages.SENDER_PROFILE_IMAGE_URL, message.sender_profile_image_url);
values.put(DirectMessages.RECIPIENT_PROFILE_IMAGE_URL, message.recipient_profile_image_url);
if (message.media != null) {
values.put(Statuses.MEDIA_LIST, SimpleValueSerializer.toSerializedString(message.media));
values.put(DirectMessages.MEDIA_LIST, SimpleValueSerializer.toSerializedString(message.media));
}
return values;
}
@ -302,8 +303,9 @@ public final class ContentValuesCreator implements TwidereConstants {
return resultValuesArray;
}
@NonNull
public static ContentValues createStatus(final Status orig, final long accountId) {
if (orig == null || orig.getId() <= 0) return null;
if (orig == null) throw new NullPointerException();
final ContentValues values = new ContentValues();
values.put(Statuses.ACCOUNT_ID, accountId);
values.put(Statuses.STATUS_ID, orig.getId());
@ -345,11 +347,6 @@ public final class ContentValuesCreator implements TwidereConstants {
values.put(Statuses.QUOTED_BY_USER_IS_VERIFIED, quoteUser.isVerified());
values.put(Statuses.QUOTED_BY_USER_IS_PROTECTED, quoteUser.isProtected());
values.put(Statuses.IS_QUOTE, true);
if (quotedById == accountId) {
values.put(Statuses.MY_QUOTE_ID, orig.getId());
// } else {
// values.put(Statuses.MY_QUOTE_ID, orig.getCurrentUserRetweet());
}
status = quotedStatus;
} else {
values.put(Statuses.MY_RETWEET_ID, orig.getCurrentUserRetweet());

View File

@ -3,31 +3,19 @@ package edu.tsinghua.spice;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.location.Location;
import android.location.LocationManager;
import android.os.IBinder;
import edu.tsinghua.spice.Utilies.NetworkStateUtil;
import edu.tsinghua.spice.Utilies.SpiceProfilingUtil;
/**
* Created by Denny C. Ng on 2/20/15.
*
* <p/>
* Request location ONCE per WAKE_PERIOD_IN_MILLI.
*/
public class SpiceService extends Service {
public static final long LOCATION_PERIOD_IN_MILLI = 15 * 60 * 1000;
public static final String ACTION_GET_LOCATION = "edu.tsinghua.spice.GET_LOCATION";
private LocationManager mLocationManager;
private AlarmManager mAlarmManager;
private LocationUpdateReceiver mAlarmReceiver;
private PendingIntent locationIntent;
private PendingIntent uploadIntent;
@Override
public IBinder onBind(final Intent intent) {
@ -39,51 +27,17 @@ public class SpiceService extends Service {
super.onCreate();
SpiceProfilingUtil.log(this, "onCreate");
mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
mAlarmManager = (AlarmManager) getSystemService(Service.ALARM_SERVICE);
mAlarmReceiver = new LocationUpdateReceiver();
final IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_GET_LOCATION);
registerReceiver(mAlarmReceiver, filter);
final Intent intent = new Intent(ACTION_GET_LOCATION);
locationIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), LOCATION_PERIOD_IN_MILLI,
locationIntent);
// Upload Service
final Intent i = new Intent(SpiceUploadReceiver.ACTION_UPLOAD_PROFILE);
uploadIntent = PendingIntent.getBroadcast(this, 0, i, 0);
mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), 12 * 60 * 60 * 1000,
uploadIntent);
final Intent uploadIntent = new Intent(SpiceUploadReceiver.ACTION_UPLOAD_PROFILE);
mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(),
12 * 60 * 60 * 1000, PendingIntent.getBroadcast(this, 0, uploadIntent, 0));
}
@Override
public void onDestroy() {
mAlarmManager.cancel(locationIntent);
unregisterReceiver(mAlarmReceiver);
super.onDestroy();
}
private final class LocationUpdateReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, final Intent intent) {
if (mLocationManager == null) return;
SpiceProfilingUtil.log(context, "AlarmReceiver");
final String provider = LocationManager.NETWORK_PROVIDER;
if (mLocationManager.isProviderEnabled(provider)) {
final Location location = mLocationManager.getLastKnownLocation(provider);
if (location != null) {
SpiceProfilingUtil.profile(SpiceService.this, SpiceProfilingUtil.FILE_NAME_LOCATION, location.getTime() + ","
+ location.getLatitude() + "," + location.getLongitude() + "," + location.getProvider());
SpiceProfilingUtil.log(context,
location.getTime() + "," + location.getLatitude() + "," + location.getLongitude() + ","
+ location.getProvider());
SpiceProfilingUtil.profile(SpiceService.this, SpiceProfilingUtil.FILE_NAME_NETWORK, NetworkStateUtil.getConnectedType(SpiceService.this));
}
}
}
}
}

View File

@ -33,7 +33,7 @@ import static org.mariotaku.twidere.annotation.Preference.Type.STRING;
public interface Constants extends TwidereConstants {
String DATABASES_NAME = "twidere.sqlite";
int DATABASES_VERSION = 96;
int DATABASES_VERSION = 97;
int MENU_GROUP_STATUS_EXTENSION = 10;
int MENU_GROUP_COMPOSE_EXTENSION = 11;

View File

@ -90,6 +90,7 @@ import com.twitter.Extractor;
import org.mariotaku.dynamicgridview.DraggableArrayAdapter;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.app.TwidereApplication;
import org.mariotaku.twidere.constant.SharedPreferenceConstants;
import org.mariotaku.twidere.fragment.support.BaseSupportDialogFragment;
import org.mariotaku.twidere.fragment.support.ViewStatusDialogFragment;
import org.mariotaku.twidere.model.DraftItem;
@ -108,6 +109,7 @@ import org.mariotaku.twidere.util.AsyncTwitterWrapper;
import org.mariotaku.twidere.util.ContentValuesCreator;
import org.mariotaku.twidere.util.MathUtils;
import org.mariotaku.twidere.util.MediaLoaderWrapper;
import org.mariotaku.twidere.util.MenuUtils;
import org.mariotaku.twidere.util.ParseUtils;
import org.mariotaku.twidere.util.SharedPreferencesWrapper;
import org.mariotaku.twidere.util.ThemeUtils;
@ -271,97 +273,6 @@ public class ComposeActivity extends ThemedFragmentActivity implements TextWatch
super.onSaveInstanceState(outState);
}
public boolean handleMenuItem(final MenuItem item) {
switch (item.getItemId()) {
case MENU_TAKE_PHOTO:
case R.id.take_photo_sub_item: {
takePhoto();
break;
}
case MENU_ADD_IMAGE:
case R.id.add_image_sub_item: {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT || !openDocument()) {
pickImage();
}
break;
}
case MENU_ADD_LOCATION: {
toggleLocation();
break;
}
case MENU_DRAFTS: {
startActivity(new Intent(INTENT_ACTION_DRAFTS));
break;
}
case MENU_DELETE: {
AsyncTaskUtils.executeTask(new DeleteImageTask(this));
break;
}
case MENU_TOGGLE_SENSITIVE: {
if (!hasMedia()) return false;
mIsPossiblySensitive = !mIsPossiblySensitive;
setMenu();
updateTextCount();
break;
}
case MENU_VIEW: {
if (mInReplyToStatus == null) return false;
final DialogFragment fragment = new ViewStatusDialogFragment();
final Bundle args = new Bundle();
args.putParcelable(EXTRA_STATUS, mInReplyToStatus);
fragment.setArguments(args);
fragment.show(getSupportFragmentManager(), "view_status");
break;
}
default: {
final Intent intent = item.getIntent();
if (intent != null) {
try {
final String action = intent.getAction();
if (INTENT_ACTION_EXTENSION_COMPOSE.equals(action)) {
final long[] accountIds = mAccountsAdapter.getSelectedAccountIds();
intent.putExtra(EXTRA_TEXT, ParseUtils.parseString(mEditText.getText()));
intent.putExtra(EXTRA_ACCOUNT_IDS, accountIds);
if (accountIds.length > 0) {
final long account_id = accountIds[0];
intent.putExtra(EXTRA_NAME, getAccountName(this, account_id));
intent.putExtra(EXTRA_SCREEN_NAME, getAccountScreenName(this, account_id));
}
if (mInReplyToStatusId > 0) {
intent.putExtra(EXTRA_IN_REPLY_TO_ID, mInReplyToStatusId);
}
if (mInReplyToStatus != null) {
intent.putExtra(EXTRA_IN_REPLY_TO_NAME, mInReplyToStatus.user_name);
intent.putExtra(EXTRA_IN_REPLY_TO_SCREEN_NAME, mInReplyToStatus.user_screen_name);
}
startActivityForResult(intent, REQUEST_EXTENSION_COMPOSE);
} else if (INTENT_ACTION_EXTENSION_EDIT_IMAGE.equals(action)) {
// final ComponentName cmp = intent.getComponent();
// if (cmp == null || !hasMedia()) return false;
// final String name = new
// File(mMediaUri.getPath()).getName();
// final Uri data =
// Uri.withAppendedPath(CacheFiles.CONTENT_URI,
// Uri.encode(name));
// intent.setData(data);
// grantUriPermission(cmp.getPackageName(), data,
// Intent.FLAG_GRANT_READ_URI_PERMISSION);
// startActivityForResult(intent,
// REQUEST_EDIT_IMAGE);
} else {
startActivity(intent);
}
} catch (final ActivityNotFoundException e) {
Log.w(LOGTAG, e);
return false;
}
}
break;
}
}
return true;
}
private void toggleLocation() {
final boolean attachLocation = mPreferences.getBoolean(KEY_ATTACH_LOCATION, false);
@ -556,7 +467,100 @@ public class ComposeActivity extends ThemedFragmentActivity implements TextWatch
@Override
public boolean onMenuItemClick(final MenuItem item) {
return handleMenuItem(item);
switch (item.getItemId()) {
case MENU_TAKE_PHOTO:
case R.id.take_photo_sub_item: {
takePhoto();
break;
}
case MENU_ADD_IMAGE:
case R.id.add_image_sub_item: {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT || !openDocument()) {
pickImage();
}
break;
}
case MENU_ADD_LOCATION: {
toggleLocation();
break;
}
case MENU_DRAFTS: {
startActivity(new Intent(INTENT_ACTION_DRAFTS));
break;
}
case MENU_DELETE: {
AsyncTaskUtils.executeTask(new DeleteImageTask(this));
break;
}
case MENU_TOGGLE_SENSITIVE: {
if (!hasMedia()) return false;
mIsPossiblySensitive = !mIsPossiblySensitive;
setMenu();
updateTextCount();
break;
}
case MENU_VIEW: {
if (mInReplyToStatus == null) return false;
final DialogFragment fragment = new ViewStatusDialogFragment();
final Bundle args = new Bundle();
args.putParcelable(EXTRA_STATUS, mInReplyToStatus);
fragment.setArguments(args);
fragment.show(getSupportFragmentManager(), "view_status");
break;
}
case R.id.link_to_quoted_status: {
final boolean newValue = !item.isChecked();
item.setChecked(newValue);
mPreferences.edit().putBoolean(KEY_LINK_TO_QUOTED_TWEET, newValue).apply();
break;
}
default: {
final Intent intent = item.getIntent();
if (intent != null) {
try {
final String action = intent.getAction();
if (INTENT_ACTION_EXTENSION_COMPOSE.equals(action)) {
final long[] accountIds = mAccountsAdapter.getSelectedAccountIds();
intent.putExtra(EXTRA_TEXT, ParseUtils.parseString(mEditText.getText()));
intent.putExtra(EXTRA_ACCOUNT_IDS, accountIds);
if (accountIds.length > 0) {
final long account_id = accountIds[0];
intent.putExtra(EXTRA_NAME, getAccountName(this, account_id));
intent.putExtra(EXTRA_SCREEN_NAME, getAccountScreenName(this, account_id));
}
if (mInReplyToStatusId > 0) {
intent.putExtra(EXTRA_IN_REPLY_TO_ID, mInReplyToStatusId);
}
if (mInReplyToStatus != null) {
intent.putExtra(EXTRA_IN_REPLY_TO_NAME, mInReplyToStatus.user_name);
intent.putExtra(EXTRA_IN_REPLY_TO_SCREEN_NAME, mInReplyToStatus.user_screen_name);
}
startActivityForResult(intent, REQUEST_EXTENSION_COMPOSE);
} else if (INTENT_ACTION_EXTENSION_EDIT_IMAGE.equals(action)) {
// final ComponentName cmp = intent.getComponent();
// if (cmp == null || !hasMedia()) return false;
// final String name = new
// File(mMediaUri.getPath()).getName();
// final Uri data =
// Uri.withAppendedPath(CacheFiles.CONTENT_URI,
// Uri.encode(name));
// intent.setData(data);
// grantUriPermission(cmp.getPackageName(), data,
// Intent.FLAG_GRANT_READ_URI_PERMISSION);
// startActivityForResult(intent,
// REQUEST_EDIT_IMAGE);
} else {
startActivity(intent);
}
} catch (final ActivityNotFoundException e) {
Log.w(LOGTAG, e);
return false;
}
}
break;
}
}
return true;
}
@Override
@ -618,7 +622,8 @@ public class ComposeActivity extends ThemedFragmentActivity implements TextWatch
// requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
super.onCreate(savedInstanceState);
mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
mPreferences = SharedPreferencesWrapper.getInstance(this, SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
mPreferences = SharedPreferencesWrapper.getInstance(this, SHARED_PREFERENCES_NAME,
Context.MODE_PRIVATE, SharedPreferenceConstants.class);
final TwidereApplication app = TwidereApplication.getInstance(this);
mTwitterWrapper = app.getTwitterWrapper();
@ -638,7 +643,7 @@ public class ComposeActivity extends ThemedFragmentActivity implements TextWatch
}
// mMenuBar.setIsBottomBar(true);
mMenuBar.setOnMenuItemClickListener(this);
mEditText.setOnEditorActionListener(mPreferences.getBoolean(KEY_QUICK_SEND, false) ? this : null);
mEditText.setOnEditorActionListener(mPreferences.getBoolean(KEY_QUICK_SEND) ? this : null);
mEditText.addTextChangedListener(this);
mEditText.setCustomSelectionActionModeCallback(this);
mAccountSelectorContainer.setOnClickListener(this);
@ -794,7 +799,7 @@ public class ComposeActivity extends ThemedFragmentActivity implements TextWatch
*/
private boolean startLocationUpdateIfEnabled() {
final LocationManager lm = mLocationManager;
final boolean attachLocation = mPreferences.getBoolean(KEY_ATTACH_LOCATION, false);
final boolean attachLocation = mPreferences.getBoolean(KEY_ATTACH_LOCATION);
if (!attachLocation) {
lm.removeUpdates(this);
return false;
@ -986,9 +991,8 @@ public class ComposeActivity extends ThemedFragmentActivity implements TextWatch
}
private boolean isQuotingProtectedStatus() {
if (INTENT_ACTION_QUOTE.equals(getIntent().getAction()) && mInReplyToStatus != null)
return mInReplyToStatus.user_is_protected && mInReplyToStatus.account_id != mInReplyToStatus.user_id;
return false;
if (!isQuote() || mInReplyToStatus == null) return false;
return mInReplyToStatus.user_is_protected && mInReplyToStatus.account_id != mInReplyToStatus.user_id;
}
private boolean noReplyContent(final String text) {
@ -1076,20 +1080,19 @@ public class ComposeActivity extends ThemedFragmentActivity implements TextWatch
* Has media & Not reply: [Take photo][Media menu][Attach location][Drafts]
* Is reply: [Media menu][View status][Attach location][Drafts]
*/
Utils.setMenuItemAvailability(menu, MENU_TAKE_PHOTO, !hasInReplyTo);
Utils.setMenuItemAvailability(menu, R.id.take_photo_sub_item, hasInReplyTo);
Utils.setMenuItemAvailability(menu, MENU_ADD_IMAGE, !hasMedia && !hasInReplyTo);
Utils.setMenuItemAvailability(menu, MENU_VIEW, hasInReplyTo);
Utils.setMenuItemAvailability(menu, R.id.media_menu, hasMedia || hasInReplyTo);
Utils.setMenuItemAvailability(menu, MENU_TOGGLE_SENSITIVE, hasMedia);
Utils.setMenuItemAvailability(menu, MENU_EDIT_MEDIA, hasMedia);
MenuUtils.setMenuItemAvailability(menu, MENU_TAKE_PHOTO, !hasInReplyTo);
MenuUtils.setMenuItemAvailability(menu, R.id.take_photo_sub_item, hasInReplyTo);
MenuUtils.setMenuItemAvailability(menu, MENU_ADD_IMAGE, !hasMedia && !hasInReplyTo);
MenuUtils.setMenuItemAvailability(menu, MENU_VIEW, hasInReplyTo);
MenuUtils.setMenuItemAvailability(menu, R.id.media_menu, hasMedia || hasInReplyTo);
MenuUtils.setMenuItemAvailability(menu, MENU_TOGGLE_SENSITIVE, hasMedia);
MenuUtils.setMenuItemAvailability(menu, MENU_EDIT_MEDIA, hasMedia);
MenuUtils.setMenuItemAvailability(menu, R.id.link_to_quoted_status, isQuote());
menu.setGroupEnabled(MENU_GROUP_IMAGE_EXTENSION, hasMedia);
menu.setGroupVisible(MENU_GROUP_IMAGE_EXTENSION, hasMedia);
final MenuItem itemToggleSensitive = menu.findItem(MENU_TOGGLE_SENSITIVE);
if (itemToggleSensitive != null) {
itemToggleSensitive.setChecked(hasMedia && mIsPossiblySensitive);
}
MenuUtils.setMenuItemChecked(menu, MENU_TOGGLE_SENSITIVE, hasMedia && mIsPossiblySensitive);
MenuUtils.setMenuItemChecked(menu, R.id.link_to_quoted_status, mPreferences.getBoolean(KEY_LINK_TO_QUOTED_TWEET));
ThemeUtils.resetCheatSheet(mMenuBar);
// mMenuBar.show();
}
@ -1152,7 +1155,7 @@ public class ComposeActivity extends ThemedFragmentActivity implements TextWatch
// setRecentLocation();
// }
final long[] accountIds = mAccountsAdapter.getSelectedAccountIds();
final boolean isQuote = INTENT_ACTION_QUOTE.equals(getIntent().getAction());
final boolean isQuote = isQuote();
final ParcelableLocation statusLocation = attachLocation ? mRecentLocation : null;
final boolean linkToQuotedTweet = mPreferences.getBoolean(KEY_LINK_TO_QUOTED_TWEET, true);
final long inReplyToStatusId = !isQuote || linkToQuotedTweet ? mInReplyToStatusId : -1;
@ -1183,6 +1186,10 @@ public class ComposeActivity extends ThemedFragmentActivity implements TextWatch
}
}
private boolean isQuote() {
return INTENT_ACTION_QUOTE.equals(getIntent().getAction());
}
private void updateTextCount() {
if (mSendTextCountView == null || mEditText == null) return;
final String textOrig = parseString(mEditText.getText());

View File

@ -66,6 +66,7 @@ import org.mariotaku.twidere.loader.support.TileImageLoader.Result;
import org.mariotaku.twidere.model.ParcelableMedia;
import org.mariotaku.twidere.model.ParcelableMedia.VideoInfo.Variant;
import org.mariotaku.twidere.model.ParcelableStatus;
import org.mariotaku.twidere.util.MenuUtils;
import org.mariotaku.twidere.util.SaveImageTask;
import org.mariotaku.twidere.util.ThemeUtils;
import org.mariotaku.twidere.util.Utils;
@ -370,6 +371,7 @@ public final class MediaViewerActivity extends ThemedActionBarActivity implement
@Override
public void onClick(View v) {
final MediaViewerActivity activity = (MediaViewerActivity) getActivity();
if (activity == null) return;
activity.toggleBar();
}
@ -440,9 +442,9 @@ public final class MediaViewerActivity extends ThemedActionBarActivity implement
final Object imageTag = mImageView.getTag();
final boolean isLoading = getLoaderManager().hasRunningLoaders();
final boolean hasImage = imageTag instanceof File;
Utils.setMenuItemAvailability(menu, R.id.refresh, !hasImage && !isLoading);
Utils.setMenuItemAvailability(menu, R.id.share, hasImage && !isLoading);
Utils.setMenuItemAvailability(menu, R.id.save, hasImage && !isLoading);
MenuUtils.setMenuItemAvailability(menu, R.id.refresh, !hasImage && !isLoading);
MenuUtils.setMenuItemAvailability(menu, R.id.share, hasImage && !isLoading);
MenuUtils.setMenuItemAvailability(menu, R.id.save, hasImage && !isLoading);
if (hasImage) {
final MenuItem shareItem = menu.findItem(R.id.share);
final ShareActionProvider shareProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(shareItem);

View File

@ -377,7 +377,8 @@ public class SignInActivity extends BaseActionBarActivity implements TwitterCons
cb.setOAuthBaseURL(Utils.getApiUrl(apiUrlFormat, "api", "/oauth/"));
cb.setUploadBaseURL(Utils.getApiUrl(apiUrlFormat, "upload", versionSuffix));
cb.setOAuthAuthorizationURL(Utils.getApiUrl(apiUrlFormat, null, "/oauth/authorize"));
cb.setHttpUserAgent(Utils.generateBrowserUserAgent());
final String userAgent = TwidereApplication.getInstance(this).getDefaultUserAgent();
cb.setHttpUserAgent(userAgent);
if (!mSameOAuthSigningUrl) {
cb.setSigningRestBaseURL(DEFAULT_SIGNING_REST_BASE_URL);
cb.setSigningOAuthBaseURL(DEFAULT_SIGNING_OAUTH_BASE_URL);

View File

@ -34,7 +34,6 @@ import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.adapter.iface.IContentCardAdapter;
import org.mariotaku.twidere.app.TwidereApplication;
import org.mariotaku.twidere.fragment.support.DirectMessagesFragment;
import org.mariotaku.twidere.model.StringLongPair;
import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages.ConversationEntries;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
@ -90,6 +89,10 @@ public class MessageEntriesAdapter extends Adapter<ViewHolder> implements Consta
};
}
public void onUserProfileClick(int position) {
mListener.onUserClick(position, getEntry(position));
}
public void updateReadState() {
mPositionPairs = mReadStateManager.getPositionPairs(TAB_TYPE_DIRECT_MESSAGES);
notifyDataSetChanged();
@ -284,7 +287,9 @@ public class MessageEntriesAdapter extends Adapter<ViewHolder> implements Consta
}
public interface MessageEntriesAdapterListener {
public void onEntryClick(int position, DirectMessageEntry entry);
void onEntryClick(int position, DirectMessageEntry entry);
void onUserClick(int position, DirectMessageEntry entry);
}
public static class DirectMessageEntry {

View File

@ -54,6 +54,7 @@ import org.mariotaku.twidere.util.MessagesManager;
import org.mariotaku.twidere.util.MultiSelectManager;
import org.mariotaku.twidere.util.ReadStateManager;
import org.mariotaku.twidere.util.StrictModeUtils;
import org.mariotaku.twidere.util.UserAgentUtils;
import org.mariotaku.twidere.util.Utils;
import org.mariotaku.twidere.util.VideoLoader;
import org.mariotaku.twidere.util.content.TwidereSQLiteOpenHelper;
@ -97,11 +98,17 @@ public class TwidereApplication extends MultiDexApplication implements Constants
private VideoLoader mVideoLoader;
private ReadStateManager mReadStateManager;
private String mDefaultUserAgent;
public AsyncTaskManager getAsyncTaskManager() {
if (mAsyncTaskManager != null) return mAsyncTaskManager;
return mAsyncTaskManager = AsyncTaskManager.getInstance();
}
public String getDefaultUserAgent() {
return mDefaultUserAgent;
}
public DiskCache getDiskCache() {
if (mDiskCache != null) return mDiskCache;
return mDiskCache = createDiskCache(DIR_NAME_IMAGE_CACHE);
@ -204,6 +211,7 @@ public class TwidereApplication extends MultiDexApplication implements Constants
StrictModeUtils.detectAllVmPolicy();
}
super.onCreate();
mDefaultUserAgent = UserAgentUtils.getDefaultUserAgentString(this);
mHandler = new Handler();
mMessageBus = new Bus();
mPreferences = getSharedPreferences(SHARED_PREFERENCES_NAME, MODE_PRIVATE);
@ -234,6 +242,8 @@ public class TwidereApplication extends MultiDexApplication implements Constants
migrateUsageStatisticsPreferences();
startUsageStatisticsServiceIfNeeded(this);
startRefreshServiceIfNeeded(this);
reloadConnectivitySettings();
}
private void migrateUsageStatisticsPreferences() {

View File

@ -39,8 +39,8 @@ import org.mariotaku.twidere.R;
import org.mariotaku.twidere.adapter.ExtensionsAdapter;
import org.mariotaku.twidere.loader.ExtensionsListLoader;
import org.mariotaku.twidere.loader.ExtensionsListLoader.ExtensionInfo;
import org.mariotaku.twidere.util.MenuUtils;
import org.mariotaku.twidere.util.PermissionsManager;
import org.mariotaku.twidere.util.Utils;
import java.util.List;
@ -104,9 +104,9 @@ public class ExtensionsListFragment extends BaseListFragment implements Constant
if (extensionInfo.pname != null && extensionInfo.settings != null) {
final Intent intent = new Intent(INTENT_ACTION_EXTENSION_SETTINGS);
intent.setClassName(extensionInfo.pname, extensionInfo.settings);
Utils.setMenuItemAvailability(menu, MENU_SETTINGS, mPackageManager.queryIntentActivities(intent, 0).size() == 1);
MenuUtils.setMenuItemAvailability(menu, MENU_SETTINGS, mPackageManager.queryIntentActivities(intent, 0).size() == 1);
} else {
Utils.setMenuItemAvailability(menu, MENU_SETTINGS, false);
MenuUtils.setMenuItemAvailability(menu, MENU_SETTINGS, false);
}
}

View File

@ -66,6 +66,32 @@ public class HostMappingsListFragment extends BaseListFragment implements MultiC
private HostMappingAdapter mAdapter;
private SharedPreferences mPreferences;
@Override
public void onActivityCreated(final Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(true);
mPreferences = getSharedPreferences(HOST_MAPPING_PREFERENCES_NAME, Context.MODE_PRIVATE);
mPreferences.registerOnSharedPreferenceChangeListener(this);
mAdapter = new HostMappingAdapter(getActivity());
setListAdapter(mAdapter);
mListView = getListView();
mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
mListView.setMultiChoiceModeListener(this);
reloadHostMappings();
}
@Override
public boolean onCreateActionMode(final ActionMode mode, final Menu menu) {
mode.getMenuInflater().inflate(R.menu.action_multi_select_items, menu);
return true;
}
@Override
public boolean onPrepareActionMode(final ActionMode mode, final Menu menu) {
updateTitle(mode);
return true;
}
@Override
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
switch (item.getItemId()) {
@ -90,40 +116,14 @@ public class HostMappingsListFragment extends BaseListFragment implements MultiC
return true;
}
@Override
public void onActivityCreated(final Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(true);
mPreferences = getSharedPreferences(HOST_MAPPING_PREFERENCES_NAME, Context.MODE_PRIVATE);
mPreferences.registerOnSharedPreferenceChangeListener(this);
mAdapter = new HostMappingAdapter(getActivity());
setListAdapter(mAdapter);
mListView = getListView();
mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
mListView.setMultiChoiceModeListener(this);
reloadHostMappings();
}
@Override
public boolean onCreateActionMode(final ActionMode mode, final Menu menu) {
mode.getMenuInflater().inflate(R.menu.action_multi_select_items, menu);
return true;
}
@Override
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
inflater.inflate(R.menu.menu_host_mapping, menu);
}
@Override
public void onDestroyActionMode(final ActionMode mode) {
}
@Override
public void onItemCheckedStateChanged(final ActionMode mode, final int position, final long id,
final boolean checked) {
updateTitle(mode);
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
inflater.inflate(R.menu.menu_host_mapping, menu);
}
@Override
@ -138,9 +138,9 @@ public class HostMappingsListFragment extends BaseListFragment implements MultiC
}
@Override
public boolean onPrepareActionMode(final ActionMode mode, final Menu menu) {
public void onItemCheckedStateChanged(final ActionMode mode, final int position, final long id,
final boolean checked) {
updateTitle(mode);
return true;
}
@Override
@ -169,14 +169,25 @@ public class HostMappingsListFragment extends BaseListFragment implements MultiC
private EditText mEditHost, mEditAddress;
private CheckBox mCheckExclude;
@Override
public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
}
@Override
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
updateButton();
}
@Override
public void afterTextChanged(final Editable s) {
}
@Override
public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
updateAddressField();
updateButton();
}
@Override
@ -237,9 +248,8 @@ public class HostMappingsListFragment extends BaseListFragment implements MultiC
updateButton();
}
@Override
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
updateButton();
private void updateAddressField() {
mEditAddress.setVisibility(mCheckExclude.isChecked() ? View.GONE : View.VISIBLE);
}
private void updateButton() {
@ -250,16 +260,6 @@ public class HostMappingsListFragment extends BaseListFragment implements MultiC
final Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
positiveButton.setEnabled(hostValid && addressValid);
}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
updateAddressField();
updateButton();
}
private void updateAddressField() {
mEditAddress.setVisibility(mCheckExclude.isChecked() ? View.GONE : View.VISIBLE);
}
}
static class HostMappingAdapter extends ArrayAdapter<String> {

View File

@ -19,8 +19,205 @@
package org.mariotaku.twidere.fragment;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.AsyncTaskLoader;
import android.content.Context;
import android.content.Loader;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Bundle;
import android.util.SparseBooleanArray;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.ListView;
import android.widget.TextView;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.adapter.ArrayAdapter;
import org.mariotaku.twidere.model.KeyboardShortcutSpec;
import org.mariotaku.twidere.util.ParseUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
/**
* Created by mariotaku on 15/4/10.
*/
public class KeyboardShortcutsFragment extends BaseListFragment {
public class KeyboardShortcutsFragment extends BaseListFragment implements LoaderCallbacks<List<KeyboardShortcutSpec>>, MultiChoiceModeListener {
private Adapter mAdapter;
private ListView mListView;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(true);
mAdapter = new Adapter(getActivity());
setListAdapter(mAdapter);
mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
mListView.setMultiChoiceModeListener(this);
getLoaderManager().initLoader(0, null, this);
setListShown(false);
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
mode.getMenuInflater().inflate(R.menu.action_multi_select_items, menu);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
updateTitle(mode);
return true;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case MENU_DELETE: {
final SharedPreferences.Editor editor = getSharedPreferences(KEYBOARD_SHORTCUTS_PREFERENCES_NAME, Context.MODE_PRIVATE).edit();
final SparseBooleanArray array = mListView.getCheckedItemPositions();
if (array == null) return false;
for (int i = 0, size = array.size(); i < size; i++) {
if (array.valueAt(i)) {
editor.remove(mAdapter.getItem(i).getRawKey());
}
}
editor.apply();
break;
}
default: {
return false;
}
}
mode.finish();
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
}
@Override
public Loader<List<KeyboardShortcutSpec>> onCreateLoader(int id, Bundle args) {
return new KeyboardShortcutSpecsLoader(getActivity());
}
@Override
public void onLoadFinished(Loader<List<KeyboardShortcutSpec>> loader, List<KeyboardShortcutSpec> data) {
mAdapter.clear();
mAdapter.addAll(data);
setListShown(true);
}
@Override
public void onLoaderReset(Loader<List<KeyboardShortcutSpec>> loader) {
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.menu_keyboard_shortcuts, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case MENU_ADD: {
final SharedPreferences preferences = getSharedPreferences(KEYBOARD_SHORTCUTS_PREFERENCES_NAME, Context.MODE_PRIVATE);
final Editor editor = preferences.edit();
editor.putString("ctrl+m", "compose");
editor.apply();
return true;
}
}
return super.onOptionsItemSelected(item);
}
@Override
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mListView = getListView();
}
private void updateTitle(final ActionMode mode) {
if (mListView == null || mode == null || getActivity() == null) return;
final int count = mListView.getCheckedItemCount();
mode.setTitle(getResources().getQuantityString(R.plurals.Nitems_selected, count, count));
}
private static class Adapter extends ArrayAdapter<KeyboardShortcutSpec> {
public Adapter(Context context) {
super(context, android.R.layout.simple_list_item_activated_2);
}
@Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
final View view = super.getView(position, convertView, parent);
final TextView text1 = (TextView) view.findViewById(android.R.id.text1);
final TextView text2 = (TextView) view.findViewById(android.R.id.text2);
final KeyboardShortcutSpec spec = getItem(position);
text1.setText(spec.getValueName(getContext()));
text2.setText(spec.toKeyString());
return view;
}
}
private static class KeyboardShortcutSpecsLoader extends AsyncTaskLoader<List<KeyboardShortcutSpec>> {
private final SharedPreferences preferences;
private final OnSharedPreferenceChangeListener changeListener = new OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
forceLoad();
}
};
public KeyboardShortcutSpecsLoader(Context context) {
super(context);
preferences = context.getSharedPreferences(KEYBOARD_SHORTCUTS_PREFERENCES_NAME, Context.MODE_PRIVATE);
preferences.registerOnSharedPreferenceChangeListener(changeListener);
}
@Override
public List<KeyboardShortcutSpec> loadInBackground() {
final ArrayList<KeyboardShortcutSpec> list = new ArrayList<>();
for (Entry<String, ?> entry : preferences.getAll().entrySet()) {
final KeyboardShortcutSpec spec = new KeyboardShortcutSpec(entry.getKey(), ParseUtils.parseString(entry.getValue()));
if (spec.isValid()) {
list.add(spec);
}
}
return list;
}
@Override
protected void onStartLoading() {
forceLoad();
}
@Override
protected void onReset() {
if (isAbandoned()) {
preferences.unregisterOnSharedPreferenceChangeListener(changeListener);
}
super.onReset();
}
}
}

View File

@ -509,7 +509,7 @@ public abstract class AbsStatusesFragment<Data> extends BaseSupportFragment impl
final int swipeStart = (mSystemWindowsInsets.top - mControlBarOffsetPixels) - progressCircleDiameter;
// 64: SwipeRefreshLayout.DEFAULT_CIRCLE_TARGET
final int swipeDistance = Math.round(64 * density);
mSwipeRefreshLayout.setProgressViewOffset(true, swipeStart, swipeStart + swipeDistance);
mSwipeRefreshLayout.setProgressViewOffset(false, swipeStart, swipeStart + swipeDistance);
}
protected final class StatusesBusCallback {

View File

@ -75,7 +75,6 @@ import org.mariotaku.twidere.util.ThemeUtils;
import org.mariotaku.twidere.util.Utils;
import org.mariotaku.twidere.util.content.SupportFragmentReloadCursorObserver;
import org.mariotaku.twidere.util.message.GetMessagesTaskEvent;
import org.mariotaku.twidere.util.message.TaskStateChangedEvent;
import java.util.Collections;
import java.util.HashSet;
@ -157,27 +156,14 @@ public class DirectMessagesFragment extends BaseSupportFragment implements Loade
Utils.openMessageConversation(getActivity(), entry.account_id, entry.conversation_id);
}
@Override
public void onUserClick(int position, DirectMessageEntry entry) {
Utils.openUserProfile(getActivity(), entry.account_id, entry.conversation_id, entry.screen_name, null);
}
@Override
public void onRefresh() {
AsyncTaskUtils.executeTask(new AsyncTask<Object, Object, long[][]>() {
@Override
protected long[][] doInBackground(final Object... params) {
final long[][] result = new long[2][];
result[0] = Utils.getActivatedAccountIds(getActivity());
result[1] = Utils.getNewestMessageIdsFromDatabase(getActivity(), DirectMessages.Inbox.CONTENT_URI);
return result;
}
@Override
protected void onPostExecute(final long[][] result) {
final AsyncTwitterWrapper twitter = getTwitterWrapper();
if (twitter == null) return;
twitter.getReceivedDirectMessagesAsync(result[0], null, result[1]);
twitter.getSentDirectMessagesAsync(result[0], null, null);
}
});
triggerRefresh();
}
private void setListShown(boolean shown) {
@ -315,6 +301,25 @@ public class DirectMessagesFragment extends BaseSupportFragment implements Loade
@Override
public boolean triggerRefresh() {
AsyncTaskUtils.executeTask(new AsyncTask<Object, Object, long[][]>() {
@Override
protected long[][] doInBackground(final Object... params) {
final long[][] result = new long[2][];
result[0] = Utils.getActivatedAccountIds(getActivity());
result[1] = Utils.getNewestMessageIdsFromDatabase(getActivity(), DirectMessages.Inbox.CONTENT_URI);
return result;
}
@Override
protected void onPostExecute(final long[][] result) {
final AsyncTwitterWrapper twitter = getTwitterWrapper();
if (twitter == null) return;
twitter.getReceivedDirectMessagesAsync(result[0], null, result[1]);
twitter.getSentDirectMessagesAsync(result[0], null, null);
}
});
return true;
}

View File

@ -78,7 +78,7 @@ public class RetweetQuoteDialogFragment extends BaseSupportDialogFragment implem
final AlertDialog.Builder builder = new AlertDialog.Builder(wrapped);
final Context context = builder.getContext();
final LayoutInflater inflater = LayoutInflater.from(context);
@SuppressLint("InflateParams") final View view = inflater.inflate(R.layout.dialog_scrollable_status, null);
@SuppressLint("InflateParams") final View view = inflater.inflate(R.layout.dialog_status_quote_retweet, null);
final StatusViewHolder holder = new StatusViewHolder(new DummyStatusHolderAdapter(context), view.findViewById(R.id.item_content));
final ParcelableStatus status = getStatus();

View File

@ -100,6 +100,7 @@ import org.mariotaku.twidere.util.CompareUtils;
import org.mariotaku.twidere.util.ImageLoadingHandler;
import org.mariotaku.twidere.util.LinkCreator;
import org.mariotaku.twidere.util.MediaLoaderWrapper;
import org.mariotaku.twidere.util.MenuUtils;
import org.mariotaku.twidere.util.SharedPreferencesWrapper;
import org.mariotaku.twidere.util.StatusAdapterLinkClickHandler;
import org.mariotaku.twidere.util.StatusLinkClickHandler;
@ -792,8 +793,8 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
}
case R.id.retweeted_by_container: {
if (status.retweet_id > 0) {
Utils.openUserProfile(adapter.getContext(), status.account_id, status.user_id,
status.user_screen_name, null);
Utils.openUserProfile(adapter.getContext(), status.account_id, status.retweeted_by_id,
status.retweeted_by_screen_name, null);
}
break;
}
@ -884,7 +885,7 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
final SpannableString string = SpannableString.valueOf(textView.getText());
final URLSpan[] spans = string.getSpans(start, end, URLSpan.class);
final boolean avail = spans.length == 1 && URLUtil.isValidUrl(spans[0].getURL());
Utils.setMenuItemAvailability(menu, android.R.id.copyUrl, avail);
MenuUtils.setMenuItemAvailability(menu, android.R.id.copyUrl, avail);
return true;
}

View File

@ -109,6 +109,7 @@ import org.mariotaku.twidere.util.ContentValuesCreator;
import org.mariotaku.twidere.util.LinkCreator;
import org.mariotaku.twidere.util.MathUtils;
import org.mariotaku.twidere.util.MediaLoaderWrapper;
import org.mariotaku.twidere.util.MenuUtils;
import org.mariotaku.twidere.util.ParseUtils;
import org.mariotaku.twidere.util.ThemeUtils;
import org.mariotaku.twidere.util.TwidereLinkify;
@ -139,6 +140,7 @@ import twitter4j.Twitter;
import twitter4j.TwitterException;
import static android.text.TextUtils.isEmpty;
import static org.mariotaku.twidere.util.MenuUtils.setMenuItemAvailability;
import static org.mariotaku.twidere.util.ParseUtils.parseLong;
import static org.mariotaku.twidere.util.UserColorNameUtils.clearUserColor;
import static org.mariotaku.twidere.util.UserColorNameUtils.clearUserNickname;
@ -162,7 +164,6 @@ import static org.mariotaku.twidere.util.Utils.openUserFollowers;
import static org.mariotaku.twidere.util.Utils.openUserFriends;
import static org.mariotaku.twidere.util.Utils.openUserLists;
import static org.mariotaku.twidere.util.Utils.openUserProfile;
import static org.mariotaku.twidere.util.Utils.setMenuItemAvailability;
import static org.mariotaku.twidere.util.Utils.showInfoMessage;
public class UserFragment extends BaseSupportFragment implements OnClickListener,
@ -794,8 +795,8 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
if (mentionItem != null) {
mentionItem.setTitle(getString(R.string.mention_user_name, UserColorNameUtils.getDisplayName(getActivity(), user)));
}
Utils.setMenuItemAvailability(menu, MENU_MENTION, !isMyself);
Utils.setMenuItemAvailability(menu, R.id.incoming_friendships, isMyself);
MenuUtils.setMenuItemAvailability(menu, MENU_MENTION, !isMyself);
MenuUtils.setMenuItemAvailability(menu, R.id.incoming_friendships, isMyself);
// final MenuItem followItem = menu.findItem(MENU_FOLLOW);
// followItem.setVisible(!isMyself);
// final boolean shouldShowFollowItem = !creatingFriendship && !destroyingFriendship && !isMyself

View File

@ -93,7 +93,7 @@ import static org.mariotaku.twidere.util.Utils.getAccountColor;
import static org.mariotaku.twidere.util.Utils.getTwitterInstance;
import static org.mariotaku.twidere.util.Utils.openUserListDetails;
import static org.mariotaku.twidere.util.Utils.openUserProfile;
import static org.mariotaku.twidere.util.Utils.setMenuItemAvailability;
import static org.mariotaku.twidere.util.MenuUtils.setMenuItemAvailability;
public class UserListFragment extends BaseSupportFragment implements OnClickListener,
LoaderCallbacks<SingleResponse<ParcelableUserList>>, DrawerCallback,

View File

@ -0,0 +1,97 @@
/*
* 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/>.
*/
package org.mariotaku.twidere.model;
import android.content.Context;
import org.mariotaku.twidere.util.KeyboardShortcutsHandler;
import java.util.Locale;
/**
* Created by mariotaku on 15/4/11.
*/
public class KeyboardShortcutSpec {
private String rawKey;
private String value;
private String contextTag;
private int keyMeta;
private String keyName;
public KeyboardShortcutSpec(String key, String value) {
rawKey = key;
final int contextDotIdx = key.indexOf('.');
if (contextDotIdx != -1) {
contextTag = key.substring(0, contextDotIdx);
}
int idx = contextDotIdx, previousIdx = idx;
while ((idx = key.indexOf('+', idx + 1)) != -1) {
keyMeta |= KeyboardShortcutsHandler.getKeyEventMeta(key.substring(previousIdx + 1, idx));
previousIdx = idx;
}
if (previousIdx != -1) {
keyName = key.substring(previousIdx + 1);
}
this.value = value;
}
public String getContextTag() {
return contextTag;
}
public int getKeyMeta() {
return keyMeta;
}
public String getKeyName() {
return keyName;
}
public String getRawKey() {
return rawKey;
}
public String getValue() {
return value;
}
public String getValueName(Context context) {
return KeyboardShortcutsHandler.getActionLabel(context, value);
}
public boolean isValid() {
return keyName != null;
}
public String toKeyString() {
return KeyboardShortcutsHandler.metaToHumanReadableString(keyMeta) + keyName.toUpperCase(Locale.US);
}
@Override
public String toString() {
return "KeyboardShortcutSpec{" +
"value='" + value + '\'' +
", contextTag='" + contextTag + '\'' +
", keyMeta=" + keyMeta +
", keyName='" + keyName + '\'' +
'}';
}
}

View File

@ -64,9 +64,6 @@ public class CacheUsersStatusesTask extends AsyncTask<TwitterListResponse<twitte
for (int bulkIdx = 0, totalSize = list.size(); bulkIdx < totalSize; bulkIdx += 100) {
for (int idx = bulkIdx, end = Math.min(totalSize, bulkIdx + ContentResolverUtils.MAX_BULK_COUNT); idx < end; idx++) {
final twitter4j.Status status = list.get(idx);
if (status == null || status.getId() <= 0) {
continue;
}
final Set<ContentValues> usersValues = new HashSet<>();
final Set<ContentValues> statusesValues = new HashSet<>();

View File

@ -2,6 +2,7 @@ package org.mariotaku.twidere.util;
import android.content.Context;
import android.content.Intent;
import android.support.v4.util.SparseArrayCompat;
import android.text.TextUtils;
import android.view.KeyEvent;
@ -17,16 +18,30 @@ import java.util.Set;
public class KeyboardShortcutsHandler implements Constants {
private static final HashMap<String, Integer> sActionLabelMap = new HashMap<>();
private static final SparseArrayCompat<String> sMetaNameMap = new SparseArrayCompat<>();
static {
sActionLabelMap.put("compose", R.string.compose);
sActionLabelMap.put("search", R.string.search);
sMetaNameMap.put(KeyEvent.META_FUNCTION_ON, "fn");
sMetaNameMap.put(KeyEvent.META_META_ON, "meta");
sMetaNameMap.put(KeyEvent.META_CTRL_ON, "ctrl");
sMetaNameMap.put(KeyEvent.META_ALT_ON, "alt");
sMetaNameMap.put(KeyEvent.META_SHIFT_ON, "shift");
}
private static final String KEYCODE_STRING_PREFIX = "KEYCODE_";
private final Context mContext;
private final SharedPreferencesWrapper mPreferences;
public static int getKeyEventMeta(String name) {
for (int i = 0, j = sMetaNameMap.size(); i < j; i++) {
if (sMetaNameMap.valueAt(i).equalsIgnoreCase(name)) return sMetaNameMap.keyAt(i);
}
return 0;
}
public KeyboardShortcutsHandler(final Context context) {
mContext = context;
mPreferences = SharedPreferencesWrapper.getInstance(context, KEYBOARD_SHORTCUTS_PREFERENCES_NAME, Context.MODE_PRIVATE);
@ -38,6 +53,19 @@ public class KeyboardShortcutsHandler implements Constants {
return context.getString(labelRes);
}
public static String metaToHumanReadableString(int metaState) {
final StringBuilder keyNameBuilder = new StringBuilder();
for (int i = 0, j = sMetaNameMap.size(); i < j; i++) {
if ((sMetaNameMap.keyAt(i) & metaState) != 0) {
final String value = sMetaNameMap.valueAt(i);
keyNameBuilder.append(value.substring(0, 1).toUpperCase(Locale.US));
keyNameBuilder.append(value.substring(1));
keyNameBuilder.append("+");
}
}
return keyNameBuilder.toString();
}
public static Set<String> getActions() {
return sActionLabelMap.keySet();
}
@ -46,16 +74,15 @@ public class KeyboardShortcutsHandler implements Constants {
final StringBuilder keyNameBuilder = new StringBuilder();
if (!TextUtils.isEmpty(contextTag)) {
keyNameBuilder.append(contextTag);
keyNameBuilder.append("_");
keyNameBuilder.append(".");
}
if (event.isCtrlPressed()) {
keyNameBuilder.append("ctrl_");
}
if (event.isAltPressed()) {
keyNameBuilder.append("alt_");
}
if (event.isShiftPressed()) {
keyNameBuilder.append("shift_");
final int metaState = KeyEvent.normalizeMetaState(event.getMetaState());
for (int i = 0, j = sMetaNameMap.size(); i < j; i++) {
if ((sMetaNameMap.keyAt(i) & metaState) != 0) {
keyNameBuilder.append(sMetaNameMap.valueAt(i));
keyNameBuilder.append("+");
}
}
final String keyCodeString = KeyEvent.keyCodeToString(keyCode);
if (keyCodeString.startsWith(KEYCODE_STRING_PREFIX)) {

View File

@ -0,0 +1,57 @@
/*
* 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/>.
*/
package org.mariotaku.twidere.util;
import android.view.Menu;
import android.view.MenuItem;
/**
* Created by mariotaku on 15/4/12.
*/
public class MenuUtils {
public static void setMenuItemAvailability(final Menu menu, final int id, final boolean available) {
if (menu == null) return;
final MenuItem item = menu.findItem(id);
if (item == null) return;
item.setVisible(available);
item.setEnabled(available);
}
public static void setMenuItemChecked(final Menu menu, final int id, final boolean checked) {
if (menu == null) return;
final MenuItem item = menu.findItem(id);
if (item == null) return;
item.setChecked(checked);
}
public static void setMenuItemIcon(final Menu menu, final int id, final int icon) {
if (menu == null) return;
final MenuItem item = menu.findItem(id);
if (item == null) return;
item.setIcon(icon);
}
public static void setMenuItemTitle(final Menu menu, final int id, final int icon) {
if (menu == null) return;
final MenuItem item = menu.findItem(id);
if (item == null) return;
item.setTitle(icon);
}
}

View File

@ -0,0 +1,67 @@
/*
* 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/>.
*/
package org.mariotaku.twidere.util;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.os.Looper;
import android.webkit.WebSettings;
import android.webkit.WebView;
import java.lang.reflect.Constructor;
/**
* Created by mariotaku on 15/4/12.
*/
public class UserAgentUtils {
// You may uncomment next line if using Android Annotations library, otherwise just be sure to run it in on the UI thread
public static String getDefaultUserAgentString(Context context) {
if (Looper.myLooper() != Looper.getMainLooper()) throw new IllegalStateException();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
return NewApiWrapper.getDefaultUserAgent(context);
}
try {
Constructor<WebSettings> constructor = WebSettings.class.getDeclaredConstructor(Context.class, WebView.class);
constructor.setAccessible(true);
try {
WebSettings settings = constructor.newInstance(context, null);
return settings.getUserAgentString();
} finally {
constructor.setAccessible(false);
}
} catch (Exception e) {
final WebView webView = new WebView(context);
try {
return webView.getSettings().getUserAgentString();
} finally {
webView.destroy();
}
}
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
static class NewApiWrapper {
static String getDefaultUserAgent(Context context) {
return WebSettings.getDefaultUserAgent(context);
}
}
}

View File

@ -281,8 +281,6 @@ import static org.mariotaku.twidere.util.UserColorNameUtils.getUserNickname;
@SuppressWarnings("unused")
public final class Utils implements Constants, TwitterConstants {
private static final String UA_TEMPLATE = "Mozilla/5.0 (Linux; Android %s; %s Build/%s) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.111 Safari/537.36";
public static final Pattern PATTERN_XML_RESOURCE_IDENTIFIER = Pattern.compile("res/xml/([\\w_]+)\\.xml");
public static final Pattern PATTERN_RESOURCE_IDENTIFIER = Pattern.compile("@([\\w_]+)/([\\w_]+)");
@ -1174,8 +1172,7 @@ public final class Utils implements Constants, TwitterConstants {
Expression.equals(Statuses.STATUS_ID, statusId)).getSQL();
final ContentResolver resolver = context.getContentResolver();
resolver.delete(CachedStatuses.CONTENT_URI, where, null);
resolver.insert(CachedStatuses.CONTENT_URI,
ContentValuesCreator.createStatus(status, accountId));
resolver.insert(CachedStatuses.CONTENT_URI, ContentValuesCreator.createStatus(status, accountId));
return new ParcelableStatus(status, accountId, false);
}
@ -1252,10 +1249,6 @@ public final class Utils implements Constants, TwitterConstants {
return DateUtils.formatDateTime(context, timestamp, format_flags);
}
public static String generateBrowserUserAgent() {
return String.format(UA_TEMPLATE, Build.VERSION.RELEASE, Build.MODEL, Build.ID);
}
public static int getAccountColor(final Context context, final long account_id) {
if (context == null) return Color.TRANSPARENT;
final Integer cached = sAccountColors.get(account_id);
@ -1586,7 +1579,7 @@ public final class Utils implements Constants, TwitterConstants {
private static String substituteLegacyApiBaseUrl(@NonNull String format, String domain) {
final int startOfHost = format.indexOf("://") + 3, endOfHost = format.indexOf('/', startOfHost);
final String host = format.substring(startOfHost, endOfHost);
final String host = endOfHost != -1 ? format.substring(startOfHost, endOfHost) : format.substring(startOfHost);
if (!host.equalsIgnoreCase("api.twitter.com")) return format;
return format.substring(0, startOfHost) + domain + ".twitter.com" + format.substring(endOfHost);
}
@ -1805,7 +1798,7 @@ public final class Utils implements Constants, TwitterConstants {
final SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
final int timeoutMillis = prefs.getInt(KEY_CONNECTION_TIMEOUT, 10000) * 1000;
final Proxy proxy = getProxy(context);
final String userAgent = generateBrowserUserAgent();
final String userAgent = TwidereApplication.getInstance(context).getDefaultUserAgent();
final HostAddressResolverFactory resolverFactory = new TwidereHostResolverFactory(
TwidereApplication.getInstance(context));
return getHttpClient(context, timeoutMillis, true, proxy, resolverFactory, userAgent, false);
@ -1816,7 +1809,7 @@ public final class Utils implements Constants, TwitterConstants {
final SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
final int timeoutMillis = prefs.getInt(KEY_CONNECTION_TIMEOUT, 10000) * 1000;
final Proxy proxy = getProxy(context);
final String userAgent = generateBrowserUserAgent();
final String userAgent = TwidereApplication.getInstance(context).getDefaultUserAgent();
final HostAddressResolverFactory resolverFactory = new TwidereHostResolverFactory(
TwidereApplication.getInstance(context));
return getHttpClient(context, timeoutMillis, true, proxy, resolverFactory, userAgent, false);
@ -3595,7 +3588,7 @@ public final class Utils implements Constants, TwitterConstants {
final boolean isOfficialKey = isOfficialCredentials(context, account);
final SharedPreferencesWrapper prefs = SharedPreferencesWrapper.getInstance(context, SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
final boolean forcePrivateApis = prefs.getBoolean(KEY_FORCE_USING_PRIVATE_APIS, false);
setMenuItemAvailability(menu, MENU_TRANSLATE, forcePrivateApis || isOfficialKey);
MenuUtils.setMenuItemAvailability(menu, MENU_TRANSLATE, forcePrivateApis || isOfficialKey);
}
menu.removeGroup(MENU_GROUP_STATUS_EXTENSION);
addIntentToMenuForExtension(context, menu, MENU_GROUP_STATUS_EXTENSION, INTENT_ACTION_EXTENSION_OPEN_STATUS,
@ -3616,28 +3609,6 @@ public final class Utils implements Constants, TwitterConstants {
}
public static void setMenuItemAvailability(final Menu menu, final int id, final boolean available) {
if (menu == null) return;
final MenuItem item = menu.findItem(id);
if (item == null) return;
item.setVisible(available);
item.setEnabled(available);
}
public static void setMenuItemIcon(final Menu menu, final int id, final int icon) {
if (menu == null) return;
final MenuItem item = menu.findItem(id);
if (item == null) return;
item.setIcon(icon);
}
public static void setMenuItemTitle(final Menu menu, final int id, final int icon) {
if (menu == null) return;
final MenuItem item = menu.findItem(id);
if (item == null) return;
item.setTitle(icon);
}
public static void setUserAgent(final Context context, final ConfigurationBuilder cb) {
final SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
final boolean gzipCompressing = prefs.getBoolean(KEY_GZIP_COMPRESSING, true);
@ -3663,14 +3634,25 @@ public final class Utils implements Constants, TwitterConstants {
* @param cb
*/
public static void setMockOfficialUserAgent(final Context context, final ConfigurationBuilder cb) {
cb.setClientVersion("5.32.0");
final PackageManager pm = context.getPackageManager();
cb.setClientName("TwitterAndroid");
cb.setClientURL(null);
String versionName;
int versionCode;
try {
final PackageInfo packageInfo = pm.getPackageInfo("com.twitter.android", 0);
versionName = packageInfo.versionName;
versionCode = packageInfo.versionCode;
} catch (PackageManager.NameNotFoundException e) {
versionName = "5.53.0";
versionCode = 4030814;
}
cb.setClientVersion(versionName);
final String deviceInfo = String.format(Locale.ROOT, "%s/%s (%s;%s;%s;%s;)",
Build.MODEL, Build.VERSION.RELEASE, Build.MANUFACTURER, Build.MODEL, Build.BRAND,
Build.PRODUCT);
cb.setHttpUserAgent(String.format(Locale.ROOT, "TwitterAndroid/%s (%d-%c-%d) %s",
"5.32.0", 3030745, 'r', 692, deviceInfo));
versionName, versionCode, 'r', versionCode / 4200, deviceInfo));
}
public static boolean shouldForceUsingPrivateAPIs(final Context context) {

View File

@ -62,6 +62,7 @@ public class MessageEntryViewHolder extends ViewHolder implements OnClickListene
setTextSize(adapter.getTextSize());
itemView.setOnClickListener(this);
profileImageView.setOnClickListener(this);
}
public void displayMessage(Cursor cursor, boolean isUnread) {
@ -101,11 +102,12 @@ public class MessageEntryViewHolder extends ViewHolder implements OnClickListene
public void onClick(View v) {
switch (v.getId()) {
case R.id.profile_image: {
adapter.onUserProfileClick(getLayoutPosition());
break;
}
default: {
if (v == itemView) {
adapter.onMessageClick(getPosition());
adapter.onMessageClick(getLayoutPosition());
}
break;
}

View File

@ -183,10 +183,10 @@ public class StatusViewHolder extends RecyclerView.ViewHolder implements Constan
final int idx = status.quote_text_unescaped.lastIndexOf(" twitter.com");
if (adapter.getLinkHighlightingStyle() == VALUE_LINK_HIGHLIGHT_OPTION_CODE_NONE) {
final String text = status.quote_text_unescaped;
quoteTextView.setText(idx > 0 ? text.substring(0, idx ) : text);
quoteTextView.setText(idx > 0 ? text.substring(0, idx) : text);
} else {
final Spanned text = Html.fromHtml(status.quote_text_html);
quoteTextView.setText(idx > 0 ? text.subSequence(0, idx ) : text);
quoteTextView.setText(idx > 0 ? text.subSequence(0, idx) : text);
linkify.applyAllLinks(quoteTextView, status.account_id, getLayoutPosition(),
status.is_possibly_sensitive, adapter.getLinkHighlightingStyle());
quoteTextView.setMovementMethod(null);
@ -373,10 +373,10 @@ public class StatusViewHolder extends RecyclerView.ViewHolder implements Constan
final String quote_text_unescaped = cursor.getString(indices.quote_text_unescaped);
final int idx = quote_text_unescaped.lastIndexOf(" twitter.com");
if (adapter.getLinkHighlightingStyle() == VALUE_LINK_HIGHLIGHT_OPTION_CODE_NONE) {
quoteTextView.setText(idx > 0 ? quote_text_unescaped.substring(0, idx ) : quote_text_unescaped);
quoteTextView.setText(idx > 0 ? quote_text_unescaped.substring(0, idx) : quote_text_unescaped);
} else {
final Spanned text = Html.fromHtml(cursor.getString(indices.quote_text_html));
quoteTextView.setText(idx > 0 ? text.subSequence(0, idx ) : text);
quoteTextView.setText(idx > 0 ? text.subSequence(0, idx) : text);
linkify.applyAllLinks(quoteTextView, account_id, getLayoutPosition(),
cursor.getShort(indices.is_possibly_sensitive) == 1,
adapter.getLinkHighlightingStyle());

View File

@ -0,0 +1,29 @@
<?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/>.
-->
<menu xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:showAsAction="always"
android:id="@id/add"
android:icon="@drawable/ic_action_add"
android:title="@string/add"
tools:ignore="AppCompatResource"/>
</menu>

View File

@ -0,0 +1,45 @@
<?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/>.
-->
<ScrollView
android:id="@+id/status_container"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/element_spacing_normal"
android:paddingLeft="@dimen/element_spacing_large"
android:paddingRight="@dimen/element_spacing_large">
<LinearLayout
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="wrap_content">
<EditText
android:layout_width="match_parent"
android:visibility="gone"
android:layout_height="wrap_content"
android:inputType="text"/>
<include layout="@layout/card_item_status_common"/>
</LinearLayout>
</ScrollView>

View File

@ -24,7 +24,8 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:orientation="vertical"
android:splitMotionEvents="false">
<LinearLayout
android:id="@+id/retweeted_by_container"

View File

@ -1,23 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<item
android:id="@id/take_photo"
android:icon="@drawable/ic_action_camera"
android:title="@string/take_photo"
app:showAsAction="always"/>
app:showAsAction="always"
tools:ignore="AlwaysShowAction"/>
<item
android:id="@id/add_image"
android:icon="@drawable/ic_action_gallery"
android:title="@string/add_image"
app:showAsAction="always"/>
app:showAsAction="always"
tools:ignore="AlwaysShowAction"/>
<item
android:id="@+id/media_menu"
android:icon="@drawable/ic_action_gallery"
android:title="@string/media"
android:visible="false"
app:showAsAction="always">
app:showAsAction="always"
tools:ignore="AlwaysShowAction">
<menu>
<item
android:id="@+id/take_photo_sub_item"
@ -43,7 +47,8 @@
android:icon="@drawable/ic_action_reply"
android:title="@string/original_status"
android:visible="false"
app:showAsAction="always"/>
app:showAsAction="always"
tools:ignore="AlwaysShowAction"/>
<item
android:id="@id/add_location"
android:checkable="true"
@ -51,11 +56,19 @@
android:icon="@drawable/ic_action_my_location"
android:title="@string/location"
android:visible="false"
app:showAsAction="always"/>
app:showAsAction="always"
tools:ignore="AlwaysShowAction"/>
<item
android:id="@id/drafts"
android:icon="@drawable/ic_action_draft"
android:title="@string/drafts"
app:showAsAction="always"/>
app:showAsAction="always"
tools:ignore="AlwaysShowAction"/>
<item
android:id="@+id/link_to_quoted_status"
android:icon="@drawable/ic_action_link"
android:checkable="true"
android:title="@string/link_to_quoted_status"
app:showAsAction="never"/>
</menu>

View File

@ -48,11 +48,6 @@
android:summary="@string/quote_format_summary"
android:title="@string/quote_format"/>
<CheckBoxPreference
android:defaultValue="true"
android:key="link_to_quoted_tweet"
android:title="@string/link_to_quoted_status"/>
<EditTextPreference
android:defaultValue="[TITLE] - [TEXT]"
android:dialogTitle="@string/share_format"