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

supports tab and filters import/export

fixed some crashes
This commit is contained in:
Mariotaku Lee 2016-03-28 12:36:31 +08:00
parent ebf3deb1d8
commit 1eada0f072
18 changed files with 534 additions and 72 deletions

View File

@ -231,7 +231,6 @@ public interface TwidereConstants extends SharedPreferenceConstants, IntentConst
String TASK_TAG_GET_SENT_DIRECT_MESSAGES = "get_sent_direct_messages";
String TASK_TAG_GET_RECEIVED_DIRECT_MESSAGES = "get_received_direct_messages";
String TASK_TAG_GET_TRENDS = "get_trends";
String TASK_TAG_STORE_TRENDS = "store_trends";
String METADATA_KEY_EXTENSION = "org.mariotaku.twidere.extension";
String METADATA_KEY_EXTENSION_PERMISSIONS = "org.mariotaku.twidere.extension.permissions";

View File

@ -208,14 +208,6 @@ public class ParcelableUser implements Parcelable, Comparable<ParcelableUser> {
}
public static int calculateHashCode(long accountId, long userId) {
final int prime = 31;
int result = 1;
result = prime * result + (int) (accountId ^ accountId >>> 32);
result = prime * result + (int) (userId ^ userId >>> 32);
return result;
}
@AfterCursorObjectCreated
void afterCursorObjectCreated() {
is_cache = true;
@ -240,7 +232,8 @@ public class ParcelableUser implements Parcelable, Comparable<ParcelableUser> {
ParcelableUser user = (ParcelableUser) o;
if (!account_key.equals(user.account_key)) return false;
if (account_key != null ? !account_key.equals(user.account_key) : user.account_key != null)
return false;
return key.equals(user.key);
}
@ -250,9 +243,9 @@ public class ParcelableUser implements Parcelable, Comparable<ParcelableUser> {
return calculateHashCode(account_key, key);
}
public static int calculateHashCode(UserKey accountKey, UserKey userKey) {
int result = accountKey.hashCode();
result = 31 * result + userKey.hashCode();
public static int calculateHashCode(UserKey accountKey, UserKey key) {
int result = accountKey != null ? key.hashCode() : 0;
result = 31 * result + key.hashCode();
return result;
}

View File

@ -87,7 +87,6 @@ import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.Window;
import android.view.inputmethod.EditorInfo;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
@ -1582,7 +1581,7 @@ public class ComposeActivity extends BaseActivity implements OnMenuItemClickList
@Override
public void onClick(View v) {
((CheckableLinearLayout) itemView).toggle();
adapter.toggleSelection(getAdapterPosition());
adapter.toggleSelection(getLayoutPosition());
}
@ -1683,7 +1682,7 @@ public class ComposeActivity extends BaseActivity implements OnMenuItemClickList
}
private void toggleSelection(int position) {
if (mAccounts == null) return;
if (mAccounts == null || position < 0) return;
final ParcelableCredentials account = mAccounts[position];
mSelection.put(account.account_key, !Boolean.TRUE.equals(mSelection.get(account.account_key)));
mActivity.notifyAccountSelectionChanged();

View File

@ -41,6 +41,8 @@ import android.view.View;
import com.squareup.otto.Subscribe;
import org.mariotaku.abstask.library.AbstractTask;
import org.mariotaku.abstask.library.TaskStarter;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.adapter.ParcelableStatusesAdapter;
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter.IndicatorPosition;
@ -53,8 +55,6 @@ import org.mariotaku.twidere.model.ParcelableStatus;
import org.mariotaku.twidere.model.RefreshTaskParam;
import org.mariotaku.twidere.model.UserKey;
import org.mariotaku.twidere.model.message.StatusListChangedEvent;
import org.mariotaku.abstask.library.AbstractTask;
import org.mariotaku.abstask.library.TaskStarter;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
import org.mariotaku.twidere.util.IntentUtils;
import org.mariotaku.twidere.util.KeyboardShortcutsHandler;
@ -549,10 +549,13 @@ public abstract class AbsStatusesFragment extends AbsContentListRecyclerViewFrag
if (status == null) return;
final long positionKey = status.position_key > 0 ? status.position_key : status.timestamp;
mReadStateManager.setPosition(readPositionTag, positionKey);
for (UserKey accountKey : getAccountKeys()) {
final String tag = Utils.getReadPositionTagWithAccounts(getReadPositionTagWithArguments(),
accountKey);
mReadStateManager.setPosition(tag, positionKey);
final UserKey[] accountKeys = getAccountKeys();
if (accountKeys.length > 1) {
for (UserKey accountKey : accountKeys) {
final String tag = Utils.getReadPositionTagWithAccounts(getReadPositionTagWithArguments(),
accountKey);
mReadStateManager.setPosition(tag, positionKey);
}
}
mReadStateManager.setPosition(getCurrentReadPositionTag(), positionKey, true);
}

View File

@ -86,6 +86,8 @@ public final class DataExportImportTypeSelectorDialogFragment extends BaseSuppor
mAdapter.add(new Type(R.string.user_colors, DataImportExportUtils.FLAG_USER_COLORS));
mAdapter.add(new Type(R.string.custom_host_mapping, DataImportExportUtils.FLAG_HOST_MAPPING));
mAdapter.add(new Type(R.string.keyboard_shortcuts, DataImportExportUtils.FLAG_KEYBOARD_SHORTCUTS));
mAdapter.add(new Type(R.string.filters, DataImportExportUtils.FLAG_FILTERS));
mAdapter.add(new Type(R.string.tabs, DataImportExportUtils.FLAG_TABS));
mListView.setAdapter(mAdapter);
mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
mListView.setOnItemClickListener(this);

View File

@ -35,7 +35,7 @@ import com.squareup.otto.Subscribe;
import org.mariotaku.twidere.adapter.TrendsAdapter;
import org.mariotaku.twidere.model.UserKey;
import org.mariotaku.twidere.model.message.TaskStateChangedEvent;
import org.mariotaku.twidere.model.message.TrendsRefreshedEvent;
import org.mariotaku.twidere.provider.TwidereDataStore.CachedTrends;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
@ -124,14 +124,8 @@ public class TrendsSuggestionsFragment extends AbsContentListViewFragment<Trends
}
@Subscribe
public void notifyTaskStateChanged(TaskStateChangedEvent event) {
updateRefreshState();
}
protected void updateRefreshState() {
final AsyncTwitterWrapper twitter = mTwitterWrapper;
if (twitter == null || !getUserVisibleHint()) return;
setRefreshing(twitter.isLocalTrendsRefreshing());
public void onTrendsRefreshedEvent(TrendsRefreshedEvent event) {
setRefreshing(false);
}
}

View File

@ -101,6 +101,8 @@ public class UserListTimelineFragment extends ParcelableStatusesFragment {
}
sb.append('_');
sb.append(listName);
} else {
return null;
}
return sb.toString();
}

View File

@ -0,0 +1,122 @@
package org.mariotaku.twidere.model;
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
import org.mariotaku.library.objectcursor.annotation.CursorField;
import org.mariotaku.library.objectcursor.annotation.CursorObject;
import org.mariotaku.twidere.provider.TwidereDataStore.Filters;
import java.util.List;
/**
* Created by mariotaku on 16/3/28.
*/
@JsonObject
public class FiltersData {
@JsonField(name = "users")
List<UserItem> users;
@JsonField(name = "keywords")
List<BaseItem> keywords;
@JsonField(name = "sources")
List<BaseItem> sources;
@JsonField(name = "links")
List<BaseItem> links;
public List<UserItem> getUsers() {
return users;
}
public List<BaseItem> getKeywords() {
return keywords;
}
public List<BaseItem> getSources() {
return sources;
}
public List<BaseItem> getLinks() {
return links;
}
public void setUsers(List<UserItem> users) {
this.users = users;
}
public void setKeywords(List<BaseItem> keywords) {
this.keywords = keywords;
}
public void setSources(List<BaseItem> sources) {
this.sources = sources;
}
public void setLinks(List<BaseItem> links) {
this.links = links;
}
@Override
public String toString() {
return "FiltersData{" +
"users=" + users +
", keywords=" + keywords +
", sources=" + sources +
", links=" + links +
'}';
}
@JsonObject
@CursorObject(valuesCreator = true)
public static class UserItem {
@CursorField(Filters.Users.USER_KEY)
@JsonField(name = "user_key")
String userKey;
@CursorField(Filters.Users.NAME)
@JsonField(name = "name")
String name;
@CursorField(Filters.Users.SCREEN_NAME)
@JsonField(name = "screen_name")
String screenName;
public String getUserKey() {
return userKey;
}
public String getName() {
return name;
}
public String getScreenName() {
return screenName;
}
@Override
public String toString() {
return "UserItem{" +
"userKey='" + userKey + '\'' +
", name='" + name + '\'' +
", screenName='" + screenName + '\'' +
'}';
}
}
@JsonObject
@CursorObject(valuesCreator = true)
public static class BaseItem {
@CursorField(Filters.VALUE)
@JsonField(name = "value")
String value;
public String getValue() {
return value;
}
@Override
public String toString() {
return "BaseItem{" +
"value='" + value + '\'' +
'}';
}
}
}

View File

@ -1,9 +1,20 @@
package org.mariotaku.twidere.model;
import android.support.annotation.Nullable;
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
import com.bluelinelabs.logansquare.annotation.OnJsonParseComplete;
import com.bluelinelabs.logansquare.annotation.OnPreJsonSerialize;
import org.mariotaku.library.objectcursor.annotation.CursorField;
import org.mariotaku.library.objectcursor.annotation.CursorObject;
import org.mariotaku.twidere.annotation.CustomTabType;
import org.mariotaku.twidere.model.tab.argument.TabArguments;
import org.mariotaku.twidere.model.tab.argument.TextQueryArguments;
import org.mariotaku.twidere.model.tab.argument.UserArguments;
import org.mariotaku.twidere.model.tab.argument.UserListArguments;
import org.mariotaku.twidere.model.tab.extra.InteractionsTabExtras;
import org.mariotaku.twidere.model.tab.extra.TabExtras;
import org.mariotaku.twidere.model.util.TabArgumentsFieldConverter;
import org.mariotaku.twidere.model.util.TabExtrasFieldConverter;
@ -13,29 +24,45 @@ import org.mariotaku.twidere.provider.TwidereDataStore.Tabs;
* Created by mariotaku on 16/3/6.
*/
@CursorObject(valuesCreator = true)
@JsonObject
public class Tab {
@CursorField(value = Tabs._ID, excludeWrite = true)
@JsonField(name = "id")
long id;
@CursorField(Tabs.NAME)
@JsonField(name = "name")
String name;
@CursorField(Tabs.ICON)
@JsonField(name = "icon")
String icon;
@CursorField(Tabs.TYPE)
@JsonField(name = "type")
@CustomTabType
String type;
@CursorField(Tabs.POSITION)
@JsonField(name = "position")
int position;
@Nullable
@CursorField(value = Tabs.ARGUMENTS, converter = TabArgumentsFieldConverter.class)
TabArguments arguments;
@Nullable
@CursorField(value = Tabs.EXTRAS, converter = TabExtrasFieldConverter.class)
TabExtras extras;
@Nullable
@JsonField(name = "arguments")
InternalArguments internalArguments;
@Nullable
@JsonField(name = "extras")
InternalExtras internalExtras;
public long getId() {
return id;
}
@ -73,22 +100,41 @@ public class Tab {
this.position = position;
}
@Nullable
public TabArguments getArguments() {
return arguments;
}
public void setArguments(TabArguments arguments) {
public void setArguments(@Nullable TabArguments arguments) {
this.arguments = arguments;
}
@Nullable
public TabExtras getExtras() {
return extras;
}
public void setExtras(TabExtras extras) {
public void setExtras(@Nullable TabExtras extras) {
this.extras = extras;
}
@OnPreJsonSerialize
void beforeJsonSerialize() {
internalArguments = InternalArguments.from(arguments);
internalExtras = InternalExtras.from(extras);
}
@OnJsonParseComplete
void onJsonParseComplete() {
if (internalArguments != null) {
arguments = internalArguments.getArguments();
}
if (internalExtras != null) {
extras = internalExtras.getExtras();
}
}
@Override
public String toString() {
return "Tab{" +
@ -101,4 +147,71 @@ public class Tab {
", extras=" + extras +
'}';
}
@JsonObject
static class InternalArguments {
@JsonField(name = "base")
TabArguments base;
@JsonField(name = "text_query")
TextQueryArguments textQuery;
@JsonField(name = "user")
UserArguments user;
@JsonField(name = "user_list")
UserListArguments userList;
public static InternalArguments from(TabArguments arguments) {
if (arguments == null) return null;
InternalArguments result = new InternalArguments();
if (arguments instanceof TextQueryArguments) {
result.textQuery = (TextQueryArguments) arguments;
} else if (arguments instanceof UserArguments) {
result.user = (UserArguments) arguments;
} else if (arguments instanceof UserListArguments) {
result.userList = (UserListArguments) arguments;
} else {
result.base = arguments;
}
return result;
}
public TabArguments getArguments() {
if (userList != null) {
return userList;
} else if (user != null) {
return user;
} else if (textQuery != null) {
return textQuery;
} else {
return base;
}
}
}
@JsonObject
static class InternalExtras {
@JsonField(name = "base")
TabExtras base;
@JsonField(name = "interactions")
InteractionsTabExtras interactions;
public static InternalExtras from(TabExtras extras) {
if (extras == null) return null;
InternalExtras result = new InternalExtras();
if (extras instanceof InteractionsTabExtras) {
result.interactions = (InteractionsTabExtras) extras;
} else {
result.base = extras;
}
return result;
}
public TabExtras getExtras() {
if (interactions != null) {
return interactions;
} else {
return base;
}
}
}
}

View File

@ -0,0 +1,7 @@
package org.mariotaku.twidere.model.message;
/**
* Created by mariotaku on 16/3/28.
*/
public class TrendsRefreshedEvent {
}

View File

@ -45,6 +45,10 @@ import org.mariotaku.sqliteqb.library.Expression;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.preference.iface.IDialogPreference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static android.text.TextUtils.isEmpty;
public class RingtonePreference extends DialogPreference implements IDialogPreference {
@ -52,7 +56,6 @@ public class RingtonePreference extends DialogPreference implements IDialogPrefe
private final int mRingtoneType;
private final boolean mShowDefault;
private final boolean mShowSilent;
private int mSelectedItem;
public RingtonePreference(final Context context, final AttributeSet attrs) {
super(context, attrs);
@ -64,12 +67,16 @@ public class RingtonePreference extends DialogPreference implements IDialogPrefe
a.recycle();
}
public int getItem() {
return mSelectedItem;
public int getRingtoneType() {
return mRingtoneType;
}
public void setItem(final int selected) {
mSelectedItem = selected;
public boolean isShowDefault() {
return mShowDefault;
}
public boolean isShowSilent() {
return mShowSilent;
}
@Override
@ -83,6 +90,7 @@ public class RingtonePreference extends DialogPreference implements IDialogPrefe
implements LoaderManager.LoaderCallbacks<Cursor> {
private MediaPlayer mMediaPlayer;
private SimpleCursorAdapter mAdapter;
private Uri mCurrentUri;
public static RingtonePreferenceDialogFragment newInstance(String key) {
final RingtonePreferenceDialogFragment df = new RingtonePreferenceDialogFragment();
@ -94,6 +102,12 @@ public class RingtonePreference extends DialogPreference implements IDialogPrefe
@Override
public void onDialogClosed(boolean positive) {
if (positive && mCurrentUri != null) {
final RingtonePreference preference = (RingtonePreference) getPreference();
if (preference.isPersistent()) {
preference.persistString(mCurrentUri.toString());
}
}
if (mMediaPlayer != null) {
if (mMediaPlayer.isPlaying()) {
mMediaPlayer.stop();
@ -151,8 +165,9 @@ public class RingtonePreference extends DialogPreference implements IDialogPrefe
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setLooping(false);
final String ringtone = cursor.getString(cursor.getColumnIndex(Audio.Media.DATA));
final Uri def_uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
final Uri uri = isEmpty(ringtone) ? def_uri : Uri.parse(ringtone);
final Uri defUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
final Uri uri = isEmpty(ringtone) ? defUri : Uri.parse(ringtone);
mCurrentUri = uri;
try {
mMediaPlayer.setDataSource(getContext(), uri);
mMediaPlayer.prepare();
@ -169,8 +184,29 @@ public class RingtonePreference extends DialogPreference implements IDialogPrefe
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
final String[] cols = new String[]{Audio.Media._ID, Audio.Media.DATA, Audio.Media.TITLE};
final String selection = Expression.equalsArgs(Audio.Media.IS_NOTIFICATION).getSQL();
final String[] selectionArgs = {"1"};
RingtonePreference preference = (RingtonePreference) getPreference();
int ringtoneType = preference.getRingtoneType();
List<Expression> expressions = new ArrayList<>();
if ((ringtoneType & RingtoneManager.TYPE_NOTIFICATION) != 0) {
expressions.add(Expression.equalsArgs(Audio.Media.IS_NOTIFICATION));
}
if ((ringtoneType & RingtoneManager.TYPE_RINGTONE) != 0) {
expressions.add(Expression.equalsArgs(Audio.Media.IS_RINGTONE));
}
if ((ringtoneType & RingtoneManager.TYPE_ALARM) != 0) {
expressions.add(Expression.equalsArgs(Audio.Media.IS_ALARM));
}
final String selection;
final String[] selectionArgs;
if (expressions.isEmpty()) {
selection = null;
selectionArgs = null;
} else {
final int size = expressions.size();
selection = Expression.or(expressions.toArray(new Expression[size])).getSQL();
selectionArgs = new String[size];
Arrays.fill(selectionArgs, "1");
}
return new CursorLoader(getContext(), Audio.Media.INTERNAL_CONTENT_URI, cols, selection,
selectionArgs, Audio.Media.DEFAULT_SORT_ORDER);
}

View File

@ -176,9 +176,7 @@ public class RefreshService extends Service implements Constants {
if (BuildConfig.DEBUG) {
Log.d(LOGTAG, String.format("Auto refreshing trends for %s", Arrays.toString(refreshIds)));
}
if (!isLocalTrendsRefreshing()) {
getLocalTrends(refreshIds);
}
getLocalTrends(refreshIds);
break;
}
}
@ -324,10 +322,6 @@ public class RefreshService extends Service implements Constants {
return mTwitterWrapper.isHomeTimelineRefreshing();
}
private boolean isLocalTrendsRefreshing() {
return mTwitterWrapper.isLocalTrendsRefreshing();
}
private boolean isReceivedDirectMessagesRefreshing() {
return mTwitterWrapper.isReceivedDirectMessagesRefreshing();
}

View File

@ -6,19 +6,25 @@ import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import com.squareup.otto.Bus;
import org.mariotaku.abstask.library.AbstractTask;
import org.mariotaku.twidere.api.twitter.Twitter;
import org.mariotaku.twidere.api.twitter.TwitterException;
import org.mariotaku.twidere.api.twitter.model.Trends;
import org.mariotaku.twidere.model.UserKey;
import org.mariotaku.twidere.model.message.TrendsRefreshedEvent;
import org.mariotaku.twidere.provider.TwidereDataStore;
import org.mariotaku.twidere.util.ContentValuesCreator;
import org.mariotaku.twidere.util.TwitterAPIFactory;
import org.mariotaku.twidere.util.content.ContentResolverUtils;
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
/**
* Created by mariotaku on 16/2/24.
*/
@ -27,7 +33,11 @@ public abstract class GetTrendsTask extends AbstractTask<Object, Object, Object>
private final Context mContext;
private final UserKey mAccountId;
@Inject
protected Bus mBus;
public GetTrendsTask(Context context, final UserKey accountKey) {
GeneralComponentHelper.build(context).inject(this);
this.mContext = context;
this.mAccountId = accountKey;
}
@ -47,6 +57,11 @@ public abstract class GetTrendsTask extends AbstractTask<Object, Object, Object>
}
}
@Override
protected void afterExecute(Object o) {
mBus.post(new TrendsRefreshedEvent());
}
protected abstract Uri getContentUri();
private static void storeTrends(ContentResolver cr, Uri uri, List<Trends> trendsList) {

View File

@ -357,11 +357,6 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
return mAsyncTaskManager.hasRunningTasksForTag(TASK_TAG_GET_HOME_TIMELINE);
}
public boolean isLocalTrendsRefreshing() {
return mAsyncTaskManager.hasRunningTasksForTag(TASK_TAG_GET_TRENDS)
|| mAsyncTaskManager.hasRunningTasksForTag(TASK_TAG_STORE_TRENDS);
}
public boolean isReceivedDirectMessagesRefreshing() {
return mAsyncTaskManager.hasRunningTasksForTag(TASK_TAG_GET_RECEIVED_DIRECT_MESSAGES);
}

View File

@ -19,20 +19,38 @@
package org.mariotaku.twidere.util;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.WorkerThread;
import android.util.Log;
import com.bluelinelabs.logansquare.JsonMapper;
import com.bluelinelabs.logansquare.LoganSquare;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import org.mariotaku.library.objectcursor.ObjectCursor;
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.annotation.Preference;
import org.mariotaku.twidere.annotation.PreferenceType;
import org.mariotaku.twidere.constant.SharedPreferenceConstants;
import org.mariotaku.twidere.model.FiltersData;
import org.mariotaku.twidere.model.FiltersData$BaseItemCursorIndices;
import org.mariotaku.twidere.model.FiltersData$BaseItemValuesCreator;
import org.mariotaku.twidere.model.FiltersData$UserItemCursorIndices;
import org.mariotaku.twidere.model.FiltersData$UserItemValuesCreator;
import org.mariotaku.twidere.model.Tab;
import org.mariotaku.twidere.model.TabCursorIndices;
import org.mariotaku.twidere.model.TabValuesCreator;
import org.mariotaku.twidere.provider.TwidereDataStore.Filters;
import org.mariotaku.twidere.provider.TwidereDataStore.Tabs;
import org.mariotaku.twidere.util.content.ContentResolverUtils;
import java.io.File;
import java.io.FileNotFoundException;
@ -40,7 +58,9 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@ -54,16 +74,19 @@ public class DataImportExportUtils implements Constants {
public static final String ENTRY_HOST_MAPPING = "host_mapping.json";
public static final String ENTRY_KEYBOARD_SHORTCUTS = "keyboard_shortcuts.json";
public static final String ENTRY_FILTERS = "filters.json";
public static final String ENTRY_TABS = "tabs.json";
public static final int FLAG_PREFERENCES = 0x1;
public static final int FLAG_NICKNAMES = 0x2;
public static final int FLAG_USER_COLORS = 0x4;
public static final int FLAG_HOST_MAPPING = 0x8;
public static final int FLAG_KEYBOARD_SHORTCUTS = 0x10;
public static final int FLAG_FILTERS = 0x20;
public static final int FLAG_PREFERENCES = 0b1;
public static final int FLAG_NICKNAMES = 0b10;
public static final int FLAG_USER_COLORS = 0b100;
public static final int FLAG_HOST_MAPPING = 0b1000;
public static final int FLAG_KEYBOARD_SHORTCUTS = 0b10000;
public static final int FLAG_FILTERS = 0b100000;
public static final int FLAG_TABS = 0b1000000;
public static final int FLAG_ALL = FLAG_PREFERENCES | FLAG_NICKNAMES | FLAG_USER_COLORS
| FLAG_HOST_MAPPING | FLAG_KEYBOARD_SHORTCUTS | FLAG_FILTERS;
| FLAG_HOST_MAPPING | FLAG_KEYBOARD_SHORTCUTS | FLAG_FILTERS | FLAG_TABS;
@WorkerThread
public static void exportData(final Context context, @NonNull final File dst, final int flags) throws IOException {
dst.delete();
final FileOutputStream fos = new FileOutputStream(dst);
@ -84,6 +107,40 @@ public class DataImportExportUtils implements Constants {
if (hasFlag(flags, FLAG_KEYBOARD_SHORTCUTS)) {
exportSharedPreferencesData(zos, context, KEYBOARD_SHORTCUTS_PREFERENCES_NAME, ENTRY_KEYBOARD_SHORTCUTS, ConvertToStringProcessStrategy.SINGLETON);
}
if (hasFlag(flags, FLAG_FILTERS)) {
// TODO export filters
FiltersData data = new FiltersData();
final ContentResolver cr = context.getContentResolver();
data.setUsers(queryAll(cr, Filters.Users.CONTENT_URI, Filters.Users.COLUMNS,
FiltersData$UserItemCursorIndices.class));
data.setKeywords(queryAll(cr, Filters.Keywords.CONTENT_URI, Filters.Keywords.COLUMNS,
FiltersData$BaseItemCursorIndices.class));
data.setSources(queryAll(cr, Filters.Sources.CONTENT_URI, Filters.Sources.COLUMNS,
FiltersData$BaseItemCursorIndices.class));
data.setLinks(queryAll(cr, Filters.Links.CONTENT_URI, Filters.Links.COLUMNS,
FiltersData$BaseItemCursorIndices.class));
exportItem(zos, ENTRY_FILTERS, FiltersData.class, data);
}
if (hasFlag(flags, FLAG_TABS)) {
// TODO export tabs
final ContentResolver cr = context.getContentResolver();
final Cursor c = cr.query(Tabs.CONTENT_URI, Tabs.COLUMNS, null, null, null);
if (c != null) {
final List<Tab> tabs = new ArrayList<>(c.getCount());
try {
TabCursorIndices ci = new TabCursorIndices(c);
c.moveToFirst();
while (!c.isAfterLast()) {
tabs.add(ci.newObject(c));
c.moveToNext();
}
} finally {
c.close();
}
exportItemsList(zos, ENTRY_TABS, Tab.class, tabs);
}
}
zos.finish();
zos.flush();
} finally {
@ -92,6 +149,30 @@ public class DataImportExportUtils implements Constants {
}
}
private static <T> List<T> queryAll(ContentResolver cr, Uri uri, String[] projection,
Class<? extends ObjectCursor.CursorIndices<T>> cls) {
Cursor c = cr.query(uri, projection, null, null, null);
if (c == null) return null;
try {
final ObjectCursor.CursorIndices<T> ci;
try {
ci = cls.getConstructor(Cursor.class).newInstance(c);
} catch (Exception e) {
throw new RuntimeException(e);
}
List<T> items = new ArrayList<>(c.getCount());
c.moveToFirst();
while (!c.isAfterLast()) {
items.add(ci.newObject(c));
c.moveToNext();
}
return items;
} finally {
c.close();
}
}
@WorkerThread
public static int getImportedSettingsFlags(@NonNull final File src) throws IOException {
final ZipFile zipFile = new ZipFile(src);
int flags = 0;
@ -113,6 +194,9 @@ public class DataImportExportUtils implements Constants {
if (zipFile.getEntry(ENTRY_FILTERS) != null) {
flags |= FLAG_FILTERS;
}
if (zipFile.getEntry(ENTRY_TABS) != null) {
flags |= FLAG_TABS;
}
zipFile.close();
return flags;
}
@ -153,7 +237,50 @@ public class DataImportExportUtils implements Constants {
importSharedPreferencesData(zipFile, context, KEYBOARD_SHORTCUTS_PREFERENCES_NAME, ENTRY_KEYBOARD_SHORTCUTS, ConvertToStringProcessStrategy.SINGLETON);
}
if (hasFlag(flags, FLAG_FILTERS)) {
importItem(context, zipFile, ENTRY_FILTERS, FiltersData.class, new ContentResolverProcessStrategy<FiltersData>() {
@Override
public boolean importItem(ContentResolver cr, FiltersData filtersData) {
if (filtersData == null) return false;
insertBase(cr, Filters.Keywords.CONTENT_URI, filtersData.getKeywords());
insertBase(cr, Filters.Sources.CONTENT_URI, filtersData.getSources());
insertBase(cr, Filters.Links.CONTENT_URI, filtersData.getLinks());
insertUser(cr, Filters.Users.CONTENT_URI, filtersData.getUsers());
return true;
}
void insertBase(ContentResolver cr, Uri uri, List<FiltersData.BaseItem> items) {
if (items == null) return;
List<ContentValues> values = new ArrayList<>(items.size());
for (FiltersData.BaseItem item : items) {
values.add(FiltersData$BaseItemValuesCreator.create(item));
}
ContentResolverUtils.bulkInsert(cr, uri, values);
}
void insertUser(ContentResolver cr, Uri uri, List<FiltersData.UserItem> items) {
if (items == null) return;
List<ContentValues> values = new ArrayList<>(items.size());
for (FiltersData.UserItem item : items) {
values.add(FiltersData$UserItemValuesCreator.create(item));
}
ContentResolverUtils.bulkInsert(cr, uri, values);
}
});
}
if (hasFlag(flags, FLAG_TABS)) {
importItemsList(context, zipFile, ENTRY_TABS, Tab.class, new ContentResolverProcessStrategy<List<Tab>>() {
@Override
public boolean importItem(ContentResolver cr, List<Tab> items) {
if (items == null) return false;
List<ContentValues> values = new ArrayList<>(items.size());
for (Tab item : items) {
values.add(TabValuesCreator.create(item));
}
cr.delete(Tabs.CONTENT_URI, null, null);
ContentResolverUtils.bulkInsert(cr, Tabs.CONTENT_URI, values);
return true;
}
});
}
zipFile.close();
}
@ -164,7 +291,7 @@ public class DataImportExportUtils implements Constants {
private static void importSharedPreferencesData(@NonNull final ZipFile zipFile, @NonNull final Context context,
@NonNull final String preferencesName, @NonNull final String entryName,
@NonNull final ProcessStrategy strategy) throws IOException {
@NonNull final SharedPreferencesProcessStrategy strategy) throws IOException {
final ZipEntry entry = zipFile.getEntry(entryName);
if (entry == null) return;
final JsonParser jsonParser = LoganSquare.JSON_FACTORY.createParser(zipFile.getInputStream(entry));
@ -186,7 +313,7 @@ public class DataImportExportUtils implements Constants {
private static void exportSharedPreferencesData(@NonNull final ZipOutputStream zos, final Context context,
@NonNull final String preferencesName, @NonNull final String entryName,
@NonNull final ProcessStrategy strategy) throws IOException {
@NonNull final SharedPreferencesProcessStrategy strategy) throws IOException {
final SharedPreferences preferences = context.getSharedPreferences(preferencesName, Context.MODE_PRIVATE);
final Map<String, ?> map = preferences.getAll();
zos.putNextEntry(new ZipEntry(entryName));
@ -200,15 +327,69 @@ public class DataImportExportUtils implements Constants {
zos.closeEntry();
}
private interface ProcessStrategy {
private static <T> void importItemsList(@NonNull final Context context,
@NonNull final ZipFile zipFile,
@NonNull final String entryName,
@NonNull final Class<T> itemCls,
@NonNull final ContentResolverProcessStrategy<List<T>> strategy)
throws IOException {
final ZipEntry entry = zipFile.getEntry(entryName);
if (entry == null) return;
final JsonMapper<T> mapper = LoganSquareMapperFinder.mapperFor(itemCls);
List<T> itemsList = mapper.parseList(zipFile.getInputStream(entry));
strategy.importItem(context.getContentResolver(), itemsList);
}
private static <T> void exportItemsList(@NonNull final ZipOutputStream zos,
@NonNull final String entryName,
@NonNull final Class<T> itemCls,
@NonNull final List<T> itemList) throws IOException {
zos.putNextEntry(new ZipEntry(entryName));
final JsonGenerator jsonGenerator = LoganSquare.JSON_FACTORY.createGenerator(zos);
LoganSquareMapperFinder.mapperFor(itemCls).serialize(itemList, jsonGenerator);
jsonGenerator.flush();
zos.closeEntry();
}
private static <T> void importItem(@NonNull final Context context,
@NonNull final ZipFile zipFile,
@NonNull final String entryName,
@NonNull final Class<T> itemCls,
@NonNull final ContentResolverProcessStrategy<T> strategy)
throws IOException {
final ZipEntry entry = zipFile.getEntry(entryName);
if (entry == null) return;
final JsonMapper<T> mapper = LoganSquareMapperFinder.mapperFor(itemCls);
T item = mapper.parse(zipFile.getInputStream(entry));
strategy.importItem(context.getContentResolver(), item);
}
private static <T> void exportItem(@NonNull final ZipOutputStream zos,
@NonNull final String entryName,
@NonNull final Class<T> itemCls,
@NonNull final T item) throws IOException {
zos.putNextEntry(new ZipEntry(entryName));
final JsonGenerator jsonGenerator = LoganSquare.JSON_FACTORY.createGenerator(zos);
LoganSquareMapperFinder.mapperFor(itemCls).serialize(item, jsonGenerator, true);
jsonGenerator.flush();
zos.closeEntry();
}
private interface ContentResolverProcessStrategy<T> {
boolean importItem(ContentResolver cr, T item);
}
private interface SharedPreferencesProcessStrategy {
boolean importValue(JsonParser jsonParser, String key, SharedPreferences.Editor editor) throws IOException;
boolean exportValue(JsonGenerator jsonGenerator, String key, SharedPreferences preferences) throws IOException;
}
private static final class ConvertToStringProcessStrategy implements ProcessStrategy {
private static final class ConvertToStringProcessStrategy implements SharedPreferencesProcessStrategy {
private static final ProcessStrategy SINGLETON = new ConvertToStringProcessStrategy();
private static final SharedPreferencesProcessStrategy SINGLETON = new ConvertToStringProcessStrategy();
@Override
public boolean importValue(JsonParser jsonParser, String key, SharedPreferences.Editor editor) throws IOException {
@ -230,9 +411,9 @@ public class DataImportExportUtils implements Constants {
}
}
private static final class ConvertToIntProcessStrategy implements ProcessStrategy {
private static final class ConvertToIntProcessStrategy implements SharedPreferencesProcessStrategy {
private static final ProcessStrategy SINGLETON = new ConvertToIntProcessStrategy();
private static final SharedPreferencesProcessStrategy SINGLETON = new ConvertToIntProcessStrategy();
@Override
public boolean importValue(JsonParser jsonParser, String key, SharedPreferences.Editor editor) throws IOException {
@ -254,7 +435,7 @@ public class DataImportExportUtils implements Constants {
}
}
private static final class AnnotationProcessStrategy implements ProcessStrategy {
private static final class AnnotationProcessStrategy implements SharedPreferencesProcessStrategy {
private final HashMap<String, Preference> supportedMap;

View File

@ -316,7 +316,8 @@ public class TwitterAPIFactory implements TwidereConstants {
}
public static boolean verifyApiFormat(@NonNull String format) {
return URLUtil.isValidUrl(getApiBaseUrl(format, "test"));
final String url = getApiBaseUrl(format, "test");
return URLUtil.isHttpsUrl(url) || URLUtil.isHttpUrl(url);
}
@NonNull
@ -339,7 +340,10 @@ public class TwitterAPIFactory implements TwidereConstants {
@NonNull
static String substituteLegacyApiBaseUrl(@NonNull String format, String domain) {
final int startOfHost = format.indexOf("://") + 3;
final int idxOfSlash = format.indexOf("://");
// Not an url
if (idxOfSlash < 0) return format;
final int startOfHost = idxOfSlash + 3;
if (startOfHost < 0) return getApiBaseUrl("https://[DOMAIN.]twitter.com/", domain);
final int endOfHost = format.indexOf('/', startOfHost);
final String host = endOfHost != -1 ? format.substring(startOfHost, endOfHost) : format.substring(startOfHost);

View File

@ -49,6 +49,7 @@ import org.mariotaku.twidere.service.BackgroundOperationService;
import org.mariotaku.twidere.service.RefreshService;
import org.mariotaku.twidere.task.AbsFriendshipOperationTask;
import org.mariotaku.twidere.task.GetDirectMessagesTask;
import org.mariotaku.twidere.task.GetTrendsTask;
import org.mariotaku.twidere.task.ManagedAsyncTask;
import org.mariotaku.twidere.task.twitter.GetActivitiesTask;
import org.mariotaku.twidere.task.twitter.GetStatusesTask;
@ -135,4 +136,6 @@ public interface GeneralComponent {
void inject(ParcelableUserLoader loader);
void inject(ParcelableStatusLoader loader);
void inject(GetTrendsTask task);
}

View File

@ -43,7 +43,7 @@
android:title="@string/read_from_bottom"/>
<org.mariotaku.twidere.preference.TrendsLocationPreference
android:key="trends_location"
android:key="local_trends_woeid"
android:summary="@string/trends_location_summary"
android:title="@string/trends_location"/>