mirror of
https://github.com/TwidereProject/Twidere-Android
synced 2025-02-17 04:00:48 +01:00
fixed text count
This commit is contained in:
parent
b39929f82b
commit
99503e83a5
@ -305,4 +305,7 @@ public interface SharedPreferenceConstants {
|
||||
String KEY_NEW_DOCUMENT_API = "new_document_api";
|
||||
@Preference(type = BOOLEAN, hasDefault = true, defaultBoolean = false)
|
||||
String KEY_DRAWER_TOGGLE = "drawer_toggle";
|
||||
|
||||
String KEY_MEDIA_LINK_COUNTS_IN_STATUS = "media_link_counts_in_status";
|
||||
|
||||
}
|
||||
|
3
twidere/src/main/assets/data/default_features.json
Normal file
3
twidere/src/main/assets/data/default_features.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"media_link_counts_in_status": true
|
||||
}
|
@ -88,7 +88,7 @@ public class HotMobiLogger implements HotMobiConstants {
|
||||
}
|
||||
|
||||
public static HotMobiLogger getInstance(Context context) {
|
||||
return DependencyHolder.get(context).getHotMobiLogger();
|
||||
return DependencyHolder.Companion.get(context).getHotMobiLogger();
|
||||
}
|
||||
|
||||
public static File getLogFile(Context context, @Nullable UserKey accountKey, String type) {
|
||||
|
@ -26,7 +26,7 @@ public class LocationUtils implements HotMobiConstants, Constants {
|
||||
|
||||
public static LatLng getCachedLatLng(@NonNull final Context context) {
|
||||
final Context appContext = context.getApplicationContext();
|
||||
final SharedPreferences prefs = DependencyHolder.get(context).getPreferences();
|
||||
final SharedPreferences prefs = DependencyHolder.Companion.get(context).getPreferences();
|
||||
if (!prefs.getBoolean(KEY_USAGE_STATISTICS, false)) return null;
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(HotMobiLogger.LOGTAG, "getting cached location");
|
||||
|
@ -144,7 +144,7 @@ public class NetworkDiagnosticsFragment extends BaseSupportFragment {
|
||||
publishProgress(new LogText("Text below may have personal information, BE CAREFUL TO MAKE IT PUBLIC",
|
||||
LogText.State.WARNING));
|
||||
publishProgress(LogText.LINEBREAK, LogText.LINEBREAK);
|
||||
DependencyHolder holder = DependencyHolder.get(mContext);
|
||||
DependencyHolder holder = DependencyHolder.Companion.get(mContext);
|
||||
final TwidereDns dns = holder.getDns();
|
||||
final SharedPreferencesWrapper prefs = holder.getPreferences();
|
||||
publishProgress(new LogText("Network preferences"), LogText.LINEBREAK);
|
||||
|
@ -1,87 +0,0 @@
|
||||
package org.mariotaku.twidere.model;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/2/11.
|
||||
*/
|
||||
public class BaseRefreshTaskParam implements RefreshTaskParam {
|
||||
|
||||
private final UserKey[] accountKeys;
|
||||
private final String[] maxIds;
|
||||
private final String[] sinceIds;
|
||||
private final long[] maxSortIds;
|
||||
private final long[] sinceSortIds;
|
||||
private boolean isLoadingMore;
|
||||
private boolean shouldAbort;
|
||||
|
||||
public BaseRefreshTaskParam(UserKey[] accountKeys, String[] maxIds, String[] sinceIds) {
|
||||
this(accountKeys, maxIds, sinceIds, null, null);
|
||||
}
|
||||
|
||||
public BaseRefreshTaskParam(UserKey[] accountKeys, String[] maxIds, String[] sinceIds,
|
||||
long[] maxSortIds, long[] sinceSortIds) {
|
||||
this.accountKeys = accountKeys;
|
||||
this.maxIds = maxIds;
|
||||
this.sinceIds = sinceIds;
|
||||
this.maxSortIds = maxSortIds;
|
||||
this.sinceSortIds = sinceSortIds;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public UserKey[] getAccountKeys() {
|
||||
return accountKeys;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String[] getMaxIds() {
|
||||
return maxIds;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String[] getSinceIds() {
|
||||
return sinceIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMaxIds() {
|
||||
return maxIds != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSinceIds() {
|
||||
return sinceIds != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long[] getMaxSortIds() {
|
||||
return maxSortIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long[] getSinceSortIds() {
|
||||
return sinceSortIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoadingMore() {
|
||||
return isLoadingMore;
|
||||
}
|
||||
|
||||
public void setLoadingMore(boolean isLoadingMore) {
|
||||
this.isLoadingMore = isLoadingMore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldAbort() {
|
||||
return shouldAbort;
|
||||
}
|
||||
|
||||
public void setShouldAbort(boolean shouldAbort) {
|
||||
this.shouldAbort = shouldAbort;
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
package org.mariotaku.twidere.model;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.support.annotation.WorkerThread;
|
||||
|
||||
import com.bluelinelabs.logansquare.JsonMapper;
|
||||
import com.bluelinelabs.logansquare.LoganSquare;
|
||||
import com.bluelinelabs.logansquare.annotation.JsonField;
|
||||
import com.bluelinelabs.logansquare.annotation.JsonObject;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
|
||||
import org.mariotaku.restfu.annotation.method.GET;
|
||||
import org.mariotaku.restfu.http.HttpRequest;
|
||||
import org.mariotaku.restfu.http.HttpResponse;
|
||||
import org.mariotaku.restfu.http.RestHttpClient;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_MEDIA_LINK_COUNTS_IN_STATUS;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/9/9.
|
||||
*/
|
||||
@JsonObject
|
||||
public class DefaultFeatures {
|
||||
|
||||
private final static String REMOTE_SETTINGS_URL = "https://raw.githubusercontent.com/TwidereProject/Twidere-Android/master/twidere/src/main/assets/data/default_features.json";
|
||||
|
||||
@JsonField(name = "media_link_counts_in_status")
|
||||
boolean mediaLinkCountsInStatus = true;
|
||||
|
||||
|
||||
public boolean isMediaLinkCountsInStatus() {
|
||||
return mediaLinkCountsInStatus;
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public boolean loadRemoteSettings(RestHttpClient client) throws IOException {
|
||||
HttpRequest request = new HttpRequest.Builder().method(GET.METHOD).url(REMOTE_SETTINGS_URL).build();
|
||||
final HttpResponse response = client.newCall(request).execute();
|
||||
try {
|
||||
final JsonMapper<DefaultFeatures> mapper = LoganSquare.mapperFor(DefaultFeatures.class);
|
||||
final JsonParser jsonParser = LoganSquare.JSON_FACTORY.createParser(response.getBody().stream());
|
||||
if (jsonParser.getCurrentToken() == null) {
|
||||
jsonParser.nextToken();
|
||||
}
|
||||
if (jsonParser.getCurrentToken() != JsonToken.START_OBJECT) {
|
||||
jsonParser.skipChildren();
|
||||
return false;
|
||||
}
|
||||
while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
|
||||
String fieldName = jsonParser.getCurrentName();
|
||||
jsonParser.nextToken();
|
||||
mapper.parseField(this, fieldName, jsonParser);
|
||||
jsonParser.skipChildren();
|
||||
}
|
||||
} finally {
|
||||
response.close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public void load(SharedPreferences preferences) {
|
||||
mediaLinkCountsInStatus = preferences.getBoolean(KEY_MEDIA_LINK_COUNTS_IN_STATUS,
|
||||
mediaLinkCountsInStatus);
|
||||
}
|
||||
|
||||
public void save(SharedPreferences preferences) {
|
||||
final SharedPreferences.Editor editor = preferences.edit();
|
||||
editor.putBoolean(KEY_MEDIA_LINK_COUNTS_IN_STATUS, mediaLinkCountsInStatus);
|
||||
editor.apply();
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
package org.mariotaku.twidere.model;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/2/14.
|
||||
*/
|
||||
public interface RefreshTaskParam {
|
||||
@NonNull
|
||||
UserKey[] getAccountKeys();
|
||||
|
||||
@Nullable
|
||||
String[] getMaxIds();
|
||||
|
||||
@Nullable
|
||||
String[] getSinceIds();
|
||||
|
||||
@Nullable
|
||||
long[] getMaxSortIds();
|
||||
|
||||
@Nullable
|
||||
long[] getSinceSortIds();
|
||||
|
||||
boolean hasMaxIds();
|
||||
|
||||
boolean hasSinceIds();
|
||||
|
||||
boolean isLoadingMore();
|
||||
|
||||
boolean shouldAbort();
|
||||
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
package org.mariotaku.twidere.model;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/2/14.
|
||||
*/
|
||||
public abstract class SimpleRefreshTaskParam implements RefreshTaskParam {
|
||||
|
||||
UserKey[] cached;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public final UserKey[] getAccountKeys() {
|
||||
if (cached != null) return cached;
|
||||
return cached = getAccountKeysWorker();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public abstract UserKey[] getAccountKeysWorker();
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String[] getMaxIds() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String[] getSinceIds() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMaxIds() {
|
||||
return getMaxIds() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSinceIds() {
|
||||
return getSinceIds() != null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public long[] getSinceSortIds() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public long[] getMaxSortIds() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoadingMore() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldAbort() {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
/*
|
||||
* 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.receiver;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.mariotaku.twidere.Constants;
|
||||
import org.mariotaku.twidere.annotation.CustomTabType;
|
||||
import org.mariotaku.twidere.annotation.NotificationType;
|
||||
import org.mariotaku.twidere.annotation.ReadPositionTag;
|
||||
import org.mariotaku.twidere.model.StringLongPair;
|
||||
import org.mariotaku.twidere.model.UserKey;
|
||||
import org.mariotaku.twidere.util.CustomTabUtils;
|
||||
import org.mariotaku.twidere.util.ReadStateManager;
|
||||
import org.mariotaku.twidere.util.UriExtraUtils;
|
||||
import org.mariotaku.twidere.util.Utils;
|
||||
import org.mariotaku.twidere.util.dagger.DependencyHolder;
|
||||
|
||||
import edu.tsinghua.hotmobi.HotMobiLogger;
|
||||
import edu.tsinghua.hotmobi.model.NotificationEvent;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 15/4/4.
|
||||
*/
|
||||
public class NotificationReceiver extends BroadcastReceiver implements Constants {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
final String action = intent.getAction();
|
||||
if (action == null) return;
|
||||
switch (action) {
|
||||
case BROADCAST_NOTIFICATION_DELETED: {
|
||||
final Uri uri = intent.getData();
|
||||
if (uri == null) return;
|
||||
DependencyHolder holder = DependencyHolder.get(context);
|
||||
@NotificationType
|
||||
final String notificationType = uri.getQueryParameter(QUERY_PARAM_NOTIFICATION_TYPE);
|
||||
final UserKey accountKey = UserKey.valueOf(uri.getQueryParameter(QUERY_PARAM_ACCOUNT_KEY));
|
||||
final long itemId = NumberUtils.toLong(UriExtraUtils.getExtra(uri, "item_id"), -1);
|
||||
final long itemUserId = NumberUtils.toLong(UriExtraUtils.getExtra(uri, "item_user_id"), -1);
|
||||
final boolean itemUserFollowing = Boolean.parseBoolean(UriExtraUtils.getExtra(uri, "item_user_following"));
|
||||
final long timestamp = NumberUtils.toLong(uri.getQueryParameter(QUERY_PARAM_TIMESTAMP), -1);
|
||||
if (CustomTabType.NOTIFICATIONS_TIMELINE.equals(CustomTabUtils.getTabTypeAlias(notificationType))
|
||||
&& accountKey != null && itemId != -1 && timestamp != -1) {
|
||||
final HotMobiLogger logger = holder.getHotMobiLogger();
|
||||
logger.log(accountKey, NotificationEvent.deleted(context, timestamp, notificationType, accountKey,
|
||||
itemId, itemUserId, itemUserFollowing));
|
||||
}
|
||||
final ReadStateManager manager = holder.getReadStateManager();
|
||||
final String paramReadPosition, paramReadPositions;
|
||||
@ReadPositionTag
|
||||
final String tag = getPositionTag(notificationType);
|
||||
if (tag != null && !TextUtils.isEmpty(paramReadPosition = uri.getQueryParameter(QUERY_PARAM_READ_POSITION))) {
|
||||
final long def = -1;
|
||||
manager.setPosition(Utils.getReadPositionTagWithAccount(tag, accountKey),
|
||||
NumberUtils.toLong(paramReadPosition, def));
|
||||
} else if (!TextUtils.isEmpty(paramReadPositions = uri.getQueryParameter(QUERY_PARAM_READ_POSITIONS))) {
|
||||
try {
|
||||
final StringLongPair[] pairs = StringLongPair.valuesOf(paramReadPositions);
|
||||
for (StringLongPair pair : pairs) {
|
||||
manager.setPosition(tag, pair.getKey(), pair.getValue());
|
||||
}
|
||||
} catch (NumberFormatException ignore) {
|
||||
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ReadPositionTag
|
||||
@Nullable
|
||||
private static String getPositionTag(@Nullable @NotificationType String type) {
|
||||
if (type == null) return null;
|
||||
switch (type) {
|
||||
case NotificationType.HOME_TIMELINE:
|
||||
return ReadPositionTag.HOME_TIMELINE;
|
||||
case NotificationType.INTERACTIONS:
|
||||
return ReadPositionTag.ACTIVITIES_ABOUT_ME;
|
||||
case NotificationType.DIRECT_MESSAGES: {
|
||||
return ReadPositionTag.DIRECT_MESSAGES;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -39,12 +39,12 @@ public class GetActivitiesAboutMeTask extends GetActivitiesTask {
|
||||
@Override
|
||||
protected void saveReadPosition(@NonNull UserKey accountKey, ParcelableCredentials credentials, @NonNull MicroBlog twitter) {
|
||||
if (ParcelableAccount.Type.TWITTER.equals(ParcelableAccountUtils.getAccountType(credentials))) {
|
||||
if (Utils.isOfficialCredentials(context, credentials)) {
|
||||
if (Utils.isOfficialCredentials(getContext(), credentials)) {
|
||||
try {
|
||||
CursorTimestampResponse response = twitter.getActivitiesAboutMeUnread(true);
|
||||
final String tag = Utils.getReadPositionTagWithAccount(ReadPositionTag.ACTIVITIES_ABOUT_ME,
|
||||
accountKey);
|
||||
readStateManager.setPosition(tag, response.getCursor(), false);
|
||||
getReadStateManager().setPosition(tag, response.getCursor(), false);
|
||||
} catch (MicroBlogException e) {
|
||||
// Ignore
|
||||
}
|
||||
@ -56,7 +56,7 @@ public class GetActivitiesAboutMeTask extends GetActivitiesTask {
|
||||
protected ResponseList<Activity> getActivities(@NonNull final MicroBlog twitter,
|
||||
@NonNull final ParcelableCredentials credentials,
|
||||
@NonNull final Paging paging) throws MicroBlogException {
|
||||
if (Utils.isOfficialCredentials(context, credentials)) {
|
||||
if (Utils.isOfficialCredentials(getContext(), credentials)) {
|
||||
return twitter.getActivitiesAboutMe(paging);
|
||||
}
|
||||
final ResponseList<Activity> activities = new ResponseList<>();
|
||||
|
@ -1,52 +0,0 @@
|
||||
package org.mariotaku.twidere.task;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.mariotaku.microblog.library.MicroBlog;
|
||||
import org.mariotaku.microblog.library.MicroBlogException;
|
||||
import org.mariotaku.microblog.library.twitter.model.Paging;
|
||||
import org.mariotaku.microblog.library.twitter.model.ResponseList;
|
||||
import org.mariotaku.microblog.library.twitter.model.Status;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore;
|
||||
import org.mariotaku.twidere.task.twitter.GetStatusesTask;
|
||||
import org.mariotaku.twidere.util.ErrorInfoStore;
|
||||
|
||||
import edu.tsinghua.hotmobi.model.TimelineType;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/2/11.
|
||||
*/
|
||||
public class GetHomeTimelineTask extends GetStatusesTask {
|
||||
|
||||
public GetHomeTimelineTask(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ResponseList<Status> getStatuses(final MicroBlog twitter, final Paging paging)
|
||||
throws MicroBlogException {
|
||||
return twitter.getHomeTimeline(paging);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Uri getContentUri() {
|
||||
return TwidereDataStore.Statuses.CONTENT_URI;
|
||||
}
|
||||
|
||||
@TimelineType
|
||||
@Override
|
||||
protected String getTimelineType() {
|
||||
return TimelineType.HOME;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected String getErrorInfoKey() {
|
||||
return ErrorInfoStore.KEY_HOME_TIMELINE;
|
||||
}
|
||||
|
||||
}
|
@ -1,239 +0,0 @@
|
||||
package org.mariotaku.twidere.task.twitter;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.util.Log;
|
||||
|
||||
import com.squareup.otto.Bus;
|
||||
|
||||
import org.mariotaku.abstask.library.AbstractTask;
|
||||
import org.mariotaku.microblog.library.MicroBlog;
|
||||
import org.mariotaku.microblog.library.MicroBlogException;
|
||||
import org.mariotaku.microblog.library.twitter.model.Activity;
|
||||
import org.mariotaku.microblog.library.twitter.model.Paging;
|
||||
import org.mariotaku.microblog.library.twitter.model.ResponseList;
|
||||
import org.mariotaku.sqliteqb.library.Expression;
|
||||
import org.mariotaku.twidere.BuildConfig;
|
||||
import org.mariotaku.twidere.Constants;
|
||||
import org.mariotaku.twidere.model.ParcelableActivity;
|
||||
import org.mariotaku.twidere.model.ParcelableCredentials;
|
||||
import org.mariotaku.twidere.model.RefreshTaskParam;
|
||||
import org.mariotaku.twidere.model.UserKey;
|
||||
import org.mariotaku.twidere.model.message.GetActivitiesTaskEvent;
|
||||
import org.mariotaku.twidere.model.util.ParcelableActivityUtils;
|
||||
import org.mariotaku.twidere.model.util.ParcelableCredentialsUtils;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Activities;
|
||||
import org.mariotaku.twidere.util.ContentValuesCreator;
|
||||
import org.mariotaku.twidere.util.DataStoreUtils;
|
||||
import org.mariotaku.twidere.util.ErrorInfoStore;
|
||||
import org.mariotaku.twidere.util.MicroBlogAPIFactory;
|
||||
import org.mariotaku.twidere.util.ReadStateManager;
|
||||
import org.mariotaku.twidere.util.SharedPreferencesWrapper;
|
||||
import org.mariotaku.twidere.util.UriUtils;
|
||||
import org.mariotaku.twidere.util.UserColorNameManager;
|
||||
import org.mariotaku.twidere.util.content.ContentResolverUtils;
|
||||
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/1/4.
|
||||
*/
|
||||
public abstract class GetActivitiesTask extends AbstractTask<RefreshTaskParam, Object, Object>
|
||||
implements Constants {
|
||||
|
||||
protected final Context context;
|
||||
@Inject
|
||||
protected SharedPreferencesWrapper preferences;
|
||||
@Inject
|
||||
protected Bus bus;
|
||||
@Inject
|
||||
protected ErrorInfoStore errorInfoStore;
|
||||
@Inject
|
||||
protected ReadStateManager readStateManager;
|
||||
@Inject
|
||||
protected UserColorNameManager userColorNameManager;
|
||||
|
||||
public GetActivitiesTask(Context context) {
|
||||
this.context = context;
|
||||
GeneralComponentHelper.build(context).inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doLongOperation(RefreshTaskParam param) {
|
||||
if (param.shouldAbort()) return null;
|
||||
final UserKey[] accountIds = param.getAccountKeys();
|
||||
final String[] maxIds = param.getMaxIds();
|
||||
final long[] maxSortIds = param.getMaxSortIds();
|
||||
final String[] sinceIds = param.getSinceIds();
|
||||
final ContentResolver cr = context.getContentResolver();
|
||||
final int loadItemLimit = preferences.getInt(KEY_LOAD_ITEM_LIMIT);
|
||||
boolean saveReadPosition = false;
|
||||
for (int i = 0; i < accountIds.length; i++) {
|
||||
final UserKey accountKey = accountIds[i];
|
||||
final boolean noItemsBefore = DataStoreUtils.getActivitiesCount(context, getContentUri(),
|
||||
accountKey) <= 0;
|
||||
final ParcelableCredentials credentials = ParcelableCredentialsUtils.getCredentials(context,
|
||||
accountKey);
|
||||
if (credentials == null) continue;
|
||||
final MicroBlog twitter = MicroBlogAPIFactory.getInstance(context, credentials, true,
|
||||
true);
|
||||
if (twitter == null) continue;
|
||||
final Paging paging = new Paging();
|
||||
paging.count(loadItemLimit);
|
||||
String maxId = null;
|
||||
long maxSortId = -1;
|
||||
if (maxIds != null) {
|
||||
maxId = maxIds[i];
|
||||
if (maxSortIds != null) {
|
||||
maxSortId = maxSortIds[i];
|
||||
}
|
||||
if (maxId != null) {
|
||||
paging.maxId(maxId);
|
||||
}
|
||||
}
|
||||
String sinceId = null;
|
||||
if (sinceIds != null) {
|
||||
sinceId = sinceIds[i];
|
||||
if (sinceId != null) {
|
||||
paging.sinceId(sinceId);
|
||||
if (maxIds == null || maxId == null) {
|
||||
paging.setLatestResults(true);
|
||||
saveReadPosition = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// We should delete old activities has intersection with new items
|
||||
try {
|
||||
final ResponseList<Activity> activities = getActivities(twitter, credentials, paging);
|
||||
storeActivities(cr, loadItemLimit, credentials, noItemsBefore, activities, sinceId,
|
||||
maxId, false);
|
||||
if (saveReadPosition) {
|
||||
saveReadPosition(accountKey, credentials, twitter);
|
||||
}
|
||||
errorInfoStore.remove(getErrorInfoKey(), accountKey);
|
||||
} catch (MicroBlogException e) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.w(LOGTAG, e);
|
||||
}
|
||||
if (e.getErrorCode() == 220) {
|
||||
errorInfoStore.put(getErrorInfoKey(), accountKey,
|
||||
ErrorInfoStore.CODE_NO_ACCESS_FOR_CREDENTIALS);
|
||||
} else if (e.isCausedByNetworkIssue()) {
|
||||
errorInfoStore.put(getErrorInfoKey(), accountKey,
|
||||
ErrorInfoStore.CODE_NETWORK_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected abstract String getErrorInfoKey();
|
||||
|
||||
private void storeActivities(ContentResolver cr, int loadItemLimit, ParcelableCredentials credentials,
|
||||
boolean noItemsBefore, ResponseList<Activity> activities,
|
||||
final String sinceId, final String maxId, boolean notify) {
|
||||
long[] deleteBound = new long[2];
|
||||
Arrays.fill(deleteBound, -1);
|
||||
List<ContentValues> valuesList = new ArrayList<>();
|
||||
int minIdx = -1;
|
||||
long minPositionKey = -1;
|
||||
if (!activities.isEmpty()) {
|
||||
final long firstSortId = activities.get(0).getCreatedAt().getTime();
|
||||
final long lastSortId = activities.get(activities.size() - 1).getCreatedAt().getTime();
|
||||
// Get id diff of first and last item
|
||||
final long sortDiff = firstSortId - lastSortId;
|
||||
for (int i = 0, j = activities.size(); i < j; i++) {
|
||||
Activity item = activities.get(i);
|
||||
final ParcelableActivity activity = ParcelableActivityUtils.INSTANCE.fromActivity(item,
|
||||
credentials.account_key, false);
|
||||
activity.position_key = GetStatusesTask.getPositionKey(activity.timestamp,
|
||||
activity.timestamp, lastSortId, sortDiff, i, j);
|
||||
if (deleteBound[0] < 0) {
|
||||
deleteBound[0] = activity.min_sort_position;
|
||||
} else {
|
||||
deleteBound[0] = Math.min(deleteBound[0], activity.min_sort_position);
|
||||
}
|
||||
if (deleteBound[1] < 0) {
|
||||
deleteBound[1] = activity.max_sort_position;
|
||||
} else {
|
||||
deleteBound[1] = Math.max(deleteBound[1], activity.max_sort_position);
|
||||
}
|
||||
if (minIdx == -1 || item.compareTo(activities.get(minIdx)) < 0) {
|
||||
minIdx = i;
|
||||
minPositionKey = activity.position_key;
|
||||
}
|
||||
|
||||
activity.inserted_date = System.currentTimeMillis();
|
||||
final ContentValues values = ContentValuesCreator.createActivity(activity,
|
||||
credentials, userColorNameManager);
|
||||
valuesList.add(values);
|
||||
}
|
||||
}
|
||||
int olderCount = -1;
|
||||
if (minPositionKey > 0) {
|
||||
olderCount = DataStoreUtils.getActivitiesCount(context, getContentUri(), minPositionKey,
|
||||
Activities.POSITION_KEY, false, credentials.account_key);
|
||||
}
|
||||
final Uri writeUri = UriUtils.appendQueryParameters(getContentUri(), QUERY_PARAM_NOTIFY,
|
||||
notify);
|
||||
if (deleteBound[0] > 0 && deleteBound[1] > 0) {
|
||||
final Expression where = Expression.and(
|
||||
Expression.equalsArgs(Activities.ACCOUNT_KEY),
|
||||
Expression.greaterEqualsArgs(Activities.MIN_SORT_POSITION),
|
||||
Expression.lesserEqualsArgs(Activities.MAX_SORT_POSITION)
|
||||
);
|
||||
final String[] whereArgs = {credentials.account_key.toString(), String.valueOf(deleteBound[0]),
|
||||
String.valueOf(deleteBound[1])};
|
||||
int rowsDeleted = cr.delete(writeUri, where.getSQL(), whereArgs);
|
||||
// Why loadItemLimit / 2? because it will not acting strange in most cases
|
||||
boolean insertGap = valuesList.size() >= loadItemLimit && !noItemsBefore && olderCount > 0
|
||||
&& rowsDeleted <= 0 && activities.size() > loadItemLimit / 2;
|
||||
if (insertGap && !valuesList.isEmpty()) {
|
||||
valuesList.get(valuesList.size() - 1).put(Activities.IS_GAP, true);
|
||||
}
|
||||
}
|
||||
ContentResolverUtils.bulkInsert(cr, writeUri, valuesList);
|
||||
|
||||
if (maxId != null && sinceId == null) {
|
||||
final ContentValues noGapValues = new ContentValues();
|
||||
noGapValues.put(Activities.IS_GAP, false);
|
||||
final String noGapWhere = Expression.and(Expression.equalsArgs(Activities.ACCOUNT_KEY),
|
||||
Expression.equalsArgs(Activities.MIN_REQUEST_POSITION),
|
||||
Expression.equalsArgs(Activities.MAX_REQUEST_POSITION)).getSQL();
|
||||
final String[] noGapWhereArgs = {credentials.toString(), maxId, maxId};
|
||||
cr.update(writeUri, noGapValues, noGapWhere, noGapWhereArgs);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void saveReadPosition(@NonNull final UserKey accountId,
|
||||
ParcelableCredentials credentials, @NonNull final MicroBlog twitter);
|
||||
|
||||
protected abstract ResponseList<Activity> getActivities(@NonNull final MicroBlog twitter,
|
||||
@NonNull final ParcelableCredentials credentials,
|
||||
@NonNull final Paging paging)
|
||||
throws MicroBlogException;
|
||||
|
||||
@Override
|
||||
public void afterExecute(Object handler, Object result) {
|
||||
context.getContentResolver().notifyChange(getContentUri(), null);
|
||||
bus.post(new GetActivitiesTaskEvent(getContentUri(), false, null));
|
||||
}
|
||||
|
||||
protected abstract Uri getContentUri();
|
||||
|
||||
@UiThread
|
||||
@Override
|
||||
public void beforeExecute() {
|
||||
bus.post(new GetActivitiesTaskEvent(getContentUri(), true, null));
|
||||
}
|
||||
}
|
@ -1,273 +0,0 @@
|
||||
package org.mariotaku.twidere.task.twitter;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import com.squareup.otto.Bus;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.mariotaku.abstask.library.AbstractTask;
|
||||
import org.mariotaku.abstask.library.TaskStarter;
|
||||
import org.mariotaku.microblog.library.MicroBlog;
|
||||
import org.mariotaku.microblog.library.MicroBlogException;
|
||||
import org.mariotaku.microblog.library.twitter.model.Paging;
|
||||
import org.mariotaku.microblog.library.twitter.model.ResponseList;
|
||||
import org.mariotaku.microblog.library.twitter.model.Status;
|
||||
import org.mariotaku.sqliteqb.library.Columns;
|
||||
import org.mariotaku.sqliteqb.library.Expression;
|
||||
import org.mariotaku.twidere.BuildConfig;
|
||||
import org.mariotaku.twidere.Constants;
|
||||
import org.mariotaku.twidere.model.ParcelableCredentials;
|
||||
import org.mariotaku.twidere.model.ParcelableStatus;
|
||||
import org.mariotaku.twidere.model.ParcelableStatusValuesCreator;
|
||||
import org.mariotaku.twidere.model.RefreshTaskParam;
|
||||
import org.mariotaku.twidere.model.UserKey;
|
||||
import org.mariotaku.twidere.model.message.GetStatusesTaskEvent;
|
||||
import org.mariotaku.twidere.model.util.ParcelableCredentialsUtils;
|
||||
import org.mariotaku.twidere.model.util.ParcelableStatusUtils;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.AccountSupportColumns;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses;
|
||||
import org.mariotaku.twidere.task.CacheUsersStatusesTask;
|
||||
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
|
||||
import org.mariotaku.twidere.util.DataStoreUtils;
|
||||
import org.mariotaku.twidere.util.ErrorInfoStore;
|
||||
import org.mariotaku.twidere.util.MicroBlogAPIFactory;
|
||||
import org.mariotaku.twidere.util.SharedPreferencesWrapper;
|
||||
import org.mariotaku.twidere.util.TwitterWrapper;
|
||||
import org.mariotaku.twidere.util.UriUtils;
|
||||
import org.mariotaku.twidere.util.UserColorNameManager;
|
||||
import org.mariotaku.twidere.util.content.ContentResolverUtils;
|
||||
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import edu.tsinghua.hotmobi.HotMobiLogger;
|
||||
import edu.tsinghua.hotmobi.model.RefreshEvent;
|
||||
import edu.tsinghua.hotmobi.model.TimelineType;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/1/2.
|
||||
*/
|
||||
public abstract class GetStatusesTask extends AbstractTask<RefreshTaskParam,
|
||||
List<TwitterWrapper.StatusListResponse>, Object> implements Constants {
|
||||
|
||||
protected final Context context;
|
||||
@Inject
|
||||
protected SharedPreferencesWrapper preferences;
|
||||
@Inject
|
||||
protected Bus bus;
|
||||
@Inject
|
||||
protected ErrorInfoStore errorInfoStore;
|
||||
@Inject
|
||||
protected UserColorNameManager manager;
|
||||
@Inject
|
||||
protected AsyncTwitterWrapper wrapper;
|
||||
|
||||
public GetStatusesTask(Context context) {
|
||||
this.context = context;
|
||||
GeneralComponentHelper.build(context).inject(this);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public abstract ResponseList<Status> getStatuses(MicroBlog twitter, Paging paging)
|
||||
throws MicroBlogException;
|
||||
|
||||
@NonNull
|
||||
protected abstract Uri getContentUri();
|
||||
|
||||
@TimelineType
|
||||
protected abstract String getTimelineType();
|
||||
|
||||
@Override
|
||||
public void afterExecute(Object handler, List<TwitterWrapper.StatusListResponse> result) {
|
||||
context.getContentResolver().notifyChange(getContentUri(), null);
|
||||
bus.post(new GetStatusesTaskEvent(getContentUri(), false, AsyncTwitterWrapper.getException(result)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void beforeExecute() {
|
||||
bus.post(new GetStatusesTaskEvent(getContentUri(), true, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TwitterWrapper.StatusListResponse> doLongOperation(final RefreshTaskParam param) {
|
||||
if (param.shouldAbort()) return Collections.emptyList();
|
||||
final UserKey[] accountKeys = param.getAccountKeys();
|
||||
final String[] maxIds = param.getMaxIds();
|
||||
final String[] sinceIds = param.getSinceIds();
|
||||
final long[] maxSortIds = param.getMaxSortIds();
|
||||
final long[] sinceSortIds = param.getSinceSortIds();
|
||||
final List<TwitterWrapper.StatusListResponse> result = new ArrayList<>();
|
||||
int idx = 0;
|
||||
final int loadItemLimit = preferences.getInt(KEY_LOAD_ITEM_LIMIT, DEFAULT_LOAD_ITEM_LIMIT);
|
||||
for (final UserKey accountKey : accountKeys) {
|
||||
final ParcelableCredentials credentials = ParcelableCredentialsUtils.getCredentials(context,
|
||||
accountKey);
|
||||
if (credentials == null) continue;
|
||||
final MicroBlog twitter = MicroBlogAPIFactory.getInstance(context, credentials,
|
||||
true, true);
|
||||
if (twitter == null) continue;
|
||||
try {
|
||||
final Paging paging = new Paging();
|
||||
paging.count(loadItemLimit);
|
||||
final String maxId, sinceId;
|
||||
long maxSortId = -1, sinceSortId = -1;
|
||||
if (maxIds != null && maxIds[idx] != null) {
|
||||
maxId = maxIds[idx];
|
||||
paging.maxId(maxId);
|
||||
if (maxSortIds != null) {
|
||||
maxSortId = maxSortIds[idx];
|
||||
}
|
||||
} else {
|
||||
maxSortId = -1;
|
||||
maxId = null;
|
||||
}
|
||||
if (sinceIds != null && sinceIds[idx] != null) {
|
||||
sinceId = sinceIds[idx];
|
||||
long sinceIdLong = NumberUtils.toLong(sinceId, -1);
|
||||
//TODO handle non-twitter case
|
||||
if (sinceIdLong != -1) {
|
||||
paging.sinceId(String.valueOf(sinceIdLong - 1));
|
||||
} else {
|
||||
paging.sinceId(sinceId);
|
||||
}
|
||||
if (sinceSortIds != null) {
|
||||
sinceSortId = sinceSortIds[idx];
|
||||
}
|
||||
if (maxIds == null || sinceIds[idx] == null) {
|
||||
paging.setLatestResults(true);
|
||||
}
|
||||
} else {
|
||||
sinceId = null;
|
||||
}
|
||||
final List<Status> statuses = getStatuses(twitter, paging);
|
||||
storeStatus(accountKey, credentials, statuses, sinceId, maxId, sinceSortId,
|
||||
maxSortId, loadItemLimit, false);
|
||||
// TODO cache related data and preload
|
||||
final CacheUsersStatusesTask cacheTask = new CacheUsersStatusesTask(context);
|
||||
cacheTask.setParams(new TwitterWrapper.StatusListResponse(accountKey, statuses));
|
||||
TaskStarter.execute(cacheTask);
|
||||
errorInfoStore.remove(getErrorInfoKey(), accountKey.getId());
|
||||
} catch (final MicroBlogException e) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.w(LOGTAG, e);
|
||||
}
|
||||
if (e.isCausedByNetworkIssue()) {
|
||||
errorInfoStore.put(getErrorInfoKey(), accountKey.getId(),
|
||||
ErrorInfoStore.CODE_NETWORK_ERROR);
|
||||
}
|
||||
result.add(new TwitterWrapper.StatusListResponse(accountKey, e));
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected abstract String getErrorInfoKey();
|
||||
|
||||
private void storeStatus(@NonNull final UserKey accountKey, ParcelableCredentials credentials,
|
||||
@NonNull final List<Status> statuses,
|
||||
final String sinceId, final String maxId,
|
||||
final long sinceSortId, final long maxSortId,
|
||||
int loadItemLimit, final boolean notify) {
|
||||
final Uri uri = getContentUri();
|
||||
final Uri writeUri = UriUtils.appendQueryParameters(uri, QUERY_PARAM_NOTIFY, notify);
|
||||
final ContentResolver resolver = context.getContentResolver();
|
||||
final boolean noItemsBefore = DataStoreUtils.getStatusCount(context, uri, accountKey) <= 0;
|
||||
final ContentValues[] values = new ContentValues[statuses.size()];
|
||||
final String[] statusIds = new String[statuses.size()];
|
||||
int minIdx = -1;
|
||||
long minPositionKey = -1;
|
||||
boolean hasIntersection = false;
|
||||
if (!statuses.isEmpty()) {
|
||||
final long firstSortId = statuses.get(0).getSortId();
|
||||
final long lastSortId = statuses.get(statuses.size() - 1).getSortId();
|
||||
// Get id diff of first and last item
|
||||
final long sortDiff = firstSortId - lastSortId;
|
||||
|
||||
for (int i = 0, j = statuses.size(); i < j; i++) {
|
||||
final Status item = statuses.get(i);
|
||||
final ParcelableStatus status = ParcelableStatusUtils.INSTANCE.fromStatus(item, accountKey,
|
||||
false);
|
||||
ParcelableStatusUtils.INSTANCE.updateExtraInformation(status, credentials, manager);
|
||||
status.position_key = getPositionKey(status.timestamp, status.sort_id, lastSortId,
|
||||
sortDiff, i, j);
|
||||
status.inserted_date = System.currentTimeMillis();
|
||||
values[i] = ParcelableStatusValuesCreator.create(status);
|
||||
if (minIdx == -1 || item.compareTo(statuses.get(minIdx)) < 0) {
|
||||
minIdx = i;
|
||||
minPositionKey = status.position_key;
|
||||
}
|
||||
if (sinceId != null && item.getSortId() <= sinceSortId) {
|
||||
hasIntersection = true;
|
||||
}
|
||||
statusIds[i] = item.getId();
|
||||
}
|
||||
}
|
||||
// Delete all rows conflicting before new data inserted.
|
||||
final Expression accountWhere = Expression.equalsArgs(AccountSupportColumns.ACCOUNT_KEY);
|
||||
final Expression statusWhere = Expression.inArgs(new Columns.Column(Statuses.STATUS_ID),
|
||||
statusIds.length);
|
||||
final String deleteWhere = Expression.and(accountWhere, statusWhere).getSQL();
|
||||
final String[] deleteWhereArgs = new String[statusIds.length + 1];
|
||||
System.arraycopy(statusIds, 0, deleteWhereArgs, 1, statusIds.length);
|
||||
deleteWhereArgs[0] = accountKey.toString();
|
||||
int olderCount = -1;
|
||||
if (minPositionKey > 0) {
|
||||
olderCount = DataStoreUtils.getStatusesCount(context, uri, null, minPositionKey,
|
||||
Statuses.POSITION_KEY, false, new UserKey[]{accountKey});
|
||||
}
|
||||
final int rowsDeleted = resolver.delete(writeUri, deleteWhere, deleteWhereArgs);
|
||||
|
||||
// BEGIN HotMobi
|
||||
final RefreshEvent event = RefreshEvent.create(context, statusIds, getTimelineType());
|
||||
HotMobiLogger.getInstance(context).log(accountKey, event);
|
||||
// END HotMobi
|
||||
|
||||
// Insert a gap.
|
||||
final boolean deletedOldGap = rowsDeleted > 0 && ArrayUtils.contains(statusIds, maxId);
|
||||
final boolean noRowsDeleted = rowsDeleted == 0;
|
||||
// Why loadItemLimit / 2? because it will not acting strange in most cases
|
||||
final boolean insertGap = minIdx != -1 && olderCount > 0 && (noRowsDeleted || deletedOldGap)
|
||||
&& !noItemsBefore && !hasIntersection && statuses.size() > loadItemLimit / 2;
|
||||
if (insertGap) {
|
||||
values[minIdx].put(Statuses.IS_GAP, true);
|
||||
}
|
||||
// Insert previously fetched items.
|
||||
ContentResolverUtils.bulkInsert(resolver, writeUri, values);
|
||||
|
||||
if (maxId != null && sinceId == null) {
|
||||
final ContentValues noGapValues = new ContentValues();
|
||||
noGapValues.put(Statuses.IS_GAP, false);
|
||||
final String noGapWhere = Expression.and(Expression.equalsArgs(Statuses.ACCOUNT_KEY),
|
||||
Expression.equalsArgs(Statuses.STATUS_ID)).getSQL();
|
||||
final String[] noGapWhereArgs = {accountKey.toString(), maxId};
|
||||
resolver.update(writeUri, noGapValues, noGapWhere, noGapWhereArgs);
|
||||
}
|
||||
}
|
||||
|
||||
public static long getPositionKey(long timestamp, long sortId, long lastSortId, long sortDiff,
|
||||
int position, int count) {
|
||||
if (sortDiff == 0) return timestamp;
|
||||
int extraValue;
|
||||
if (sortDiff > 0) {
|
||||
// descent sorted by time
|
||||
extraValue = count - 1 - position;
|
||||
} else {
|
||||
// ascent sorted by time
|
||||
extraValue = position;
|
||||
}
|
||||
return timestamp + (sortId - lastSortId) * (499 - count) / sortDiff + extraValue;
|
||||
}
|
||||
|
||||
}
|
@ -1,114 +0,0 @@
|
||||
package org.mariotaku.twidere.util;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.mariotaku.restfu.http.RestHttpClient;
|
||||
import org.mariotaku.restfu.okhttp3.OkHttpRestClient;
|
||||
import org.mariotaku.twidere.Constants;
|
||||
import org.mariotaku.twidere.util.dagger.DependencyHolder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import okhttp3.Authenticator;
|
||||
import okhttp3.ConnectionPool;
|
||||
import okhttp3.Credentials;
|
||||
import okhttp3.Dns;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.Route;
|
||||
|
||||
import static android.text.TextUtils.isEmpty;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/1/27.
|
||||
*/
|
||||
public class HttpClientFactory implements Constants {
|
||||
private HttpClientFactory() {
|
||||
}
|
||||
|
||||
public static RestHttpClient createRestHttpClient(final Context context,
|
||||
final SharedPreferencesWrapper prefs, final Dns dns,
|
||||
final ConnectionPool connectionPool) {
|
||||
final OkHttpClient.Builder builder = new OkHttpClient.Builder();
|
||||
initOkHttpClient(context, prefs, builder, dns, connectionPool);
|
||||
return new OkHttpRestClient(builder.build());
|
||||
}
|
||||
|
||||
public static void initOkHttpClient(final Context context, final SharedPreferencesWrapper prefs,
|
||||
final OkHttpClient.Builder builder, final Dns dns,
|
||||
final ConnectionPool connectionPool) {
|
||||
updateHttpClientConfiguration(context, builder, prefs, dns, connectionPool);
|
||||
DebugModeUtils.initForOkHttpClient(builder);
|
||||
}
|
||||
|
||||
@SuppressLint("SSLCertificateSocketFactoryGetInsecure")
|
||||
public static void updateHttpClientConfiguration(final Context context,
|
||||
final OkHttpClient.Builder builder,
|
||||
final SharedPreferencesWrapper prefs, final Dns dns,
|
||||
final ConnectionPool connectionPool) {
|
||||
final boolean enableProxy = prefs.getBoolean(KEY_ENABLE_PROXY, false);
|
||||
builder.connectTimeout(prefs.getInt(KEY_CONNECTION_TIMEOUT, 10), TimeUnit.SECONDS);
|
||||
builder.connectionPool(connectionPool);
|
||||
if (enableProxy) {
|
||||
final String proxyType = prefs.getString(KEY_PROXY_TYPE, null);
|
||||
final String proxyHost = prefs.getString(KEY_PROXY_HOST, null);
|
||||
final int proxyPort = NumberUtils.toInt(prefs.getString(KEY_PROXY_PORT, null), -1);
|
||||
if (!isEmpty(proxyHost) && TwidereMathUtils.inRange(proxyPort, 0, 65535,
|
||||
TwidereMathUtils.RANGE_INCLUSIVE_INCLUSIVE)) {
|
||||
final Proxy.Type type = getProxyType(proxyType);
|
||||
if (type != Proxy.Type.DIRECT) {
|
||||
builder.proxy(new Proxy(type, InetSocketAddress.createUnresolved(proxyHost, proxyPort)));
|
||||
}
|
||||
}
|
||||
final String username = prefs.getString(KEY_PROXY_USERNAME, null);
|
||||
final String password = prefs.getString(KEY_PROXY_PASSWORD, null);
|
||||
builder.authenticator(new Authenticator() {
|
||||
@Override
|
||||
public Request authenticate(Route route, Response response) throws IOException {
|
||||
final Request.Builder builder = response.request().newBuilder();
|
||||
if (response.code() == 407) {
|
||||
if (!TextUtils.isEmpty(username) && !TextUtils.isEmpty(password)) {
|
||||
final String credential = Credentials.basic(username, password);
|
||||
builder.header("Proxy-Authorization", credential);
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
builder.dns(dns);
|
||||
}
|
||||
|
||||
private static Proxy.Type getProxyType(String proxyType) {
|
||||
if (proxyType == null) return Proxy.Type.DIRECT;
|
||||
switch (proxyType.toLowerCase()) {
|
||||
// case "socks": {
|
||||
// return Proxy.Type.SOCKS;
|
||||
// }
|
||||
case "http": {
|
||||
return Proxy.Type.HTTP;
|
||||
}
|
||||
}
|
||||
return Proxy.Type.DIRECT;
|
||||
}
|
||||
|
||||
public static void reloadConnectivitySettings(Context context) {
|
||||
final DependencyHolder holder = DependencyHolder.get(context);
|
||||
final RestHttpClient client = holder.getRestHttpClient();
|
||||
if (client instanceof OkHttpRestClient) {
|
||||
final OkHttpClient.Builder builder = new OkHttpClient.Builder();
|
||||
initOkHttpClient(context, holder.getPreferences(), builder,
|
||||
holder.getDns(), holder.getConnectionPoll());
|
||||
final OkHttpRestClient restClient = (OkHttpRestClient) client;
|
||||
restClient.setClient(builder.build());
|
||||
}
|
||||
}
|
||||
}
|
@ -181,7 +181,7 @@ public class MicroBlogAPIFactory implements TwidereConstants {
|
||||
} else {
|
||||
userAgent = getTwidereUserAgent(context);
|
||||
}
|
||||
DependencyHolder holder = DependencyHolder.get(context);
|
||||
DependencyHolder holder = DependencyHolder.Companion.get(context);
|
||||
factory.setHttpClient(holder.getRestHttpClient());
|
||||
factory.setAuthorization(auth);
|
||||
factory.setEndpoint(endpoint);
|
||||
|
@ -1,60 +0,0 @@
|
||||
/*
|
||||
* 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.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.mariotaku.restfu.annotation.method.GET;
|
||||
import org.mariotaku.restfu.http.ContentType;
|
||||
import org.mariotaku.restfu.http.HttpRequest;
|
||||
import org.mariotaku.restfu.http.HttpResponse;
|
||||
import org.mariotaku.restfu.http.RestHttpClient;
|
||||
import org.mariotaku.restfu.http.mime.Body;
|
||||
import org.mariotaku.twidere.activity.ThemedImagePickerActivity;
|
||||
import org.mariotaku.twidere.util.dagger.DependencyHolder;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 15/6/17.
|
||||
*/
|
||||
public class RestFuNetworkStreamDownloader extends ThemedImagePickerActivity.NetworkStreamDownloader {
|
||||
|
||||
public RestFuNetworkStreamDownloader(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public DownloadResult get(Uri uri) throws IOException {
|
||||
final RestHttpClient client = DependencyHolder.get(getContext()).getRestHttpClient();
|
||||
final HttpRequest.Builder builder = new HttpRequest.Builder();
|
||||
builder.method(GET.METHOD);
|
||||
builder.url(uri.toString());
|
||||
final HttpResponse response = client.newCall(builder.build()).execute();
|
||||
if (response.isSuccessful()) {
|
||||
final Body body = response.getBody();
|
||||
final ContentType contentType = body.contentType();
|
||||
return DownloadResult.get(body.stream(), contentType != null ? contentType.getContentType() : "image/*");
|
||||
} else {
|
||||
throw new IOException("Unable to get " + uri);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,106 +0,0 @@
|
||||
/*
|
||||
* 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.dagger;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.mariotaku.restfu.http.RestHttpClient;
|
||||
import org.mariotaku.twidere.util.ActivityTracker;
|
||||
import org.mariotaku.twidere.util.ExternalThemeManager;
|
||||
import org.mariotaku.twidere.util.ReadStateManager;
|
||||
import org.mariotaku.twidere.util.SharedPreferencesWrapper;
|
||||
import org.mariotaku.twidere.util.TwidereValidator;
|
||||
import org.mariotaku.twidere.util.net.TwidereDns;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import edu.tsinghua.hotmobi.HotMobiLogger;
|
||||
import okhttp3.ConnectionPool;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 15/12/31.
|
||||
*/
|
||||
public class DependencyHolder {
|
||||
|
||||
private static DependencyHolder sInstance;
|
||||
@Inject
|
||||
HotMobiLogger mHotMobiLogger;
|
||||
@Inject
|
||||
ReadStateManager mReadStateManager;
|
||||
@Inject
|
||||
RestHttpClient mRestHttpClient;
|
||||
@Inject
|
||||
ExternalThemeManager mExternalThemeManager;
|
||||
@Inject
|
||||
ActivityTracker mActivityTracker;
|
||||
@Inject
|
||||
TwidereDns mDns;
|
||||
@Inject
|
||||
TwidereValidator mValidator;
|
||||
@Inject
|
||||
SharedPreferencesWrapper mPreferences;
|
||||
@Inject
|
||||
ConnectionPool mConnectionPoll;
|
||||
|
||||
DependencyHolder(Context context) {
|
||||
GeneralComponentHelper.build(context).inject(this);
|
||||
}
|
||||
|
||||
public static DependencyHolder get(Context context) {
|
||||
if (sInstance != null) return sInstance;
|
||||
return sInstance = new DependencyHolder(context);
|
||||
}
|
||||
|
||||
public HotMobiLogger getHotMobiLogger() {
|
||||
return mHotMobiLogger;
|
||||
}
|
||||
|
||||
public ReadStateManager getReadStateManager() {
|
||||
return mReadStateManager;
|
||||
}
|
||||
|
||||
public RestHttpClient getRestHttpClient() {
|
||||
return mRestHttpClient;
|
||||
}
|
||||
|
||||
public ExternalThemeManager getExternalThemeManager() {
|
||||
return mExternalThemeManager;
|
||||
}
|
||||
|
||||
public ActivityTracker getActivityTracker() {
|
||||
return mActivityTracker;
|
||||
}
|
||||
|
||||
public TwidereDns getDns() {
|
||||
return mDns;
|
||||
}
|
||||
|
||||
public TwidereValidator getValidator() {
|
||||
return mValidator;
|
||||
}
|
||||
|
||||
public SharedPreferencesWrapper getPreferences() {
|
||||
return mPreferences;
|
||||
}
|
||||
|
||||
public ConnectionPool getConnectionPoll() {
|
||||
return mConnectionPoll;
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* 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.dagger
|
||||
|
||||
import android.content.Context
|
||||
import edu.tsinghua.hotmobi.HotMobiLogger
|
||||
import okhttp3.ConnectionPool
|
||||
import org.mariotaku.restfu.http.RestHttpClient
|
||||
import org.mariotaku.twidere.model.DefaultFeatures
|
||||
import org.mariotaku.twidere.util.*
|
||||
import org.mariotaku.twidere.util.net.TwidereDns
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 15/12/31.
|
||||
*/
|
||||
class DependencyHolder internal constructor(context: Context) {
|
||||
@Inject
|
||||
lateinit var hotMobiLogger: HotMobiLogger
|
||||
internal set
|
||||
@Inject
|
||||
lateinit var readStateManager: ReadStateManager
|
||||
internal set
|
||||
@Inject
|
||||
lateinit var restHttpClient: RestHttpClient
|
||||
internal set
|
||||
@Inject
|
||||
lateinit var externalThemeManager: ExternalThemeManager
|
||||
internal set
|
||||
@Inject
|
||||
lateinit var activityTracker: ActivityTracker
|
||||
internal set
|
||||
@Inject
|
||||
lateinit var dns: TwidereDns
|
||||
internal set
|
||||
@Inject
|
||||
lateinit var validator: TwidereValidator
|
||||
internal set
|
||||
@Inject
|
||||
lateinit var preferences: SharedPreferencesWrapper
|
||||
internal set
|
||||
@Inject
|
||||
lateinit var connectionPoll: ConnectionPool
|
||||
internal set
|
||||
@Inject
|
||||
lateinit var defaultFeatures: DefaultFeatures
|
||||
internal set
|
||||
|
||||
init {
|
||||
GeneralComponentHelper.build(context).inject(this)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private var sInstance: DependencyHolder? = null
|
||||
|
||||
fun get(context: Context): DependencyHolder {
|
||||
if (sInstance != null) return sInstance!!
|
||||
sInstance = DependencyHolder(context)
|
||||
return sInstance!!
|
||||
}
|
||||
}
|
||||
}
|
@ -21,11 +21,12 @@ package org.mariotaku.twidere.util.dagger
|
||||
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import dagger.Component
|
||||
import org.mariotaku.twidere.activity.iface.APIEditorActivity
|
||||
import org.mariotaku.twidere.activity.BaseActivity
|
||||
import org.mariotaku.twidere.activity.ComposeActivity
|
||||
import org.mariotaku.twidere.activity.MediaViewerActivity
|
||||
import org.mariotaku.twidere.activity.iface.APIEditorActivity
|
||||
import org.mariotaku.twidere.adapter.*
|
||||
import org.mariotaku.twidere.app.TwidereApplication
|
||||
import org.mariotaku.twidere.fragment.*
|
||||
import org.mariotaku.twidere.loader.MicroBlogAPIStatusesLoader
|
||||
import org.mariotaku.twidere.loader.ParcelableStatusLoader
|
||||
@ -133,4 +134,6 @@ interface GeneralComponent {
|
||||
fun inject(loader: APIEditorActivity.LoadDefaultsChooserDialogFragment.DefaultAPIConfigLoader)
|
||||
|
||||
fun inject(task: UpdateStatusTask)
|
||||
|
||||
fun inject(application: TwidereApplication)
|
||||
}
|
||||
|
@ -55,12 +55,14 @@ class StatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View)
|
||||
private val quotedTextView by lazy { itemView.quotedText }
|
||||
private val actionButtons by lazy { itemView.actionButtons }
|
||||
private val mediaLabel by lazy { itemView.mediaLabel }
|
||||
private val quotedMediaLabel by lazy { itemView.quotedMediaLabel }
|
||||
private val statusContentLowerSpace by lazy { itemView.statusContentLowerSpace }
|
||||
private val quotedMediaPreview by lazy { itemView.quotedMediaPreview }
|
||||
private val favoriteIcon by lazy { itemView.favoriteIcon }
|
||||
private val retweetIcon by lazy { itemView.retweetIcon }
|
||||
private val favoriteCountView by lazy { itemView.favoriteCount }
|
||||
private val mediaLabelTextView by lazy { itemView.mediaLabelText }
|
||||
private val quotedMediaLabelTextView by lazy { itemView.quotedMediaLabelText }
|
||||
private val replyButton by lazy { itemView.reply }
|
||||
private val retweetButton by lazy { itemView.retweet }
|
||||
private val favoriteButton by lazy { itemView.favorite }
|
||||
@ -227,12 +229,15 @@ class StatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View)
|
||||
if (!adapter.sensitiveContentEnabled && status.is_possibly_sensitive) {
|
||||
// Sensitive content, show label instead of media view
|
||||
quotedMediaPreview.visibility = View.GONE
|
||||
quotedMediaLabel.visibility = View.VISIBLE
|
||||
} else if (!adapter.mediaPreviewEnabled) {
|
||||
// Media preview disabled, just show label
|
||||
|
||||
quotedMediaPreview.visibility = View.GONE
|
||||
quotedMediaLabel.visibility = View.VISIBLE
|
||||
} else {
|
||||
// Show media
|
||||
quotedMediaPreview.visibility = View.VISIBLE
|
||||
quotedMediaLabel.visibility = View.GONE
|
||||
|
||||
quotedMediaPreview.displayMedia(status.quoted_media, loader, status.account_key, -1,
|
||||
null, null)
|
||||
@ -240,6 +245,7 @@ class StatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View)
|
||||
} else {
|
||||
// No media, hide all related views
|
||||
quotedMediaPreview.visibility = View.GONE
|
||||
quotedMediaLabel.visibility = View.GONE
|
||||
}
|
||||
} else {
|
||||
quotedNameView.visibility = View.GONE
|
||||
|
@ -65,8 +65,8 @@ import org.apache.commons.lang3.ObjectUtils
|
||||
import org.mariotaku.abstask.library.AbstractTask
|
||||
import org.mariotaku.abstask.library.TaskStarter
|
||||
import org.mariotaku.commons.io.StreamUtils
|
||||
import org.mariotaku.ktextension.toTypedArray
|
||||
import org.mariotaku.ktextension.setItemChecked
|
||||
import org.mariotaku.ktextension.toTypedArray
|
||||
import org.mariotaku.twidere.BuildConfig
|
||||
import org.mariotaku.twidere.Constants.*
|
||||
import org.mariotaku.twidere.R
|
||||
@ -103,6 +103,8 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
|
||||
lateinit var extractor: Extractor
|
||||
@Inject
|
||||
lateinit var validator: TwidereValidator
|
||||
@Inject
|
||||
lateinit var defaultFeatures: DefaultFeatures
|
||||
|
||||
private var locationManager: LocationManager? = null
|
||||
private var mTask: AsyncTask<Any, Any, *>? = null
|
||||
@ -751,10 +753,7 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
|
||||
}
|
||||
|
||||
private val media: Array<ParcelableMediaUpdate>
|
||||
get() {
|
||||
val list = mediaList
|
||||
return list.toTypedArray()
|
||||
}
|
||||
get() = mediaList.toTypedArray()
|
||||
|
||||
private val mediaList: List<ParcelableMediaUpdate>
|
||||
get() = mediaPreviewAdapter!!.asList
|
||||
@ -1312,9 +1311,11 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener
|
||||
}
|
||||
|
||||
private fun updateTextCount() {
|
||||
val text = ParseUtils.parseString(editText.text)
|
||||
val validatedCount = if (text != null) validator.getTweetLength(text) else 0
|
||||
statusTextCount.textCount = validatedCount
|
||||
var text = editText.text?.toString() ?: return
|
||||
if (defaultFeatures.isMediaLinkCountsInStatus && media.isNotEmpty()) {
|
||||
text += " https://twitter.com/example/status/12345678901234567890/photos/1"
|
||||
}
|
||||
statusTextCount.textCount = validator.getTweetLength(text)
|
||||
}
|
||||
|
||||
override fun getLightToolbarMode(toolbar: Toolbar?): Int {
|
||||
|
@ -34,6 +34,7 @@ import android.support.v4.content.ContextCompat
|
||||
import android.support.v4.widget.SwipeRefreshLayout
|
||||
import android.support.v7.app.AppCompatDelegate
|
||||
import android.support.v7.widget.ActionBarContextView
|
||||
import android.util.Log
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import com.afollestad.appthemeengine.ATE
|
||||
@ -42,7 +43,11 @@ import com.pnikosis.materialishprogress.ProgressWheel
|
||||
import com.rengwuxian.materialedittext.MaterialEditText
|
||||
import nl.komponents.kovenant.android.startKovenant
|
||||
import nl.komponents.kovenant.android.stopKovenant
|
||||
import nl.komponents.kovenant.task
|
||||
import org.apache.commons.lang3.ArrayUtils
|
||||
import org.mariotaku.kpreferences.KPreferences
|
||||
import org.mariotaku.ktextension.configure
|
||||
import org.mariotaku.restfu.http.RestHttpClient
|
||||
import org.mariotaku.twidere.BuildConfig
|
||||
import org.mariotaku.twidere.Constants
|
||||
import org.mariotaku.twidere.R
|
||||
@ -50,22 +55,38 @@ import org.mariotaku.twidere.TwidereConstants.*
|
||||
import org.mariotaku.twidere.activity.AssistLauncherActivity
|
||||
import org.mariotaku.twidere.activity.MainActivity
|
||||
import org.mariotaku.twidere.activity.MainHondaJOJOActivity
|
||||
import org.mariotaku.twidere.constant.defaultFeatureLastUpdated
|
||||
import org.mariotaku.twidere.model.DefaultFeatures
|
||||
import org.mariotaku.twidere.service.RefreshService
|
||||
import org.mariotaku.twidere.util.*
|
||||
import org.mariotaku.twidere.util.content.TwidereSQLiteOpenHelper
|
||||
import org.mariotaku.twidere.util.dagger.DependencyHolder
|
||||
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper
|
||||
import org.mariotaku.twidere.util.net.TwidereDns
|
||||
import org.mariotaku.twidere.util.theme.*
|
||||
import org.mariotaku.twidere.view.ProfileImageView
|
||||
import org.mariotaku.twidere.view.TabPagerIndicator
|
||||
import org.mariotaku.twidere.view.ThemedMultiValueSwitch
|
||||
import org.mariotaku.twidere.view.TimelineContentTextView
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
class TwidereApplication : Application(), Constants, OnSharedPreferenceChangeListener {
|
||||
|
||||
@Inject
|
||||
lateinit internal var activityTracker: ActivityTracker
|
||||
@Inject
|
||||
lateinit internal var restHttpClient: RestHttpClient
|
||||
@Inject
|
||||
lateinit internal var dns: TwidereDns
|
||||
@Inject
|
||||
lateinit internal var defaultFeatures: DefaultFeatures
|
||||
@Inject
|
||||
lateinit internal var externalThemeManager: ExternalThemeManager
|
||||
@Inject
|
||||
lateinit internal var kPreferences: KPreferences
|
||||
|
||||
var handler: Handler? = null
|
||||
private set
|
||||
private var mPreferences: SharedPreferences? = null
|
||||
private var mSQLiteOpenHelper: SQLiteOpenHelper? = null
|
||||
|
||||
private var profileImageViewViewProcessor: ProfileImageViewViewProcessor? = null
|
||||
private var fontFamilyTagProcessor: FontFamilyTagProcessor? = null
|
||||
@ -102,9 +123,93 @@ class TwidereApplication : Application(), Constants, OnSharedPreferenceChangeLis
|
||||
resetTheme(preferences)
|
||||
super.onCreate()
|
||||
startKovenant()
|
||||
initAppThemeEngine(preferences)
|
||||
initializeAsyncTask()
|
||||
initDebugMode()
|
||||
initBugReport()
|
||||
handler = Handler()
|
||||
|
||||
profileImageViewViewProcessor = ProfileImageViewViewProcessor()
|
||||
fontFamilyTagProcessor = FontFamilyTagProcessor()
|
||||
updateEasterEggIcon()
|
||||
|
||||
migrateUsageStatisticsPreferences()
|
||||
Utils.startRefreshServiceIfNeeded(this)
|
||||
|
||||
GeneralComponentHelper.build(this).inject(this)
|
||||
|
||||
registerActivityLifecycleCallbacks(activityTracker)
|
||||
|
||||
listenExternalThemeChange()
|
||||
|
||||
loadDefaultFeatures()
|
||||
}
|
||||
|
||||
private fun loadDefaultFeatures() {
|
||||
val lastUpdated = kPreferences[defaultFeatureLastUpdated]
|
||||
if (lastUpdated > 0 && TimeUnit.MILLISECONDS.toHours(System.currentTimeMillis() - lastUpdated) < 12) {
|
||||
return
|
||||
}
|
||||
task {
|
||||
defaultFeatures.loadRemoteSettings(restHttpClient)
|
||||
}.success {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(LOGTAG, "Loaded remote features")
|
||||
}
|
||||
}.fail {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.w(LOGTAG, "Unable to load remote features", it)
|
||||
}
|
||||
}.always {
|
||||
kPreferences[defaultFeatureLastUpdated] = System.currentTimeMillis()
|
||||
}
|
||||
}
|
||||
|
||||
private fun listenExternalThemeChange() {
|
||||
val packageFilter = IntentFilter()
|
||||
packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED)
|
||||
packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED)
|
||||
packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED)
|
||||
packageFilter.addAction(Intent.ACTION_PACKAGE_REPLACED)
|
||||
registerReceiver(object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val uid = intent.getIntExtra(Intent.EXTRA_UID, -1)
|
||||
val packages = packageManager.getPackagesForUid(uid)
|
||||
val manager = externalThemeManager
|
||||
if (ArrayUtils.contains(packages, manager.emojiPackageName)) {
|
||||
manager.reloadEmojiPreferences()
|
||||
}
|
||||
}
|
||||
}, packageFilter)
|
||||
}
|
||||
|
||||
private fun updateEasterEggIcon() {
|
||||
val pm = packageManager
|
||||
val main = ComponentName(this, MainActivity::class.java)
|
||||
val main2 = ComponentName(this, MainHondaJOJOActivity::class.java)
|
||||
val mainDisabled = pm.getComponentEnabledSetting(main) != PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|
||||
val main2Disabled = pm.getComponentEnabledSetting(main2) != PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|
||||
val noEntry = mainDisabled && main2Disabled
|
||||
if (noEntry) {
|
||||
pm.setComponentEnabledSetting(main, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||
PackageManager.DONT_KILL_APP)
|
||||
} else if (!mainDisabled) {
|
||||
pm.setComponentEnabledSetting(main2, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||
PackageManager.DONT_KILL_APP)
|
||||
}
|
||||
if (!Utils.isComposeNowSupported(this)) {
|
||||
val assist = ComponentName(this, AssistLauncherActivity::class.java)
|
||||
pm.setComponentEnabledSetting(assist, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||
PackageManager.DONT_KILL_APP)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initAppThemeEngine(preferences: SharedPreferences) {
|
||||
|
||||
profileImageViewViewProcessor = configure(ProfileImageViewViewProcessor()) {
|
||||
setStyle(Utils.getProfileImageStyle(preferences))
|
||||
}
|
||||
fontFamilyTagProcessor = configure(FontFamilyTagProcessor()) {
|
||||
setFontFamily(ThemeUtils.getThemeFontFamily(preferences))
|
||||
}
|
||||
|
||||
ATE.registerViewProcessor(TabPagerIndicator::class.java, TabPagerIndicatorViewProcessor())
|
||||
ATE.registerViewProcessor(FloatingActionButton::class.java, FloatingActionButtonViewProcessor())
|
||||
@ -127,9 +232,6 @@ class TwidereApplication : Application(), Constants, OnSharedPreferenceChangeLis
|
||||
ATE.registerTagProcessor(ThemedMultiValueSwitch.PREFIX_TINT, ThemedMultiValueSwitch.TintTagProcessor())
|
||||
|
||||
|
||||
profileImageViewViewProcessor!!.setStyle(Utils.getProfileImageStyle(preferences))
|
||||
fontFamilyTagProcessor!!.setFontFamily(ThemeUtils.getThemeFontFamily(preferences))
|
||||
|
||||
val themeColor = preferences.getInt(KEY_THEME_COLOR, ContextCompat.getColor(this,
|
||||
R.color.branding_color))
|
||||
if (!ATE.config(this, VALUE_THEME_NAME_LIGHT).isConfigured) {
|
||||
@ -142,52 +244,6 @@ class TwidereApplication : Application(), Constants, OnSharedPreferenceChangeLis
|
||||
if (!ATE.config(this, null).isConfigured) {
|
||||
ATE.config(this, null).accentColor(ThemeUtils.getOptimalAccentColor(themeColor, Color.WHITE)).coloredActionBar(false).coloredStatusBar(false).commit()
|
||||
}
|
||||
initializeAsyncTask()
|
||||
initDebugMode()
|
||||
initBugReport()
|
||||
handler = Handler()
|
||||
|
||||
val pm = packageManager
|
||||
val main = ComponentName(this, MainActivity::class.java)
|
||||
val main2 = ComponentName(this, MainHondaJOJOActivity::class.java)
|
||||
val mainDisabled = pm.getComponentEnabledSetting(main) != PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|
||||
val main2Disabled = pm.getComponentEnabledSetting(main2) != PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|
||||
val noEntry = mainDisabled && main2Disabled
|
||||
if (noEntry) {
|
||||
pm.setComponentEnabledSetting(main, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||
PackageManager.DONT_KILL_APP)
|
||||
} else if (!mainDisabled) {
|
||||
pm.setComponentEnabledSetting(main2, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||
PackageManager.DONT_KILL_APP)
|
||||
}
|
||||
if (!Utils.isComposeNowSupported(this)) {
|
||||
val assist = ComponentName(this, AssistLauncherActivity::class.java)
|
||||
pm.setComponentEnabledSetting(assist, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||
PackageManager.DONT_KILL_APP)
|
||||
}
|
||||
|
||||
migrateUsageStatisticsPreferences()
|
||||
Utils.startRefreshServiceIfNeeded(this)
|
||||
|
||||
val holder = DependencyHolder.get(this)
|
||||
registerActivityLifecycleCallbacks(holder.activityTracker)
|
||||
|
||||
val packageFilter = IntentFilter()
|
||||
packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED)
|
||||
packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED)
|
||||
packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED)
|
||||
packageFilter.addAction(Intent.ACTION_PACKAGE_REPLACED)
|
||||
registerReceiver(object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val uid = intent.getIntExtra(Intent.EXTRA_UID, -1)
|
||||
val packages = packageManager.getPackagesForUid(uid)
|
||||
val holder = DependencyHolder.get(context)
|
||||
val manager = holder.externalThemeManager
|
||||
if (ArrayUtils.contains(packages, manager.emojiPackageName)) {
|
||||
manager.reloadEmojiPreferences()
|
||||
}
|
||||
}
|
||||
}, packageFilter)
|
||||
}
|
||||
|
||||
private fun initDebugMode() {
|
||||
@ -226,7 +282,6 @@ class TwidereApplication : Application(), Constants, OnSharedPreferenceChangeLis
|
||||
}
|
||||
|
||||
override fun onLowMemory() {
|
||||
val holder = DependencyHolder.get(this)
|
||||
super.onLowMemory()
|
||||
}
|
||||
|
||||
@ -248,7 +303,7 @@ class TwidereApplication : Application(), Constants, OnSharedPreferenceChangeLis
|
||||
editor.apply()
|
||||
}
|
||||
KEY_EMOJI_SUPPORT -> {
|
||||
DependencyHolder.get(this).externalThemeManager.reloadEmojiPreferences()
|
||||
externalThemeManager.reloadEmojiPreferences()
|
||||
}
|
||||
KEY_THEME -> {
|
||||
resetTheme(preferences)
|
||||
@ -296,8 +351,6 @@ class TwidereApplication : Application(), Constants, OnSharedPreferenceChangeLis
|
||||
}
|
||||
|
||||
private fun reloadDnsSettings() {
|
||||
val holder = DependencyHolder.get(this)
|
||||
val dns = holder.dns
|
||||
dns.reloadDnsSettings()
|
||||
}
|
||||
|
||||
|
@ -29,6 +29,7 @@ val statusShortenerKey = KNullableStringKey(KEY_STATUS_SHORTENER, null)
|
||||
val mediaUploaderKey = KNullableStringKey(KEY_MEDIA_UPLOADER, null)
|
||||
val newDocumentApiKey = KBooleanKey(KEY_NEW_DOCUMENT_API, Build.VERSION.SDK_INT == Build.VERSION_CODES.M)
|
||||
val loadItemLimitKey: KIntKey = KIntKey(KEY_LOAD_ITEM_LIMIT, DEFAULT_LOAD_ITEM_LIMIT)
|
||||
val defaultFeatureLastUpdated: KLongKey = KLongKey("default_feature_last_updated", -1)
|
||||
|
||||
object defaultAPIConfigKey : KPreferenceKey<CustomAPIConfig> {
|
||||
override fun contains(preferences: SharedPreferences): Boolean {
|
||||
|
@ -404,7 +404,7 @@ abstract class AbsStatusesFragment protected constructor() :
|
||||
}
|
||||
if (status == null) return
|
||||
val accountIds = arrayOf(status.account_key)
|
||||
val maxIds = arrayOf(status.id)
|
||||
val maxIds = arrayOf<String?>(status.id)
|
||||
val maxSortIds = longArrayOf(status.sort_id)
|
||||
getStatuses(BaseRefreshTaskParam(accountIds, maxIds, null, maxSortIds, null))
|
||||
}
|
||||
@ -425,8 +425,7 @@ abstract class AbsStatusesFragment protected constructor() :
|
||||
val context = context ?: return
|
||||
val adapter = adapter
|
||||
val status = adapter!!.getStatus(position) ?: return
|
||||
handleStatusActionClick(context, fragmentManager, twitterWrapper,
|
||||
holder as StatusViewHolder, status, id)
|
||||
handleStatusActionClick(context, fragmentManager, twitterWrapper, holder as StatusViewHolder, status, id)
|
||||
}
|
||||
|
||||
override fun createItemDecoration(context: Context, recyclerView: RecyclerView, layoutManager: LinearLayoutManager): RecyclerView.ItemDecoration? {
|
||||
|
@ -171,23 +171,21 @@ abstract class CursorActivitiesFragment : AbsActivitiesFragment() {
|
||||
return this@CursorActivitiesFragment.accountKeys
|
||||
}
|
||||
|
||||
override fun getMaxIds(): Array<String>? {
|
||||
return getOldestActivityIds(accountKeys)
|
||||
}
|
||||
override val maxIds: Array<String?>?
|
||||
get() = getOldestActivityIds(accountKeys)
|
||||
|
||||
override fun getMaxSortIds(): LongArray? {
|
||||
val context = context ?: return null
|
||||
return DataStoreUtils.getOldestActivityMaxSortPositions(context,
|
||||
contentUri, accountKeys)
|
||||
}
|
||||
override val maxSortIds: LongArray?
|
||||
get() {
|
||||
val context = context ?: return null
|
||||
return DataStoreUtils.getOldestActivityMaxSortPositions(context,
|
||||
contentUri, accountKeys)
|
||||
}
|
||||
|
||||
override fun hasMaxIds(): Boolean {
|
||||
return true
|
||||
}
|
||||
override val hasMaxIds: Boolean
|
||||
get() = true
|
||||
|
||||
override fun shouldAbort(): Boolean {
|
||||
return context == null
|
||||
}
|
||||
override val shouldAbort: Boolean
|
||||
get() = context == null
|
||||
})
|
||||
}
|
||||
|
||||
@ -198,23 +196,21 @@ abstract class CursorActivitiesFragment : AbsActivitiesFragment() {
|
||||
return this@CursorActivitiesFragment.accountKeys
|
||||
}
|
||||
|
||||
override fun getSinceIds(): Array<String>? {
|
||||
return getNewestActivityIds(accountKeys)
|
||||
}
|
||||
override val sinceIds: Array<String?>?
|
||||
get() = getNewestActivityIds(accountKeys)
|
||||
|
||||
override fun getSinceSortIds(): LongArray? {
|
||||
val context = context ?: return null
|
||||
return DataStoreUtils.getNewestActivityMaxSortPositions(context,
|
||||
contentUri, accountKeys)
|
||||
}
|
||||
override val sinceSortIds: LongArray?
|
||||
get() {
|
||||
val context = context ?: return null
|
||||
return DataStoreUtils.getNewestActivityMaxSortPositions(context,
|
||||
contentUri, accountKeys)
|
||||
}
|
||||
|
||||
override fun hasSinceIds(): Boolean {
|
||||
return true
|
||||
}
|
||||
override val hasSinceIds: Boolean
|
||||
get() = true
|
||||
|
||||
override fun shouldAbort(): Boolean {
|
||||
return context == null
|
||||
}
|
||||
override val shouldAbort: Boolean
|
||||
get() = context == null
|
||||
})
|
||||
return true
|
||||
}
|
||||
@ -224,7 +220,7 @@ abstract class CursorActivitiesFragment : AbsActivitiesFragment() {
|
||||
return DataStoreUtils.buildActivityFilterWhereClause(table, null)
|
||||
}
|
||||
|
||||
protected fun getNewestActivityIds(accountKeys: Array<UserKey>): Array<String>? {
|
||||
protected fun getNewestActivityIds(accountKeys: Array<UserKey>): Array<String?>? {
|
||||
val context = context ?: return null
|
||||
return DataStoreUtils.getNewestActivityMaxPositions(context, contentUri, accountKeys)
|
||||
}
|
||||
@ -242,7 +238,7 @@ abstract class CursorActivitiesFragment : AbsActivitiesFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
protected fun getOldestActivityIds(accountKeys: Array<UserKey>): Array<String>? {
|
||||
protected fun getOldestActivityIds(accountKeys: Array<UserKey>): Array<String?>? {
|
||||
val context = context ?: return null
|
||||
return DataStoreUtils.getOldestActivityMaxPositions(context, contentUri, accountKeys)
|
||||
}
|
||||
|
@ -185,23 +185,21 @@ abstract class CursorStatusesFragment : AbsStatusesFragment() {
|
||||
return this@CursorStatusesFragment.accountKeys
|
||||
}
|
||||
|
||||
override fun getMaxIds(): Array<String>? {
|
||||
return getOldestStatusIds(accountKeys)
|
||||
}
|
||||
override val maxIds: Array<String?>?
|
||||
get() = getOldestStatusIds(accountKeys)
|
||||
|
||||
override fun getMaxSortIds(): LongArray? {
|
||||
val context = context ?: return null
|
||||
return DataStoreUtils.getOldestStatusSortIds(context, contentUri,
|
||||
accountKeys)
|
||||
}
|
||||
override val maxSortIds: LongArray?
|
||||
get() {
|
||||
val context = context ?: return null
|
||||
return DataStoreUtils.getOldestStatusSortIds(context, contentUri,
|
||||
accountKeys)
|
||||
}
|
||||
|
||||
override fun hasMaxIds(): Boolean {
|
||||
return true
|
||||
}
|
||||
override val hasMaxIds: Boolean
|
||||
get() = true
|
||||
|
||||
override fun shouldAbort(): Boolean {
|
||||
return context == null
|
||||
}
|
||||
override val shouldAbort: Boolean
|
||||
get() = context == null
|
||||
})
|
||||
}
|
||||
|
||||
@ -212,23 +210,17 @@ abstract class CursorStatusesFragment : AbsStatusesFragment() {
|
||||
return this@CursorStatusesFragment.accountKeys
|
||||
}
|
||||
|
||||
override fun hasMaxIds(): Boolean {
|
||||
return false
|
||||
}
|
||||
override val hasMaxIds: Boolean
|
||||
get() = false
|
||||
|
||||
override fun getSinceIds(): Array<String>? {
|
||||
return getNewestStatusIds(accountKeys)
|
||||
}
|
||||
override val sinceIds: Array<String?>?
|
||||
get() = getNewestStatusIds(accountKeys)
|
||||
|
||||
override fun getSinceSortIds(): LongArray? {
|
||||
val context = context ?: return null
|
||||
return DataStoreUtils.getNewestStatusSortIds(context, contentUri,
|
||||
accountKeys)
|
||||
}
|
||||
override val sinceSortIds: LongArray?
|
||||
get() = DataStoreUtils.getNewestStatusSortIds(context, contentUri, accountKeys)
|
||||
|
||||
override fun shouldAbort(): Boolean {
|
||||
return context == null
|
||||
}
|
||||
override val shouldAbort: Boolean
|
||||
get() = context == null
|
||||
})
|
||||
return true
|
||||
}
|
||||
@ -238,7 +230,7 @@ abstract class CursorStatusesFragment : AbsStatusesFragment() {
|
||||
return buildStatusFilterWhereClause(table, null)
|
||||
}
|
||||
|
||||
protected fun getNewestStatusIds(accountKeys: Array<UserKey>): Array<String>? {
|
||||
protected fun getNewestStatusIds(accountKeys: Array<UserKey>): Array<String?>? {
|
||||
val context = context ?: return null
|
||||
return DataStoreUtils.getNewestStatusIds(context, contentUri, accountKeys)
|
||||
}
|
||||
@ -254,7 +246,7 @@ abstract class CursorStatusesFragment : AbsStatusesFragment() {
|
||||
}
|
||||
|
||||
|
||||
protected fun getOldestStatusIds(accountKeys: Array<UserKey>): Array<String>? {
|
||||
protected fun getOldestStatusIds(accountKeys: Array<UserKey>): Array<String?>? {
|
||||
val context = context ?: return null
|
||||
return DataStoreUtils.getOldestStatusIds(context, contentUri, accountKeys)
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ class HomeTimelineFragment : CursorStatusesFragment() {
|
||||
}
|
||||
|
||||
override fun getStatuses(param: RefreshTaskParam): Boolean {
|
||||
if (!param.hasMaxIds()) return twitterWrapper.refreshAll(param.accountKeys)
|
||||
if (!param.hasMaxIds) return twitterWrapper.refreshAll(param.accountKeys)
|
||||
return twitterWrapper.getHomeTimelineAsync(param)
|
||||
}
|
||||
|
||||
|
@ -153,7 +153,7 @@ abstract class ParcelableStatusesFragment : AbsStatusesFragment() {
|
||||
if (idx < 0) return
|
||||
val status = adapter.getStatus(idx) ?: return
|
||||
val accountKeys = arrayOf(status.account_key)
|
||||
val maxIds = arrayOf(status.id)
|
||||
val maxIds = arrayOf<String?>(status.id)
|
||||
page += pageDelta
|
||||
val param = BaseRefreshTaskParam(accountKeys, maxIds, null)
|
||||
param.isLoadingMore = true
|
||||
@ -181,13 +181,16 @@ abstract class ParcelableStatusesFragment : AbsStatusesFragment() {
|
||||
|
||||
override fun triggerRefresh(): Boolean {
|
||||
super.triggerRefresh()
|
||||
val adapter = adapter
|
||||
val accountIds = accountKeys
|
||||
if (adapter!!.statusCount > 0) {
|
||||
val sinceIds = arrayOf(adapter.getStatus(0)!!.id)
|
||||
getStatuses(BaseRefreshTaskParam(accountIds, null, sinceIds))
|
||||
val adapter = adapter ?: return false
|
||||
val accountKeys = accountKeys
|
||||
if (adapter.statusCount > 0) {
|
||||
val firstStatus = adapter.getStatus(0)!!
|
||||
val sinceIds = Array(accountKeys.size) {
|
||||
return@Array if (firstStatus.account_key == accountKeys[it]) firstStatus.id else null
|
||||
}
|
||||
getStatuses(BaseRefreshTaskParam(accountKeys, null, sinceIds))
|
||||
} else {
|
||||
getStatuses(BaseRefreshTaskParam(accountIds, null, null))
|
||||
getStatuses(BaseRefreshTaskParam(accountKeys, null, null))
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
@ -0,0 +1,21 @@
|
||||
package org.mariotaku.twidere.model
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/2/11.
|
||||
*/
|
||||
class BaseRefreshTaskParam(
|
||||
override val accountKeys: Array<UserKey>,
|
||||
override val maxIds: Array<String?>?,
|
||||
override val sinceIds: Array<String?>?,
|
||||
override val maxSortIds: LongArray? = null,
|
||||
override val sinceSortIds: LongArray? = null
|
||||
) : RefreshTaskParam {
|
||||
override var isLoadingMore: Boolean = false
|
||||
override var shouldAbort: Boolean = false
|
||||
|
||||
override val hasMaxIds: Boolean
|
||||
get() = maxIds != null
|
||||
|
||||
override val hasSinceIds: Boolean
|
||||
get() = sinceIds != null
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package org.mariotaku.twidere.model
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/2/14.
|
||||
*/
|
||||
interface RefreshTaskParam {
|
||||
val accountKeys: Array<UserKey>
|
||||
|
||||
val maxIds: Array<String?>?
|
||||
|
||||
val sinceIds: Array<String?>?
|
||||
|
||||
val maxSortIds: LongArray?
|
||||
|
||||
val sinceSortIds: LongArray?
|
||||
|
||||
val hasMaxIds: Boolean
|
||||
|
||||
val hasSinceIds: Boolean
|
||||
|
||||
val isLoadingMore: Boolean
|
||||
|
||||
val shouldAbort: Boolean
|
||||
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package org.mariotaku.twidere.model
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/2/14.
|
||||
*/
|
||||
abstract class SimpleRefreshTaskParam : RefreshTaskParam {
|
||||
|
||||
internal var cached: Array<UserKey>? = null
|
||||
|
||||
override val accountKeys: Array<UserKey>
|
||||
get() {
|
||||
if (cached != null) return cached!!
|
||||
cached = getAccountKeysWorker()
|
||||
return cached!!
|
||||
}
|
||||
|
||||
abstract fun getAccountKeysWorker(): Array<UserKey>
|
||||
|
||||
override val maxIds: Array<String?>?
|
||||
get() = null
|
||||
|
||||
override val sinceIds: Array<String?>?
|
||||
get() = null
|
||||
|
||||
override val hasMaxIds: Boolean
|
||||
get() = maxIds != null
|
||||
|
||||
override val hasSinceIds: Boolean
|
||||
get() = sinceIds != null
|
||||
|
||||
override val sinceSortIds: LongArray?
|
||||
get() = null
|
||||
|
||||
override val maxSortIds: LongArray?
|
||||
get() = null
|
||||
|
||||
override val isLoadingMore: Boolean
|
||||
get() = false
|
||||
|
||||
override val shouldAbort: Boolean
|
||||
get() = false
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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.receiver
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.text.TextUtils
|
||||
import edu.tsinghua.hotmobi.model.NotificationEvent
|
||||
import org.apache.commons.lang3.math.NumberUtils
|
||||
import org.mariotaku.ktextension.toLong
|
||||
import org.mariotaku.twidere.TwidereConstants.*
|
||||
import org.mariotaku.twidere.annotation.CustomTabType
|
||||
import org.mariotaku.twidere.annotation.NotificationType
|
||||
import org.mariotaku.twidere.annotation.ReadPositionTag
|
||||
import org.mariotaku.twidere.constant.IntentConstants.BROADCAST_NOTIFICATION_DELETED
|
||||
import org.mariotaku.twidere.model.StringLongPair
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.util.CustomTabUtils
|
||||
import org.mariotaku.twidere.util.UriExtraUtils
|
||||
import org.mariotaku.twidere.util.Utils
|
||||
import org.mariotaku.twidere.util.dagger.DependencyHolder
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 15/4/4.
|
||||
*/
|
||||
class NotificationReceiver : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val action = intent.action ?: return
|
||||
when (action) {
|
||||
BROADCAST_NOTIFICATION_DELETED -> {
|
||||
val uri = intent.data ?: return
|
||||
val holder = DependencyHolder.get(context)
|
||||
@NotificationType
|
||||
val notificationType = uri.getQueryParameter(QUERY_PARAM_NOTIFICATION_TYPE)
|
||||
val accountKey = UserKey.valueOf(uri.getQueryParameter(QUERY_PARAM_ACCOUNT_KEY))
|
||||
val itemId = NumberUtils.toLong(UriExtraUtils.getExtra(uri, "item_id"), -1)
|
||||
val itemUserId = NumberUtils.toLong(UriExtraUtils.getExtra(uri, "item_user_id"), -1)
|
||||
val itemUserFollowing = java.lang.Boolean.parseBoolean(UriExtraUtils.getExtra(uri, "item_user_following"))
|
||||
val timestamp = NumberUtils.toLong(uri.getQueryParameter(QUERY_PARAM_TIMESTAMP), -1)
|
||||
if (CustomTabType.NOTIFICATIONS_TIMELINE == CustomTabUtils.getTabTypeAlias(notificationType)
|
||||
&& accountKey != null && itemId != -1L && timestamp != -1L) {
|
||||
val logger = holder.hotMobiLogger
|
||||
logger.log(accountKey, NotificationEvent.deleted(context, timestamp, notificationType, accountKey,
|
||||
itemId, itemUserId, itemUserFollowing))
|
||||
}
|
||||
val manager = holder.readStateManager
|
||||
val paramReadPosition: String = uri.getQueryParameter(QUERY_PARAM_READ_POSITION)
|
||||
val paramReadPositions: String = uri.getQueryParameter(QUERY_PARAM_READ_POSITIONS)
|
||||
@ReadPositionTag
|
||||
val tag = getPositionTag(notificationType)
|
||||
|
||||
if (tag != null && !TextUtils.isEmpty(paramReadPosition)) {
|
||||
manager.setPosition(Utils.getReadPositionTagWithAccount(tag, accountKey),
|
||||
paramReadPosition.toLong(-1))
|
||||
} else if (!TextUtils.isEmpty(paramReadPositions)) {
|
||||
try {
|
||||
val pairs = StringLongPair.valuesOf(paramReadPositions)
|
||||
for (pair in pairs) {
|
||||
manager.setPosition(tag!!, pair.key, pair.value)
|
||||
}
|
||||
} catch (ignore: NumberFormatException) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ReadPositionTag
|
||||
private fun getPositionTag(@NotificationType type: String?): String? {
|
||||
if (type == null) return null
|
||||
when (type) {
|
||||
NotificationType.HOME_TIMELINE -> return ReadPositionTag.HOME_TIMELINE
|
||||
NotificationType.INTERACTIONS -> return ReadPositionTag.ACTIVITIES_ABOUT_ME
|
||||
NotificationType.DIRECT_MESSAGES -> {
|
||||
return ReadPositionTag.DIRECT_MESSAGES
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package org.mariotaku.twidere.task
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import edu.tsinghua.hotmobi.model.TimelineType
|
||||
import org.mariotaku.microblog.library.MicroBlog
|
||||
import org.mariotaku.microblog.library.MicroBlogException
|
||||
import org.mariotaku.microblog.library.twitter.model.Paging
|
||||
import org.mariotaku.microblog.library.twitter.model.ResponseList
|
||||
import org.mariotaku.microblog.library.twitter.model.Status
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore
|
||||
import org.mariotaku.twidere.task.twitter.GetStatusesTask
|
||||
import org.mariotaku.twidere.util.ErrorInfoStore
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/2/11.
|
||||
*/
|
||||
class GetHomeTimelineTask(context: Context) : GetStatusesTask(context) {
|
||||
|
||||
@Throws(MicroBlogException::class)
|
||||
override fun getStatuses(twitter: MicroBlog, paging: Paging): ResponseList<Status> {
|
||||
return twitter.getHomeTimeline(paging)
|
||||
}
|
||||
|
||||
override val contentUri: Uri
|
||||
get() = TwidereDataStore.Statuses.CONTENT_URI
|
||||
|
||||
@TimelineType
|
||||
override val timelineType: String
|
||||
get() = TimelineType.HOME
|
||||
|
||||
override val errorInfoKey: String
|
||||
get() = ErrorInfoStore.KEY_HOME_TIMELINE
|
||||
|
||||
}
|
@ -0,0 +1,214 @@
|
||||
package org.mariotaku.twidere.task.twitter
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.support.annotation.UiThread
|
||||
import android.util.Log
|
||||
import com.squareup.otto.Bus
|
||||
import org.mariotaku.abstask.library.AbstractTask
|
||||
import org.mariotaku.microblog.library.MicroBlog
|
||||
import org.mariotaku.microblog.library.MicroBlogException
|
||||
import org.mariotaku.microblog.library.twitter.model.Activity
|
||||
import org.mariotaku.microblog.library.twitter.model.Paging
|
||||
import org.mariotaku.microblog.library.twitter.model.ResponseList
|
||||
import org.mariotaku.sqliteqb.library.Expression
|
||||
import org.mariotaku.twidere.BuildConfig
|
||||
import org.mariotaku.twidere.Constants
|
||||
import org.mariotaku.twidere.TwidereConstants.LOGTAG
|
||||
import org.mariotaku.twidere.TwidereConstants.QUERY_PARAM_NOTIFY
|
||||
import org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_LOAD_ITEM_LIMIT
|
||||
import org.mariotaku.twidere.model.ParcelableCredentials
|
||||
import org.mariotaku.twidere.model.RefreshTaskParam
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.model.message.GetActivitiesTaskEvent
|
||||
import org.mariotaku.twidere.model.util.ParcelableActivityUtils
|
||||
import org.mariotaku.twidere.model.util.ParcelableCredentialsUtils
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Activities
|
||||
import org.mariotaku.twidere.util.*
|
||||
import org.mariotaku.twidere.util.content.ContentResolverUtils
|
||||
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/1/4.
|
||||
*/
|
||||
abstract class GetActivitiesTask(protected val context: Context) : AbstractTask<RefreshTaskParam, Any, Any>(), Constants {
|
||||
@Inject
|
||||
lateinit var preferences: SharedPreferencesWrapper
|
||||
@Inject
|
||||
lateinit var bus: Bus
|
||||
@Inject
|
||||
lateinit var errorInfoStore: ErrorInfoStore
|
||||
@Inject
|
||||
lateinit var readStateManager: ReadStateManager
|
||||
@Inject
|
||||
lateinit var userColorNameManager: UserColorNameManager
|
||||
|
||||
init {
|
||||
GeneralComponentHelper.build(context).inject(this)
|
||||
}
|
||||
|
||||
public override fun doLongOperation(param: RefreshTaskParam): Any? {
|
||||
if (param.shouldAbort) return null
|
||||
val accountIds = param.accountKeys
|
||||
val maxIds = param.maxIds
|
||||
val maxSortIds = param.maxSortIds
|
||||
val sinceIds = param.sinceIds
|
||||
val cr = context.contentResolver
|
||||
val loadItemLimit = preferences.getInt(KEY_LOAD_ITEM_LIMIT)
|
||||
var saveReadPosition = false
|
||||
for (i in accountIds.indices) {
|
||||
val accountKey = accountIds[i]
|
||||
val noItemsBefore = DataStoreUtils.getActivitiesCount(context, contentUri,
|
||||
accountKey) <= 0
|
||||
val credentials = ParcelableCredentialsUtils.getCredentials(context,
|
||||
accountKey) ?: continue
|
||||
val twitter = MicroBlogAPIFactory.getInstance(context, credentials, true,
|
||||
true) ?: continue
|
||||
val paging = Paging()
|
||||
paging.count(loadItemLimit)
|
||||
var maxId: String? = null
|
||||
var maxSortId: Long = -1
|
||||
if (maxIds != null) {
|
||||
maxId = maxIds[i]
|
||||
if (maxSortIds != null) {
|
||||
maxSortId = maxSortIds[i]
|
||||
}
|
||||
if (maxId != null) {
|
||||
paging.maxId(maxId)
|
||||
}
|
||||
}
|
||||
var sinceId: String? = null
|
||||
if (sinceIds != null) {
|
||||
sinceId = sinceIds[i]
|
||||
if (sinceId != null) {
|
||||
paging.sinceId(sinceId)
|
||||
if (maxIds == null || maxId == null) {
|
||||
paging.setLatestResults(true)
|
||||
saveReadPosition = true
|
||||
}
|
||||
}
|
||||
}
|
||||
// We should delete old activities has intersection with new items
|
||||
try {
|
||||
val activities = getActivities(twitter, credentials, paging)
|
||||
storeActivities(cr, loadItemLimit, credentials, noItemsBefore, activities, sinceId,
|
||||
maxId, false)
|
||||
if (saveReadPosition) {
|
||||
saveReadPosition(accountKey, credentials, twitter)
|
||||
}
|
||||
errorInfoStore.remove(errorInfoKey, accountKey)
|
||||
} catch (e: MicroBlogException) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.w(LOGTAG, e)
|
||||
}
|
||||
if (e.errorCode == 220) {
|
||||
errorInfoStore.put(errorInfoKey, accountKey,
|
||||
ErrorInfoStore.CODE_NO_ACCESS_FOR_CREDENTIALS)
|
||||
} else if (e.isCausedByNetworkIssue) {
|
||||
errorInfoStore.put(errorInfoKey, accountKey,
|
||||
ErrorInfoStore.CODE_NETWORK_ERROR)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
protected abstract val errorInfoKey: String
|
||||
|
||||
private fun storeActivities(cr: ContentResolver, loadItemLimit: Int, credentials: ParcelableCredentials,
|
||||
noItemsBefore: Boolean, activities: ResponseList<Activity>,
|
||||
sinceId: String?, maxId: String?, notify: Boolean) {
|
||||
val deleteBound = LongArray(2, { return@LongArray -1 })
|
||||
val valuesList = ArrayList<ContentValues>()
|
||||
var minIdx = -1
|
||||
var minPositionKey: Long = -1
|
||||
if (!activities.isEmpty()) {
|
||||
val firstSortId = activities.first().createdAt.time
|
||||
val lastSortId = activities.last().createdAt.time
|
||||
// Get id diff of first and last item
|
||||
val sortDiff = firstSortId - lastSortId
|
||||
for (i in activities.indices) {
|
||||
val item = activities[i]
|
||||
val activity = ParcelableActivityUtils.fromActivity(item,
|
||||
credentials.account_key, false)
|
||||
activity.position_key = GetStatusesTask.getPositionKey(activity.timestamp,
|
||||
activity.timestamp, lastSortId, sortDiff, i, activities.size)
|
||||
if (deleteBound[0] < 0) {
|
||||
deleteBound[0] = activity.min_sort_position
|
||||
} else {
|
||||
deleteBound[0] = Math.min(deleteBound[0], activity.min_sort_position)
|
||||
}
|
||||
if (deleteBound[1] < 0) {
|
||||
deleteBound[1] = activity.max_sort_position
|
||||
} else {
|
||||
deleteBound[1] = Math.max(deleteBound[1], activity.max_sort_position)
|
||||
}
|
||||
if (minIdx == -1 || item < activities[minIdx]) {
|
||||
minIdx = i
|
||||
minPositionKey = activity.position_key
|
||||
}
|
||||
|
||||
activity.inserted_date = System.currentTimeMillis()
|
||||
val values = ContentValuesCreator.createActivity(activity,
|
||||
credentials, userColorNameManager)
|
||||
valuesList.add(values)
|
||||
}
|
||||
}
|
||||
var olderCount = -1
|
||||
if (minPositionKey > 0) {
|
||||
olderCount = DataStoreUtils.getActivitiesCount(context, contentUri, minPositionKey,
|
||||
Activities.POSITION_KEY, false, credentials.account_key)
|
||||
}
|
||||
val writeUri = UriUtils.appendQueryParameters(contentUri, QUERY_PARAM_NOTIFY, notify)
|
||||
if (deleteBound[0] > 0 && deleteBound[1] > 0) {
|
||||
val where = Expression.and(
|
||||
Expression.equalsArgs(Activities.ACCOUNT_KEY),
|
||||
Expression.greaterEqualsArgs(Activities.MIN_SORT_POSITION),
|
||||
Expression.lesserEqualsArgs(Activities.MAX_SORT_POSITION))
|
||||
val whereArgs = arrayOf(credentials.account_key.toString(), deleteBound[0].toString(), deleteBound[1].toString())
|
||||
val rowsDeleted = cr.delete(writeUri, where.sql, whereArgs)
|
||||
// Why loadItemLimit / 2? because it will not acting strange in most cases
|
||||
val insertGap = valuesList.size >= loadItemLimit && !noItemsBefore && olderCount > 0
|
||||
&& rowsDeleted <= 0 && activities.size > loadItemLimit / 2
|
||||
if (insertGap && !valuesList.isEmpty()) {
|
||||
valuesList[valuesList.size - 1].put(Activities.IS_GAP, true)
|
||||
}
|
||||
}
|
||||
ContentResolverUtils.bulkInsert(cr, writeUri, valuesList)
|
||||
|
||||
if (maxId != null && sinceId == null) {
|
||||
val noGapValues = ContentValues()
|
||||
noGapValues.put(Activities.IS_GAP, false)
|
||||
val noGapWhere = Expression.and(Expression.equalsArgs(Activities.ACCOUNT_KEY),
|
||||
Expression.equalsArgs(Activities.MIN_REQUEST_POSITION),
|
||||
Expression.equalsArgs(Activities.MAX_REQUEST_POSITION)).sql
|
||||
val noGapWhereArgs = arrayOf(credentials.toString(), maxId, maxId)
|
||||
cr.update(writeUri, noGapValues, noGapWhere, noGapWhereArgs)
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract fun saveReadPosition(accountId: UserKey,
|
||||
credentials: ParcelableCredentials, twitter: MicroBlog)
|
||||
|
||||
@Throws(MicroBlogException::class)
|
||||
protected abstract fun getActivities(twitter: MicroBlog,
|
||||
credentials: ParcelableCredentials,
|
||||
paging: Paging): ResponseList<Activity>
|
||||
|
||||
public override fun afterExecute(handler: Any?, result: Any?) {
|
||||
context.contentResolver.notifyChange(contentUri, null)
|
||||
bus.post(GetActivitiesTaskEvent(contentUri, false, null))
|
||||
}
|
||||
|
||||
protected abstract val contentUri: Uri
|
||||
|
||||
@UiThread
|
||||
public override fun beforeExecute() {
|
||||
bus.post(GetActivitiesTaskEvent(contentUri, true, null))
|
||||
}
|
||||
}
|
@ -0,0 +1,249 @@
|
||||
package org.mariotaku.twidere.task.twitter
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import com.squareup.otto.Bus
|
||||
import edu.tsinghua.hotmobi.HotMobiLogger
|
||||
import edu.tsinghua.hotmobi.model.RefreshEvent
|
||||
import org.apache.commons.lang3.ArrayUtils
|
||||
import org.apache.commons.lang3.math.NumberUtils
|
||||
import org.mariotaku.abstask.library.AbstractTask
|
||||
import org.mariotaku.abstask.library.TaskStarter
|
||||
import org.mariotaku.kpreferences.KPreferences
|
||||
import org.mariotaku.microblog.library.MicroBlog
|
||||
import org.mariotaku.microblog.library.MicroBlogException
|
||||
import org.mariotaku.microblog.library.twitter.model.Paging
|
||||
import org.mariotaku.microblog.library.twitter.model.ResponseList
|
||||
import org.mariotaku.microblog.library.twitter.model.Status
|
||||
import org.mariotaku.sqliteqb.library.Columns
|
||||
import org.mariotaku.sqliteqb.library.Expression
|
||||
import org.mariotaku.twidere.BuildConfig
|
||||
import org.mariotaku.twidere.Constants
|
||||
import org.mariotaku.twidere.TwidereConstants.LOGTAG
|
||||
import org.mariotaku.twidere.TwidereConstants.QUERY_PARAM_NOTIFY
|
||||
import org.mariotaku.twidere.constant.loadItemLimitKey
|
||||
import org.mariotaku.twidere.model.ParcelableCredentials
|
||||
import org.mariotaku.twidere.model.ParcelableStatusValuesCreator
|
||||
import org.mariotaku.twidere.model.RefreshTaskParam
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.model.message.GetStatusesTaskEvent
|
||||
import org.mariotaku.twidere.model.util.ParcelableCredentialsUtils
|
||||
import org.mariotaku.twidere.model.util.ParcelableStatusUtils
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.AccountSupportColumns
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
|
||||
import org.mariotaku.twidere.task.CacheUsersStatusesTask
|
||||
import org.mariotaku.twidere.util.*
|
||||
import org.mariotaku.twidere.util.content.ContentResolverUtils
|
||||
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/1/2.
|
||||
*/
|
||||
abstract class GetStatusesTask(protected val context: Context) : AbstractTask<RefreshTaskParam, List<TwitterWrapper.StatusListResponse>, Any>(), Constants {
|
||||
@Inject
|
||||
lateinit var preferences: KPreferences
|
||||
@Inject
|
||||
lateinit var bus: Bus
|
||||
@Inject
|
||||
lateinit var errorInfoStore: ErrorInfoStore
|
||||
@Inject
|
||||
lateinit var manager: UserColorNameManager
|
||||
@Inject
|
||||
lateinit var wrapper: AsyncTwitterWrapper
|
||||
|
||||
init {
|
||||
GeneralComponentHelper.build(context).inject(this)
|
||||
}
|
||||
|
||||
@Throws(MicroBlogException::class)
|
||||
abstract fun getStatuses(twitter: MicroBlog, paging: Paging): ResponseList<Status>
|
||||
|
||||
protected abstract val contentUri: Uri
|
||||
|
||||
protected abstract val timelineType: String
|
||||
|
||||
public override fun afterExecute(handler: Any?, result: List<TwitterWrapper.StatusListResponse>?) {
|
||||
context.contentResolver.notifyChange(contentUri, null)
|
||||
bus.post(GetStatusesTaskEvent(contentUri, false, AsyncTwitterWrapper.getException(result)))
|
||||
}
|
||||
|
||||
override fun beforeExecute() {
|
||||
bus.post(GetStatusesTaskEvent(contentUri, true, null))
|
||||
}
|
||||
|
||||
protected abstract val errorInfoKey: String
|
||||
|
||||
public override fun doLongOperation(param: RefreshTaskParam): List<TwitterWrapper.StatusListResponse> {
|
||||
if (param.shouldAbort) return emptyList()
|
||||
val accountKeys = param.accountKeys
|
||||
val maxIds = param.maxIds
|
||||
val sinceIds = param.sinceIds
|
||||
val maxSortIds = param.maxSortIds
|
||||
val sinceSortIds = param.sinceSortIds
|
||||
val result = ArrayList<TwitterWrapper.StatusListResponse>()
|
||||
val loadItemLimit = preferences[loadItemLimitKey]
|
||||
for (i in 0 until accountKeys.size) {
|
||||
val accountKey = accountKeys[i]
|
||||
val credentials = ParcelableCredentialsUtils.getCredentials(context,
|
||||
accountKey) ?: continue
|
||||
val twitter = MicroBlogAPIFactory.getInstance(context, credentials,
|
||||
true, true) ?: continue
|
||||
try {
|
||||
val paging = Paging()
|
||||
paging.count(loadItemLimit)
|
||||
val maxId: String?
|
||||
val sinceId: String?
|
||||
var maxSortId: Long = -1
|
||||
var sinceSortId: Long = -1
|
||||
if (maxIds != null && maxIds[i] != null) {
|
||||
maxId = maxIds[i]
|
||||
paging.maxId(maxId)
|
||||
if (maxSortIds != null) {
|
||||
maxSortId = maxSortIds[i]
|
||||
}
|
||||
} else {
|
||||
maxSortId = -1
|
||||
maxId = null
|
||||
}
|
||||
if (sinceIds != null && sinceIds[i] != null) {
|
||||
sinceId = sinceIds[i]
|
||||
val sinceIdLong = NumberUtils.toLong(sinceId, -1)
|
||||
//TODO handle non-twitter case
|
||||
if (sinceIdLong != -1L) {
|
||||
paging.sinceId((sinceIdLong - 1).toString())
|
||||
} else {
|
||||
paging.sinceId(sinceId)
|
||||
}
|
||||
if (sinceSortIds != null) {
|
||||
sinceSortId = sinceSortIds[i]
|
||||
}
|
||||
if (maxIds == null) {
|
||||
paging.setLatestResults(true)
|
||||
}
|
||||
} else {
|
||||
sinceId = null
|
||||
}
|
||||
val statuses = getStatuses(twitter, paging)
|
||||
storeStatus(accountKey, credentials, statuses, sinceId, maxId, sinceSortId,
|
||||
maxSortId, loadItemLimit, false)
|
||||
// TODO cache related data and preload
|
||||
val cacheTask = CacheUsersStatusesTask(context)
|
||||
cacheTask.params = TwitterWrapper.StatusListResponse(accountKey, statuses)
|
||||
TaskStarter.execute(cacheTask)
|
||||
errorInfoStore.remove(errorInfoKey, accountKey.id)
|
||||
} catch (e: MicroBlogException) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.w(LOGTAG, e)
|
||||
}
|
||||
if (e.isCausedByNetworkIssue) {
|
||||
errorInfoStore.put(errorInfoKey, accountKey.id,
|
||||
ErrorInfoStore.CODE_NETWORK_ERROR)
|
||||
}
|
||||
result.add(TwitterWrapper.StatusListResponse(accountKey, e))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun storeStatus(accountKey: UserKey, credentials: ParcelableCredentials,
|
||||
statuses: List<Status>,
|
||||
sinceId: String?, maxId: String?,
|
||||
sinceSortId: Long, maxSortId: Long,
|
||||
loadItemLimit: Int, notify: Boolean) {
|
||||
val uri = contentUri
|
||||
val writeUri = UriUtils.appendQueryParameters(uri, QUERY_PARAM_NOTIFY, notify)
|
||||
val resolver = context.contentResolver
|
||||
val noItemsBefore = DataStoreUtils.getStatusCount(context, uri, accountKey) <= 0
|
||||
val values = arrayOfNulls<ContentValues>(statuses.size)
|
||||
val statusIds = arrayOfNulls<String>(statuses.size)
|
||||
var minIdx = -1
|
||||
var minPositionKey: Long = -1
|
||||
var hasIntersection = false
|
||||
if (!statuses.isEmpty()) {
|
||||
val firstSortId = statuses.first().sortId
|
||||
val lastSortId = statuses.last().sortId
|
||||
// Get id diff of first and last item
|
||||
val sortDiff = firstSortId - lastSortId
|
||||
|
||||
for (i in 0 until statuses.size) {
|
||||
val item = statuses[i]
|
||||
val status = ParcelableStatusUtils.fromStatus(item, accountKey,
|
||||
false)
|
||||
ParcelableStatusUtils.updateExtraInformation(status, credentials, manager)
|
||||
status.position_key = getPositionKey(status.timestamp, status.sort_id, lastSortId,
|
||||
sortDiff, i, statuses.size)
|
||||
status.inserted_date = System.currentTimeMillis()
|
||||
values[i] = ParcelableStatusValuesCreator.create(status)
|
||||
if (minIdx == -1 || item < statuses[minIdx]) {
|
||||
minIdx = i
|
||||
minPositionKey = status.position_key
|
||||
}
|
||||
if (sinceId != null && item.sortId <= sinceSortId) {
|
||||
hasIntersection = true
|
||||
}
|
||||
statusIds[i] = item.id
|
||||
}
|
||||
}
|
||||
// Delete all rows conflicting before new data inserted.
|
||||
val accountWhere = Expression.equalsArgs(AccountSupportColumns.ACCOUNT_KEY)
|
||||
val statusWhere = Expression.inArgs(Columns.Column(Statuses.STATUS_ID),
|
||||
statusIds.size)
|
||||
val deleteWhere = Expression.and(accountWhere, statusWhere).sql
|
||||
val deleteWhereArgs = arrayOf(accountKey.toString(), *statusIds)
|
||||
var olderCount = -1
|
||||
if (minPositionKey > 0) {
|
||||
olderCount = DataStoreUtils.getStatusesCount(context, uri, null, minPositionKey,
|
||||
Statuses.POSITION_KEY, false, arrayOf(accountKey))
|
||||
}
|
||||
val rowsDeleted = resolver.delete(writeUri, deleteWhere, deleteWhereArgs)
|
||||
|
||||
// BEGIN HotMobi
|
||||
val event = RefreshEvent.create(context, statusIds, timelineType)
|
||||
HotMobiLogger.getInstance(context).log(accountKey, event)
|
||||
// END HotMobi
|
||||
|
||||
// Insert a gap.
|
||||
val deletedOldGap = rowsDeleted > 0 && ArrayUtils.contains(statusIds, maxId)
|
||||
val noRowsDeleted = rowsDeleted == 0
|
||||
// Why loadItemLimit / 2? because it will not acting strange in most cases
|
||||
val insertGap = minIdx != -1 && olderCount > 0 && (noRowsDeleted || deletedOldGap)
|
||||
&& !noItemsBefore && !hasIntersection && statuses.size > loadItemLimit / 2
|
||||
if (insertGap) {
|
||||
values[minIdx]!!.put(Statuses.IS_GAP, true)
|
||||
}
|
||||
// Insert previously fetched items.
|
||||
ContentResolverUtils.bulkInsert(resolver, writeUri, values)
|
||||
|
||||
// Remove gap flag
|
||||
if (maxId != null && sinceId == null) {
|
||||
val noGapValues = ContentValues()
|
||||
noGapValues.put(Statuses.IS_GAP, false)
|
||||
val noGapWhere = Expression.and(Expression.equalsArgs(Statuses.ACCOUNT_KEY),
|
||||
Expression.equalsArgs(Statuses.STATUS_ID)).sql
|
||||
val noGapWhereArgs = arrayOf(accountKey.toString(), maxId)
|
||||
resolver.update(writeUri, noGapValues, noGapWhere, noGapWhereArgs)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun getPositionKey(timestamp: Long, sortId: Long, lastSortId: Long, sortDiff: Long,
|
||||
position: Int, count: Int): Long {
|
||||
if (sortDiff == 0L) return timestamp
|
||||
val extraValue: Int
|
||||
if (sortDiff > 0) {
|
||||
// descent sorted by time
|
||||
extraValue = count - 1 - position
|
||||
} else {
|
||||
// ascent sorted by time
|
||||
extraValue = position
|
||||
}
|
||||
return timestamp + (sortId - lastSortId) * (499 - count) / sortDiff + extraValue.toLong()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
package org.mariotaku.twidere.util
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import android.text.TextUtils.isEmpty
|
||||
import okhttp3.ConnectionPool
|
||||
import okhttp3.Credentials
|
||||
import okhttp3.Dns
|
||||
import okhttp3.OkHttpClient
|
||||
import org.apache.commons.lang3.math.NumberUtils
|
||||
import org.mariotaku.restfu.http.RestHttpClient
|
||||
import org.mariotaku.restfu.okhttp3.OkHttpRestClient
|
||||
import org.mariotaku.twidere.constant.SharedPreferenceConstants.*
|
||||
import org.mariotaku.twidere.util.dagger.DependencyHolder
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.Proxy
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/1/27.
|
||||
*/
|
||||
object HttpClientFactory {
|
||||
|
||||
fun createRestHttpClient(context: Context,
|
||||
prefs: SharedPreferencesWrapper, dns: Dns,
|
||||
connectionPool: ConnectionPool): RestHttpClient {
|
||||
val builder = OkHttpClient.Builder()
|
||||
initOkHttpClient(context, prefs, builder, dns, connectionPool)
|
||||
return OkHttpRestClient(builder.build())
|
||||
}
|
||||
|
||||
fun initOkHttpClient(context: Context, prefs: SharedPreferencesWrapper,
|
||||
builder: OkHttpClient.Builder, dns: Dns,
|
||||
connectionPool: ConnectionPool) {
|
||||
updateHttpClientConfiguration(context, builder, prefs, dns, connectionPool)
|
||||
DebugModeUtils.initForOkHttpClient(builder)
|
||||
}
|
||||
|
||||
@SuppressLint("SSLCertificateSocketFactoryGetInsecure")
|
||||
fun updateHttpClientConfiguration(context: Context,
|
||||
builder: OkHttpClient.Builder,
|
||||
prefs: SharedPreferencesWrapper, dns: Dns,
|
||||
connectionPool: ConnectionPool) {
|
||||
val enableProxy = prefs.getBoolean(KEY_ENABLE_PROXY, false)
|
||||
builder.connectTimeout(prefs.getInt(KEY_CONNECTION_TIMEOUT, 10).toLong(), TimeUnit.SECONDS)
|
||||
builder.connectionPool(connectionPool)
|
||||
if (enableProxy) {
|
||||
val proxyType = prefs.getString(KEY_PROXY_TYPE, null)
|
||||
val proxyHost = prefs.getString(KEY_PROXY_HOST, null)
|
||||
val proxyPort = NumberUtils.toInt(prefs.getString(KEY_PROXY_PORT, null), -1)
|
||||
if (!isEmpty(proxyHost) && TwidereMathUtils.inRange(proxyPort, 0, 65535,
|
||||
TwidereMathUtils.RANGE_INCLUSIVE_INCLUSIVE)) {
|
||||
val type = getProxyType(proxyType)
|
||||
if (type != Proxy.Type.DIRECT) {
|
||||
builder.proxy(Proxy(type, InetSocketAddress.createUnresolved(proxyHost, proxyPort)))
|
||||
}
|
||||
}
|
||||
val username = prefs.getString(KEY_PROXY_USERNAME, null)
|
||||
val password = prefs.getString(KEY_PROXY_PASSWORD, null)
|
||||
builder.authenticator { route, response ->
|
||||
val b = response.request().newBuilder()
|
||||
if (response.code() == 407) {
|
||||
if (!TextUtils.isEmpty(username) && !TextUtils.isEmpty(password)) {
|
||||
val credential = Credentials.basic(username, password)
|
||||
b.header("Proxy-Authorization", credential)
|
||||
}
|
||||
}
|
||||
b.build()
|
||||
}
|
||||
}
|
||||
builder.dns(dns)
|
||||
}
|
||||
|
||||
private fun getProxyType(proxyType: String?): Proxy.Type {
|
||||
if (proxyType == null) return Proxy.Type.DIRECT
|
||||
when (proxyType.toLowerCase()) {
|
||||
// case "socks": {
|
||||
// return Proxy.Type.SOCKS;
|
||||
// }
|
||||
"http" -> {
|
||||
return Proxy.Type.HTTP
|
||||
}
|
||||
}
|
||||
return Proxy.Type.DIRECT
|
||||
}
|
||||
|
||||
fun reloadConnectivitySettings(context: Context) {
|
||||
val holder = DependencyHolder.get(context)
|
||||
val client = holder.restHttpClient
|
||||
if (client is OkHttpRestClient) {
|
||||
val builder = OkHttpClient.Builder()
|
||||
initOkHttpClient(context, holder.preferences, builder,
|
||||
holder.dns, holder.connectionPoll)
|
||||
client.client = builder.build()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.content.Context
|
||||
import android.net.Uri
|
||||
import org.mariotaku.pickncrop.library.ImagePickerActivity
|
||||
import org.mariotaku.restfu.annotation.method.GET
|
||||
import org.mariotaku.restfu.http.HttpRequest
|
||||
import org.mariotaku.twidere.util.dagger.DependencyHolder
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 15/6/17.
|
||||
*/
|
||||
class RestFuNetworkStreamDownloader(context: Context) : ImagePickerActivity.NetworkStreamDownloader(context) {
|
||||
|
||||
@Throws(IOException::class)
|
||||
override operator fun get(uri: Uri): ImagePickerActivity.NetworkStreamDownloader.DownloadResult {
|
||||
val client = DependencyHolder.get(context).restHttpClient
|
||||
val builder = HttpRequest.Builder()
|
||||
builder.method(GET.METHOD)
|
||||
builder.url(uri.toString())
|
||||
val response = client.newCall(builder.build()).execute()
|
||||
if (response.isSuccessful) {
|
||||
val body = response.body
|
||||
val contentType = body.contentType()
|
||||
return ImagePickerActivity.NetworkStreamDownloader.DownloadResult.get(body.stream(), if (contentType != null) contentType!!.contentType else "image/*")
|
||||
} else {
|
||||
throw IOException("Unable to get " + uri)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -36,12 +36,14 @@ import dagger.Module
|
||||
import dagger.Provides
|
||||
import edu.tsinghua.hotmobi.HotMobiLogger
|
||||
import okhttp3.ConnectionPool
|
||||
import org.mariotaku.kpreferences.KPreferences
|
||||
import org.mariotaku.mediaviewer.library.FileCache
|
||||
import org.mariotaku.mediaviewer.library.MediaDownloader
|
||||
import org.mariotaku.restfu.http.RestHttpClient
|
||||
import org.mariotaku.twidere.BuildConfig
|
||||
import org.mariotaku.twidere.Constants
|
||||
import org.mariotaku.twidere.constant.SharedPreferenceConstants
|
||||
import org.mariotaku.twidere.model.DefaultFeatures
|
||||
import org.mariotaku.twidere.util.*
|
||||
import org.mariotaku.twidere.util.imageloader.ReadOnlyDiskLRUNameCache
|
||||
import org.mariotaku.twidere.util.imageloader.TwidereImageDownloader
|
||||
@ -89,6 +91,12 @@ class ApplicationModule(private val application: Application) {
|
||||
Context.MODE_PRIVATE, SharedPreferenceConstants::class.java)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun kPreferences(sharedPreferences: SharedPreferencesWrapper): KPreferences {
|
||||
return KPreferences(sharedPreferences)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun permissionsManager(): PermissionsManager {
|
||||
@ -220,6 +228,12 @@ class ApplicationModule(private val application: Application) {
|
||||
return BidiFormatter.getInstance()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun defaultFeatures(): DefaultFeatures {
|
||||
return DefaultFeatures()
|
||||
}
|
||||
|
||||
private fun createDiskCache(dirName: String, preferences: SharedPreferencesWrapper): DiskCache {
|
||||
val cacheDir = Utils.getExternalCacheDir(application, dirName)
|
||||
val fallbackCacheDir = Utils.getInternalCacheDir(application, dirName)
|
@ -256,6 +256,8 @@
|
||||
android:id="@+id/quotedName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignTop="@+id/quotedMediaPreview"
|
||||
android:layout_toEndOf="@+id/quotedMediaPreview"
|
||||
android:layout_toRightOf="@+id/quotedMediaPreview"
|
||||
@ -278,7 +280,9 @@
|
||||
android:id="@+id/quotedText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignEnd="@+id/quotedName"
|
||||
android:layout_alignLeft="@+id/quotedName"
|
||||
android:layout_alignRight="@+id/quotedName"
|
||||
android:layout_alignStart="@+id/quotedName"
|
||||
android:layout_below="@+id/quotedName"
|
||||
android:paddingBottom="@dimen/element_spacing_small"
|
||||
@ -294,6 +298,38 @@
|
||||
tools:text="@string/sample_status_text"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/quotedMediaLabel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignEnd="@+id/quotedName"
|
||||
android:layout_alignLeft="@+id/quotedName"
|
||||
android:layout_alignRight="@+id/quotedName"
|
||||
android:layout_alignStart="@+id/quotedName"
|
||||
android:layout_below="@+id/quotedText"
|
||||
android:layout_marginTop="@dimen/element_spacing_xsmall"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<org.mariotaku.twidere.view.IconActionView
|
||||
android:layout_width="@dimen/element_size_small"
|
||||
android:layout_height="@dimen/element_size_small"
|
||||
android:layout_weight="0"
|
||||
android:color="?android:textColorSecondary"
|
||||
android:scaleType="centerInside"
|
||||
android:src="@drawable/ic_action_gallery"
|
||||
tools:tint="?android:textColorSecondary"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/quotedMediaLabelText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/media"
|
||||
android:textAppearance="?android:textAppearanceSmall"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
</LinearLayout>
|
||||
</org.mariotaku.twidere.view.ColorLabelRelativeLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
Loading…
x
Reference in New Issue
Block a user