code cleanup
This commit is contained in:
parent
ea39c48d55
commit
b842520d53
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<bool name="is_debug">true</bool>
|
||||
</resources>
|
|
@ -1,5 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<integer name="min_database_item_limit">10</integer>
|
||||
<bool name="is_debug">true</bool>
|
||||
</resources>
|
|
@ -461,7 +461,14 @@
|
|||
|
||||
<service
|
||||
android:name=".service.RefreshService"
|
||||
android:enabled="@bool/use_legacy_refresh_service"
|
||||
android:label="@string/label_refresh_service"/>
|
||||
<service
|
||||
android:name=".service.JobRefreshService"
|
||||
android:enabled="@bool/use_job_refresh_service"
|
||||
android:exported="true"
|
||||
android:label="@string/label_refresh_service"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE"/>
|
||||
<service
|
||||
android:name=".service.StreamingService"
|
||||
android:label="@string/label_streaming_service"/>
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.service;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.job.JobParameters;
|
||||
import android.app.job.JobService;
|
||||
import android.os.Build;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 14/12/12.
|
||||
*/
|
||||
@SuppressLint("Registered")
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public class BackgroundJobService extends JobService {
|
||||
@Override
|
||||
public boolean onStartJob(JobParameters params) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStopJob(JobParameters params) {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,411 +0,0 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.service;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.IBinder;
|
||||
import android.os.SystemClock;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.mariotaku.twidere.BuildConfig;
|
||||
import org.mariotaku.twidere.Constants;
|
||||
import org.mariotaku.twidere.model.AccountPreferences;
|
||||
import org.mariotaku.twidere.model.SimpleRefreshTaskParam;
|
||||
import org.mariotaku.twidere.model.UserKey;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Activities;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses;
|
||||
import org.mariotaku.twidere.receiver.PowerStateReceiver;
|
||||
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
|
||||
import org.mariotaku.twidere.util.DataStoreUtils;
|
||||
import org.mariotaku.twidere.util.SharedPreferencesWrapper;
|
||||
import org.mariotaku.twidere.util.Utils;
|
||||
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import edu.tsinghua.hotmobi.model.BatteryRecord;
|
||||
import edu.tsinghua.hotmobi.model.ScreenEvent;
|
||||
|
||||
public class RefreshService extends Service implements Constants {
|
||||
|
||||
@Inject
|
||||
SharedPreferencesWrapper mPreferences;
|
||||
|
||||
private AlarmManager mAlarmManager;
|
||||
@Inject
|
||||
AsyncTwitterWrapper mTwitterWrapper;
|
||||
private PendingIntent mPendingRefreshHomeTimelineIntent, mPendingRefreshMentionsIntent,
|
||||
mPendingRefreshDirectMessagesIntent, mPendingRefreshTrendsIntent;
|
||||
|
||||
private final BroadcastReceiver mStateReceiver = new BroadcastReceiver() {
|
||||
|
||||
@Override
|
||||
public void onReceive(final Context context, final Intent intent) {
|
||||
final String action = intent.getAction();
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(LOGTAG, String.format("Refresh service received action %s", action));
|
||||
}
|
||||
switch (action) {
|
||||
case BROADCAST_RESCHEDULE_HOME_TIMELINE_REFRESHING: {
|
||||
rescheduleHomeTimelineRefreshing();
|
||||
break;
|
||||
}
|
||||
case BROADCAST_RESCHEDULE_MENTIONS_REFRESHING: {
|
||||
rescheduleMentionsRefreshing();
|
||||
break;
|
||||
}
|
||||
case BROADCAST_RESCHEDULE_DIRECT_MESSAGES_REFRESHING: {
|
||||
rescheduleDirectMessagesRefreshing();
|
||||
break;
|
||||
}
|
||||
case BROADCAST_RESCHEDULE_TRENDS_REFRESHING: {
|
||||
rescheduleTrendsRefreshing();
|
||||
break;
|
||||
}
|
||||
case BROADCAST_REFRESH_HOME_TIMELINE: {
|
||||
if (isAutoRefreshAllowed()) {
|
||||
mTwitterWrapper.getHomeTimelineAsync(new SimpleRefreshTaskParam() {
|
||||
private UserKey[] accountIds;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public UserKey[] getAccountKeysWorker() {
|
||||
if (accountIds != null) return accountIds;
|
||||
final AccountPreferences[] prefs = AccountPreferences.getAccountPreferences(context,
|
||||
DataStoreUtils.getAccountKeys(context));
|
||||
return accountIds = getRefreshableIds(prefs, HomeRefreshableFilter.INSTANCE);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String[] getSinceIds() {
|
||||
return DataStoreUtils.getNewestStatusIds(context,
|
||||
Statuses.CONTENT_URI, getAccountKeys());
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case BROADCAST_REFRESH_NOTIFICATIONS: {
|
||||
if (isAutoRefreshAllowed()) {
|
||||
mTwitterWrapper.getActivitiesAboutMeAsync(new SimpleRefreshTaskParam() {
|
||||
private UserKey[] accountIds;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public UserKey[] getAccountKeysWorker() {
|
||||
if (accountIds != null) return accountIds;
|
||||
final AccountPreferences[] prefs = AccountPreferences.getAccountPreferences(context,
|
||||
DataStoreUtils.getAccountKeys(context));
|
||||
return accountIds = getRefreshableIds(prefs, MentionsRefreshableFilter.INSTANCE);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String[] getSinceIds() {
|
||||
return DataStoreUtils.getNewestActivityMaxPositions(context,
|
||||
Activities.AboutMe.CONTENT_URI, getAccountKeys());
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case BROADCAST_REFRESH_DIRECT_MESSAGES: {
|
||||
if (isAutoRefreshAllowed()) {
|
||||
mTwitterWrapper.getReceivedDirectMessagesAsync(new SimpleRefreshTaskParam() {
|
||||
private UserKey[] accountIds;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public UserKey[] getAccountKeysWorker() {
|
||||
if (accountIds != null) return accountIds;
|
||||
final AccountPreferences[] prefs = AccountPreferences.getAccountPreferences(context,
|
||||
DataStoreUtils.getAccountKeys(context));
|
||||
return accountIds = getRefreshableIds(prefs, MessagesRefreshableFilter.INSTANCE);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String[] getSinceIds() {
|
||||
return DataStoreUtils.getNewestMessageIds(context,
|
||||
DirectMessages.Inbox.CONTENT_URI, getAccountKeys());
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case BROADCAST_REFRESH_TRENDS: {
|
||||
if (isAutoRefreshAllowed()) {
|
||||
final AccountPreferences[] prefs = AccountPreferences.getAccountPreferences(context,
|
||||
DataStoreUtils.getAccountKeys(context));
|
||||
final UserKey[] refreshIds = getRefreshableIds(prefs, TrendsRefreshableFilter.INSTANCE);
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(LOGTAG, String.format("Auto refreshing trends for %s", Arrays.toString(refreshIds)));
|
||||
}
|
||||
getLocalTrends(refreshIds);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
private final BroadcastReceiver mPowerStateReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
switch (intent.getAction()) {
|
||||
case Intent.ACTION_BATTERY_CHANGED: {
|
||||
BatteryRecord.log(context, intent);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
BatteryRecord.log(context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final BroadcastReceiver mScreenStateReceiver = new BroadcastReceiver() {
|
||||
public long mPresentTime = -1;
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
switch (intent.getAction()) {
|
||||
case Intent.ACTION_SCREEN_ON: {
|
||||
ScreenEvent.log(context, ScreenEvent.Action.ON, getPresentDuration());
|
||||
break;
|
||||
}
|
||||
case Intent.ACTION_SCREEN_OFF: {
|
||||
ScreenEvent.log(context, ScreenEvent.Action.OFF, getPresentDuration());
|
||||
mPresentTime = -1;
|
||||
break;
|
||||
}
|
||||
case Intent.ACTION_USER_PRESENT: {
|
||||
mPresentTime = SystemClock.elapsedRealtime();
|
||||
ScreenEvent.log(context, ScreenEvent.Action.PRESENT, -1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private long getPresentDuration() {
|
||||
if (mPresentTime < 0) return -1;
|
||||
return SystemClock.elapsedRealtime() - mPresentTime;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public IBinder onBind(final Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
GeneralComponentHelper.build(this).inject(this);
|
||||
mAlarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
|
||||
mPendingRefreshHomeTimelineIntent = PendingIntent.getBroadcast(this, 0, new Intent(
|
||||
BROADCAST_REFRESH_HOME_TIMELINE), 0);
|
||||
mPendingRefreshMentionsIntent = PendingIntent.getBroadcast(this, 0, new Intent(BROADCAST_REFRESH_NOTIFICATIONS), 0);
|
||||
mPendingRefreshDirectMessagesIntent = PendingIntent.getBroadcast(this, 0, new Intent(
|
||||
BROADCAST_REFRESH_DIRECT_MESSAGES), 0);
|
||||
mPendingRefreshTrendsIntent = PendingIntent.getBroadcast(this, 0, new Intent(BROADCAST_REFRESH_TRENDS), 0);
|
||||
final IntentFilter refreshFilter = new IntentFilter(BROADCAST_NOTIFICATION_DELETED);
|
||||
refreshFilter.addAction(BROADCAST_REFRESH_HOME_TIMELINE);
|
||||
refreshFilter.addAction(BROADCAST_REFRESH_NOTIFICATIONS);
|
||||
refreshFilter.addAction(BROADCAST_REFRESH_DIRECT_MESSAGES);
|
||||
refreshFilter.addAction(BROADCAST_RESCHEDULE_HOME_TIMELINE_REFRESHING);
|
||||
refreshFilter.addAction(BROADCAST_RESCHEDULE_MENTIONS_REFRESHING);
|
||||
refreshFilter.addAction(BROADCAST_RESCHEDULE_DIRECT_MESSAGES_REFRESHING);
|
||||
registerReceiver(mStateReceiver, refreshFilter);
|
||||
final IntentFilter batteryFilter = new IntentFilter();
|
||||
batteryFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
|
||||
batteryFilter.addAction(Intent.ACTION_BATTERY_OKAY);
|
||||
batteryFilter.addAction(Intent.ACTION_BATTERY_LOW);
|
||||
batteryFilter.addAction(Intent.ACTION_POWER_CONNECTED);
|
||||
batteryFilter.addAction(Intent.ACTION_POWER_DISCONNECTED);
|
||||
final IntentFilter screenFilter = new IntentFilter();
|
||||
screenFilter.addAction(Intent.ACTION_SCREEN_ON);
|
||||
screenFilter.addAction(Intent.ACTION_SCREEN_OFF);
|
||||
screenFilter.addAction(Intent.ACTION_USER_PRESENT);
|
||||
registerReceiver(mPowerStateReceiver, batteryFilter);
|
||||
registerReceiver(mScreenStateReceiver, screenFilter);
|
||||
PowerStateReceiver.Companion.setServiceReceiverStarted(true);
|
||||
if (Utils.hasAutoRefreshAccounts(this)) {
|
||||
startAutoRefresh();
|
||||
} else {
|
||||
stopSelf();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
PowerStateReceiver.Companion.setServiceReceiverStarted(false);
|
||||
unregisterReceiver(mScreenStateReceiver);
|
||||
unregisterReceiver(mPowerStateReceiver);
|
||||
unregisterReceiver(mStateReceiver);
|
||||
if (Utils.hasAutoRefreshAccounts(this)) {
|
||||
// Auto refresh enabled, so I will try to start service after it was
|
||||
// stopped.
|
||||
startService(new Intent(this, getClass()));
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
protected boolean isAutoRefreshAllowed() {
|
||||
return Utils.isNetworkAvailable(this) && (Utils.isBatteryOkay(this) || !Utils.shouldStopAutoRefreshOnBatteryLow(this));
|
||||
}
|
||||
|
||||
private void getLocalTrends(final UserKey[] accountIds) {
|
||||
final UserKey account_id = Utils.getDefaultAccountKey(this);
|
||||
final int woeid = mPreferences.getInt(KEY_LOCAL_TRENDS_WOEID, 1);
|
||||
mTwitterWrapper.getLocalTrendsAsync(account_id, woeid);
|
||||
}
|
||||
|
||||
private UserKey[] getRefreshableIds(final AccountPreferences[] prefs, final RefreshableAccountFilter filter) {
|
||||
if (prefs == null) return null;
|
||||
final UserKey[] temp = new UserKey[prefs.length];
|
||||
int i = 0;
|
||||
for (final AccountPreferences pref : prefs) {
|
||||
if (pref.isAutoRefreshEnabled() && filter.isRefreshable(pref)) {
|
||||
temp[i++] = pref.getAccountKey();
|
||||
}
|
||||
}
|
||||
final UserKey[] result = new UserKey[i];
|
||||
System.arraycopy(temp, 0, result, 0, i);
|
||||
return result;
|
||||
}
|
||||
|
||||
private long getRefreshInterval() {
|
||||
if (mPreferences == null) return 0;
|
||||
final int prefValue = NumberUtils.toInt(mPreferences.getString(KEY_REFRESH_INTERVAL, DEFAULT_REFRESH_INTERVAL), -1);
|
||||
return Math.max(prefValue, 3) * 60 * 1000;
|
||||
}
|
||||
|
||||
private void rescheduleDirectMessagesRefreshing() {
|
||||
mAlarmManager.cancel(mPendingRefreshDirectMessagesIntent);
|
||||
final long refreshInterval = getRefreshInterval();
|
||||
if (refreshInterval > 0) {
|
||||
mAlarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + refreshInterval,
|
||||
refreshInterval, mPendingRefreshDirectMessagesIntent);
|
||||
}
|
||||
}
|
||||
|
||||
private void rescheduleHomeTimelineRefreshing() {
|
||||
mAlarmManager.cancel(mPendingRefreshHomeTimelineIntent);
|
||||
final long refreshInterval = getRefreshInterval();
|
||||
if (refreshInterval > 0) {
|
||||
mAlarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + refreshInterval,
|
||||
refreshInterval, mPendingRefreshHomeTimelineIntent);
|
||||
}
|
||||
}
|
||||
|
||||
private void rescheduleMentionsRefreshing() {
|
||||
mAlarmManager.cancel(mPendingRefreshMentionsIntent);
|
||||
final long refreshInterval = getRefreshInterval();
|
||||
if (refreshInterval > 0) {
|
||||
mAlarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + refreshInterval,
|
||||
refreshInterval, mPendingRefreshMentionsIntent);
|
||||
}
|
||||
}
|
||||
|
||||
private void rescheduleTrendsRefreshing() {
|
||||
mAlarmManager.cancel(mPendingRefreshTrendsIntent);
|
||||
final long refreshInterval = getRefreshInterval();
|
||||
if (refreshInterval > 0) {
|
||||
mAlarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + refreshInterval,
|
||||
refreshInterval, mPendingRefreshTrendsIntent);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean startAutoRefresh() {
|
||||
stopAutoRefresh();
|
||||
final long refreshInterval = getRefreshInterval();
|
||||
if (refreshInterval <= 0) return false;
|
||||
rescheduleHomeTimelineRefreshing();
|
||||
rescheduleMentionsRefreshing();
|
||||
rescheduleDirectMessagesRefreshing();
|
||||
rescheduleTrendsRefreshing();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void stopAutoRefresh() {
|
||||
mAlarmManager.cancel(mPendingRefreshHomeTimelineIntent);
|
||||
mAlarmManager.cancel(mPendingRefreshMentionsIntent);
|
||||
mAlarmManager.cancel(mPendingRefreshDirectMessagesIntent);
|
||||
mAlarmManager.cancel(mPendingRefreshTrendsIntent);
|
||||
}
|
||||
|
||||
private interface RefreshableAccountFilter {
|
||||
boolean isRefreshable(AccountPreferences pref);
|
||||
}
|
||||
|
||||
private static class HomeRefreshableFilter implements RefreshableAccountFilter {
|
||||
public static final RefreshableAccountFilter INSTANCE = new HomeRefreshableFilter();
|
||||
|
||||
@Override
|
||||
public boolean isRefreshable(final AccountPreferences pref) {
|
||||
return pref.isAutoRefreshHomeTimelineEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
private static class MentionsRefreshableFilter implements RefreshableAccountFilter {
|
||||
|
||||
static final RefreshableAccountFilter INSTANCE = new MentionsRefreshableFilter();
|
||||
|
||||
@Override
|
||||
public boolean isRefreshable(final AccountPreferences pref) {
|
||||
return pref.isAutoRefreshMentionsEnabled();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class MessagesRefreshableFilter implements RefreshableAccountFilter {
|
||||
public static final RefreshableAccountFilter INSTANCE = new MentionsRefreshableFilter();
|
||||
|
||||
@Override
|
||||
public boolean isRefreshable(final AccountPreferences pref) {
|
||||
return pref.isAutoRefreshDirectMessagesEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
private static class TrendsRefreshableFilter implements RefreshableAccountFilter {
|
||||
public static final RefreshableAccountFilter INSTANCE = new TrendsRefreshableFilter();
|
||||
|
||||
@Override
|
||||
public boolean isRefreshable(final AccountPreferences pref) {
|
||||
return pref.isAutoRefreshTrendsEnabled();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,156 +0,0 @@
|
|||
package org.mariotaku.twidere.task;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import com.squareup.otto.Bus;
|
||||
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
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.DirectMessage;
|
||||
import org.mariotaku.microblog.library.twitter.model.ErrorInfo;
|
||||
import org.mariotaku.microblog.library.twitter.model.Paging;
|
||||
import org.mariotaku.microblog.library.twitter.model.ResponseList;
|
||||
import org.mariotaku.twidere.BuildConfig;
|
||||
import org.mariotaku.twidere.Constants;
|
||||
import org.mariotaku.twidere.TwidereConstants;
|
||||
import org.mariotaku.twidere.model.RefreshTaskParam;
|
||||
import org.mariotaku.twidere.model.UserKey;
|
||||
import org.mariotaku.twidere.model.message.GetMessagesTaskEvent;
|
||||
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
|
||||
import org.mariotaku.twidere.util.ContentValuesCreator;
|
||||
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.content.ContentResolverUtils;
|
||||
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/2/14.
|
||||
*/
|
||||
public abstract class GetDirectMessagesTask extends AbstractTask<RefreshTaskParam,
|
||||
List<TwitterWrapper.MessageListResponse>, Object> implements Constants {
|
||||
|
||||
protected final Context context;
|
||||
@Inject
|
||||
protected ErrorInfoStore errorInfoStore;
|
||||
@Inject
|
||||
protected SharedPreferencesWrapper preferences;
|
||||
@Inject
|
||||
protected Bus bus;
|
||||
|
||||
public GetDirectMessagesTask(Context context) {
|
||||
this.context = context;
|
||||
GeneralComponentHelper.build(context).inject(this);
|
||||
}
|
||||
|
||||
public abstract ResponseList<DirectMessage> getDirectMessages(MicroBlog twitter, Paging paging)
|
||||
throws MicroBlogException;
|
||||
|
||||
protected abstract Uri getDatabaseUri();
|
||||
|
||||
protected abstract boolean isOutgoing();
|
||||
|
||||
@Override
|
||||
public List<TwitterWrapper.MessageListResponse> doLongOperation(final RefreshTaskParam param) {
|
||||
final UserKey[] accountKeys = param.getAccountKeys();
|
||||
final String[] sinceIds = param.getSinceIds(), maxIds = param.getMaxIds();
|
||||
final List<TwitterWrapper.MessageListResponse> 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 MicroBlog twitter = MicroBlogAPIFactory.getInstance(context, accountKey);
|
||||
if (twitter == null) continue;
|
||||
try {
|
||||
final Paging paging = new Paging();
|
||||
paging.setCount(loadItemLimit);
|
||||
String maxId = null, sinceId = null;
|
||||
if (maxIds != null && maxIds[idx] != null) {
|
||||
maxId = maxIds[idx];
|
||||
paging.setMaxId(maxId);
|
||||
}
|
||||
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 (maxIds == null || sinceIds[idx] == null) {
|
||||
paging.setLatestResults(true);
|
||||
}
|
||||
}
|
||||
final List<DirectMessage> messages = getDirectMessages(twitter, paging);
|
||||
result.add(new TwitterWrapper.MessageListResponse(accountKey, maxId, sinceId, messages));
|
||||
storeMessages(accountKey, messages, isOutgoing(), true);
|
||||
errorInfoStore.remove(ErrorInfoStore.KEY_DIRECT_MESSAGES, accountKey);
|
||||
} catch (final MicroBlogException e) {
|
||||
if (e.getErrorCode() == ErrorInfo.NO_DIRECT_MESSAGE_PERMISSION) {
|
||||
errorInfoStore.put(ErrorInfoStore.KEY_DIRECT_MESSAGES, accountKey,
|
||||
ErrorInfoStore.CODE_NO_DM_PERMISSION);
|
||||
} else if (e.isCausedByNetworkIssue()) {
|
||||
errorInfoStore.put(ErrorInfoStore.KEY_DIRECT_MESSAGES, accountKey,
|
||||
ErrorInfoStore.CODE_NETWORK_ERROR);
|
||||
}
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.w(TwidereConstants.LOGTAG, e);
|
||||
}
|
||||
result.add(new TwitterWrapper.MessageListResponse(accountKey, e));
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
private boolean storeMessages(UserKey accountKey, List<DirectMessage> messages, boolean isOutgoing, boolean notify) {
|
||||
if (messages == null) return true;
|
||||
final Uri uri = getDatabaseUri();
|
||||
final ContentValues[] valuesArray = new ContentValues[messages.size()];
|
||||
|
||||
for (int i = 0, j = messages.size(); i < j; i++) {
|
||||
final DirectMessage message = messages.get(i);
|
||||
try {
|
||||
valuesArray[i] = ContentValuesCreator.createDirectMessage(message, accountKey, isOutgoing);
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Delete all rows conflicting before new data inserted.
|
||||
// final Expression deleteWhere = Expression.and(Expression.equals(DirectMessages.ACCOUNT_ID, accountKey),
|
||||
// Expression.in(new Column(DirectMessages.MESSAGE_ID), new RawItemArray(messageIds)));
|
||||
// final Uri deleteUri = UriUtils.appendQueryParameters(uri, QUERY_PARAM_NOTIFY, false);
|
||||
// mResolver.delete(deleteUri, deleteWhere.getSQL(), null);
|
||||
|
||||
|
||||
// Insert previously fetched items.
|
||||
final Uri insertUri = UriUtils.appendQueryParameters(uri, TwidereConstants.QUERY_PARAM_NOTIFY, notify);
|
||||
ContentResolverUtils.bulkInsert(context.getContentResolver(), insertUri, valuesArray);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public void beforeExecute(RefreshTaskParam params) {
|
||||
bus.post(new GetMessagesTaskEvent(getDatabaseUri(), true, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void afterExecute(Object handler, List<TwitterWrapper.MessageListResponse> result) {
|
||||
bus.post(new GetMessagesTaskEvent(getDatabaseUri(), false, AsyncTwitterWrapper.getException(result)));
|
||||
}
|
||||
}
|
|
@ -1,37 +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.Trends;
|
||||
import org.mariotaku.twidere.model.UserKey;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.CachedTrends;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/2/24.
|
||||
*/
|
||||
public class GetLocalTrendsTask extends GetTrendsTask {
|
||||
|
||||
private final int woeid;
|
||||
|
||||
public GetLocalTrendsTask(final Context context, final UserKey accountKey, final int woeid) {
|
||||
super(context, accountKey);
|
||||
this.woeid = woeid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Trends> getTrends(@NonNull final MicroBlog twitter) throws MicroBlogException {
|
||||
return twitter.getLocationTrends(woeid);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Uri getContentUri() {
|
||||
return CachedTrends.Local.CONTENT_URI;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
package org.mariotaku.twidere.task;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
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.ResponseList;
|
||||
import org.mariotaku.microblog.library.twitter.model.SavedSearch;
|
||||
import org.mariotaku.sqliteqb.library.Expression;
|
||||
import org.mariotaku.twidere.BuildConfig;
|
||||
import org.mariotaku.twidere.model.SingleResponse;
|
||||
import org.mariotaku.twidere.model.UserKey;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.SavedSearches;
|
||||
import org.mariotaku.twidere.util.ContentValuesCreator;
|
||||
import org.mariotaku.twidere.util.MicroBlogAPIFactory;
|
||||
import org.mariotaku.twidere.util.content.ContentResolverUtils;
|
||||
|
||||
import static org.mariotaku.twidere.TwidereConstants.LOGTAG;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/2/13.
|
||||
*/
|
||||
public class GetSavedSearchesTask extends AbstractTask<UserKey[], SingleResponse<Object>, Object> {
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
public GetSavedSearchesTask(Context context) {
|
||||
this.mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SingleResponse<Object> doLongOperation(UserKey[] params) {
|
||||
final ContentResolver cr = mContext.getContentResolver();
|
||||
for (UserKey accountKey : params) {
|
||||
final MicroBlog twitter = MicroBlogAPIFactory.getInstance(mContext, accountKey);
|
||||
if (twitter == null) continue;
|
||||
try {
|
||||
final ResponseList<SavedSearch> searches = twitter.getSavedSearches();
|
||||
final ContentValues[] values = ContentValuesCreator.createSavedSearches(searches,
|
||||
accountKey);
|
||||
final Expression where = Expression.equalsArgs(SavedSearches.ACCOUNT_KEY);
|
||||
final String[] whereArgs = {accountKey.toString()};
|
||||
cr.delete(SavedSearches.CONTENT_URI, where.getSQL(), whereArgs);
|
||||
ContentResolverUtils.bulkInsert(cr, SavedSearches.CONTENT_URI, values);
|
||||
} catch (MicroBlogException e) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.w(LOGTAG, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return SingleResponse.Companion.getInstance();
|
||||
}
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
package org.mariotaku.twidere.task;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.squareup.otto.Bus;
|
||||
|
||||
import org.mariotaku.abstask.library.AbstractTask;
|
||||
import org.mariotaku.microblog.library.MicroBlog;
|
||||
import org.mariotaku.microblog.library.MicroBlogException;
|
||||
import org.mariotaku.microblog.library.twitter.model.Trends;
|
||||
import org.mariotaku.twidere.model.UserKey;
|
||||
import org.mariotaku.twidere.model.message.TrendsRefreshedEvent;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore;
|
||||
import org.mariotaku.twidere.util.ContentValuesCreator;
|
||||
import org.mariotaku.twidere.util.MicroBlogAPIFactory;
|
||||
import org.mariotaku.twidere.util.content.ContentResolverUtils;
|
||||
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/2/24.
|
||||
*/
|
||||
public abstract class GetTrendsTask extends AbstractTask<Object, Object, Object> {
|
||||
|
||||
private final Context mContext;
|
||||
private final UserKey mAccountId;
|
||||
|
||||
@Inject
|
||||
protected Bus mBus;
|
||||
|
||||
public GetTrendsTask(Context context, final UserKey accountKey) {
|
||||
GeneralComponentHelper.build(context).inject(this);
|
||||
this.mContext = context;
|
||||
this.mAccountId = accountKey;
|
||||
}
|
||||
|
||||
public abstract List<Trends> getTrends(@NonNull MicroBlog twitter) throws MicroBlogException;
|
||||
|
||||
@Override
|
||||
public Object doLongOperation(final Object param) {
|
||||
final MicroBlog twitter = MicroBlogAPIFactory.getInstance(mContext, mAccountId);
|
||||
if (twitter == null) return null;
|
||||
try {
|
||||
final List<Trends> trends = getTrends(twitter);
|
||||
storeTrends(mContext.getContentResolver(), getContentUri(), trends);
|
||||
return null;
|
||||
} catch (final MicroBlogException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void afterExecute(Object handler, Object result) {
|
||||
mBus.post(new TrendsRefreshedEvent());
|
||||
}
|
||||
|
||||
protected abstract Uri getContentUri();
|
||||
|
||||
private static void storeTrends(ContentResolver cr, Uri uri, List<Trends> trendsList) {
|
||||
final ArrayList<String> hashtags = new ArrayList<>();
|
||||
final ArrayList<ContentValues> hashtagValues = new ArrayList<>();
|
||||
if (trendsList != null && trendsList.size() > 0) {
|
||||
final ContentValues[] valuesArray = ContentValuesCreator.createTrends(trendsList);
|
||||
for (final ContentValues values : valuesArray) {
|
||||
final String hashtag = values.getAsString(TwidereDataStore.CachedTrends.NAME).replaceFirst("#", "");
|
||||
if (hashtags.contains(hashtag)) {
|
||||
continue;
|
||||
}
|
||||
hashtags.add(hashtag);
|
||||
final ContentValues hashtagValue = new ContentValues();
|
||||
hashtagValue.put(TwidereDataStore.CachedHashtags.NAME, hashtag);
|
||||
hashtagValues.add(hashtagValue);
|
||||
}
|
||||
cr.delete(uri, null, null);
|
||||
ContentResolverUtils.bulkInsert(cr, uri, valuesArray);
|
||||
ContentResolverUtils.bulkDelete(cr, TwidereDataStore.CachedHashtags.CONTENT_URI, TwidereDataStore.CachedHashtags.NAME, hashtags, null);
|
||||
ContentResolverUtils.bulkInsert(cr, TwidereDataStore.CachedHashtags.CONTENT_URI,
|
||||
hashtagValues.toArray(new ContentValues[hashtagValues.size()]));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,82 +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.task;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.media.MediaScannerConnection;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.mariotaku.twidere.R;
|
||||
import org.mariotaku.twidere.provider.CacheProvider;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 15/12/28.
|
||||
*/
|
||||
public class SaveMediaToGalleryTask extends ProgressSaveFileTask {
|
||||
|
||||
public SaveMediaToGalleryTask(@NonNull Activity activity, @NonNull Uri source, @NonNull File destination, String type) {
|
||||
super(activity, source, destination, new CacheProvider.CacheFileTypeCallback(activity, type));
|
||||
}
|
||||
|
||||
public static SaveFileTask create(final Activity activity, final Uri source,
|
||||
@NonNull @CacheProvider.Type final String type) {
|
||||
final File pubDir;
|
||||
switch (type) {
|
||||
case CacheProvider.Type.VIDEO: {
|
||||
pubDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES);
|
||||
break;
|
||||
}
|
||||
case CacheProvider.Type.IMAGE: {
|
||||
pubDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
pubDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||
break;
|
||||
}
|
||||
}
|
||||
final File saveDir = new File(pubDir, "Twidere");
|
||||
return new SaveMediaToGalleryTask(activity, source, saveDir, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFileSaved(@NonNull File savedFile, @Nullable String mimeType) {
|
||||
final Context context = getContext();
|
||||
if (context == null) return;
|
||||
MediaScannerConnection.scanFile(context, new String[]{savedFile.getPath()},
|
||||
new String[]{mimeType}, null);
|
||||
Toast.makeText(context, R.string.saved_to_gallery, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFileSaveFailed() {
|
||||
final Context context = getContext();
|
||||
if (context == null) return;
|
||||
Toast.makeText(context, R.string.error_occurred, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
}
|
|
@ -47,9 +47,7 @@ import org.mariotaku.microblog.library.twitter.http.HttpResponseCode;
|
|||
import org.mariotaku.microblog.library.twitter.model.DirectMessage;
|
||||
import org.mariotaku.microblog.library.twitter.model.ErrorInfo;
|
||||
import org.mariotaku.microblog.library.twitter.model.FriendshipUpdate;
|
||||
import org.mariotaku.microblog.library.twitter.model.Paging;
|
||||
import org.mariotaku.microblog.library.twitter.model.Relationship;
|
||||
import org.mariotaku.microblog.library.twitter.model.ResponseList;
|
||||
import org.mariotaku.microblog.library.twitter.model.SavedSearch;
|
||||
import org.mariotaku.microblog.library.twitter.model.User;
|
||||
import org.mariotaku.microblog.library.twitter.model.UserList;
|
||||
|
@ -106,10 +104,11 @@ import org.mariotaku.twidere.task.DestroyStatusTask;
|
|||
import org.mariotaku.twidere.task.DestroyUserBlockTask;
|
||||
import org.mariotaku.twidere.task.DestroyUserMuteTask;
|
||||
import org.mariotaku.twidere.task.GetActivitiesAboutMeTask;
|
||||
import org.mariotaku.twidere.task.GetDirectMessagesTask;
|
||||
import org.mariotaku.twidere.task.GetHomeTimelineTask;
|
||||
import org.mariotaku.twidere.task.GetLocalTrendsTask;
|
||||
import org.mariotaku.twidere.task.GetReceivedDirectMessagesTask;
|
||||
import org.mariotaku.twidere.task.GetSavedSearchesTask;
|
||||
import org.mariotaku.twidere.task.GetSentDirectMessagesTask;
|
||||
import org.mariotaku.twidere.task.ManagedAsyncTask;
|
||||
import org.mariotaku.twidere.task.ReportSpamAndBlockTask;
|
||||
import org.mariotaku.twidere.task.twitter.GetActivitiesTask;
|
||||
|
@ -1325,61 +1324,6 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
|
|||
|
||||
}
|
||||
|
||||
static class GetReceivedDirectMessagesTask extends GetDirectMessagesTask {
|
||||
|
||||
public GetReceivedDirectMessagesTask(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseList<DirectMessage> getDirectMessages(final MicroBlog twitter, final Paging paging)
|
||||
throws MicroBlogException {
|
||||
return twitter.getDirectMessages(paging);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Uri getDatabaseUri() {
|
||||
return Inbox.CONTENT_URI;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean isOutgoing() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeExecute(RefreshTaskParam params) {
|
||||
final Intent intent = new Intent(BROADCAST_RESCHEDULE_DIRECT_MESSAGES_REFRESHING);
|
||||
context.sendBroadcast(intent);
|
||||
super.beforeExecute(params);
|
||||
}
|
||||
}
|
||||
|
||||
static class GetSentDirectMessagesTask extends GetDirectMessagesTask {
|
||||
|
||||
public GetSentDirectMessagesTask(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseList<DirectMessage> getDirectMessages(final MicroBlog twitter, final Paging paging)
|
||||
throws MicroBlogException {
|
||||
return twitter.getSentDirectMessages(paging);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isOutgoing() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Uri getDatabaseUri() {
|
||||
return Outbox.CONTENT_URI;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public SharedPreferencesWrapper getPreferences() {
|
||||
return preferences;
|
||||
}
|
||||
|
|
|
@ -1187,6 +1187,7 @@ public final class Utils implements Constants {
|
|||
public static void startRefreshServiceIfNeeded(@NonNull final Context context) {
|
||||
final Context appContext = context.getApplicationContext();
|
||||
if (appContext == null) return;
|
||||
if (!appContext.getResources().getBoolean(R.bool.use_legacy_refresh_service)) return;
|
||||
final Intent refreshServiceIntent = new Intent(appContext, RefreshService.class);
|
||||
AsyncTask.execute(new Runnable() {
|
||||
@Override
|
||||
|
|
|
@ -396,15 +396,11 @@ class MediaViewerActivity : BaseActivity(), IExtendedActivity, ATEToolbarCustomi
|
|||
val viewPager = findViewPager()
|
||||
val adapter = viewPager.adapter
|
||||
val f = adapter.instantiateItem(viewPager, saveToStoragePosition) as? CacheDownloadMediaViewerFragment ?: return
|
||||
val result = f.downloadResult ?: return
|
||||
val cacheUri = result.cacheUri
|
||||
val hasMedia = cacheUri != null
|
||||
if (!hasMedia) return
|
||||
val task: SaveFileTask
|
||||
when (f) {
|
||||
is ImagePageFragment -> task = SaveMediaToGalleryTask.create(this, cacheUri, CacheProvider.Type.IMAGE)
|
||||
is VideoPageFragment -> task = SaveMediaToGalleryTask.create(this, cacheUri, CacheProvider.Type.VIDEO)
|
||||
is GifPageFragment -> task = SaveMediaToGalleryTask.create(this, cacheUri, CacheProvider.Type.IMAGE)
|
||||
val cacheUri = f.downloadResult?.cacheUri ?: return
|
||||
val task: SaveFileTask = when (f) {
|
||||
is ImagePageFragment -> SaveMediaToGalleryTask.create(this, cacheUri, CacheProvider.Type.IMAGE)
|
||||
is VideoPageFragment -> SaveMediaToGalleryTask.create(this, cacheUri, CacheProvider.Type.VIDEO)
|
||||
is GifPageFragment -> SaveMediaToGalleryTask.create(this, cacheUri, CacheProvider.Type.IMAGE)
|
||||
else -> throw UnsupportedOperationException()
|
||||
}
|
||||
AsyncTaskUtils.executeTask(task)
|
||||
|
|
|
@ -27,7 +27,6 @@ import android.database.sqlite.SQLiteDatabase
|
|||
import android.database.sqlite.SQLiteOpenHelper
|
||||
import android.graphics.Color
|
||||
import android.os.AsyncTask
|
||||
import android.os.Handler
|
||||
import android.support.design.widget.FloatingActionButton
|
||||
import android.support.multidex.MultiDex
|
||||
import android.support.v4.content.ContextCompat
|
||||
|
@ -46,6 +45,8 @@ 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.kpreferences.get
|
||||
import org.mariotaku.kpreferences.set
|
||||
import org.mariotaku.ktextension.configure
|
||||
import org.mariotaku.mediaviewer.library.MediaDownloader
|
||||
import org.mariotaku.restfu.http.RestHttpClient
|
||||
|
@ -56,6 +57,8 @@ 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.apiLastChangeKey
|
||||
import org.mariotaku.twidere.constant.bugReportsKey
|
||||
import org.mariotaku.twidere.constant.defaultFeatureLastUpdated
|
||||
import org.mariotaku.twidere.model.DefaultFeatures
|
||||
import org.mariotaku.twidere.service.RefreshService
|
||||
|
@ -89,25 +92,14 @@ class TwidereApplication : Application(), Constants, OnSharedPreferenceChangeLis
|
|||
@Inject
|
||||
lateinit internal var kPreferences: KPreferences
|
||||
|
||||
var handler: Handler? = null
|
||||
private set
|
||||
|
||||
private var profileImageViewViewProcessor: ProfileImageViewViewProcessor? = null
|
||||
private var fontFamilyTagProcessor: FontFamilyTagProcessor? = null
|
||||
private lateinit var profileImageViewViewProcessor: ProfileImageViewViewProcessor
|
||||
private lateinit var fontFamilyTagProcessor: FontFamilyTagProcessor
|
||||
|
||||
override fun attachBaseContext(base: Context) {
|
||||
super.attachBaseContext(base)
|
||||
MultiDex.install(this)
|
||||
}
|
||||
|
||||
fun initKeyboardShortcuts() {
|
||||
val preferences = sharedPreferences
|
||||
if (!preferences.getBoolean(KEY_KEYBOARD_SHORTCUT_INITIALIZED, false)) {
|
||||
// getApplicationModule().getKeyboardShortcutsHandler().reset();
|
||||
preferences.edit().putBoolean(KEY_KEYBOARD_SHORTCUT_INITIALIZED, true).apply()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val sqLiteDatabase: SQLiteDatabase by lazy {
|
||||
StrictModeUtils.checkDiskIO()
|
||||
|
@ -131,12 +123,14 @@ class TwidereApplication : Application(), Constants, OnSharedPreferenceChangeLis
|
|||
initializeAsyncTask()
|
||||
initDebugMode()
|
||||
initBugReport()
|
||||
handler = Handler()
|
||||
|
||||
updateEasterEggIcon()
|
||||
|
||||
migrateUsageStatisticsPreferences()
|
||||
if (resources.getBoolean(R.bool.use_job_refresh_service)) {
|
||||
} else {
|
||||
Utils.startRefreshServiceIfNeeded(this)
|
||||
}
|
||||
|
||||
GeneralComponentHelper.build(this).inject(this)
|
||||
|
||||
|
@ -225,9 +219,9 @@ class TwidereApplication : Application(), Constants, OnSharedPreferenceChangeLis
|
|||
ATE.registerViewProcessor(ImageView::class.java, ImageViewViewProcessor())
|
||||
ATE.registerViewProcessor(MaterialEditText::class.java, MaterialEditTextViewProcessor())
|
||||
ATE.registerViewProcessor(ProgressWheel::class.java, ProgressWheelViewProcessor())
|
||||
ATE.registerViewProcessor(ProfileImageView::class.java, profileImageViewViewProcessor!!)
|
||||
ATE.registerViewProcessor(ProfileImageView::class.java, profileImageViewViewProcessor)
|
||||
ATE.registerTagProcessor(OptimalLinkColorTagProcessor.TAG, OptimalLinkColorTagProcessor())
|
||||
ATE.registerTagProcessor(FontFamilyTagProcessor.TAG, fontFamilyTagProcessor!!)
|
||||
ATE.registerTagProcessor(FontFamilyTagProcessor.TAG, fontFamilyTagProcessor)
|
||||
ATE.registerTagProcessor(IconActionButtonTagProcessor.PREFIX_COLOR,
|
||||
IconActionButtonTagProcessor(IconActionButtonTagProcessor.PREFIX_COLOR))
|
||||
ATE.registerTagProcessor(IconActionButtonTagProcessor.PREFIX_COLOR_ACTIVATED,
|
||||
|
@ -256,8 +250,7 @@ class TwidereApplication : Application(), Constants, OnSharedPreferenceChangeLis
|
|||
}
|
||||
|
||||
private fun initBugReport() {
|
||||
val preferences = sharedPreferences
|
||||
if (!preferences.getBoolean(KEY_BUG_REPORTS, BuildConfig.DEBUG)) return
|
||||
if (!sharedPreferences[bugReportsKey]) return
|
||||
BugReporter.setImplementation(TwidereBugReporter())
|
||||
BugReporter.init(this)
|
||||
}
|
||||
|
@ -305,9 +298,7 @@ class TwidereApplication : Application(), Constants, OnSharedPreferenceChangeLis
|
|||
}
|
||||
KEY_CONSUMER_KEY, KEY_CONSUMER_SECRET, KEY_API_URL_FORMAT, KEY_CREDENTIALS_TYPE,
|
||||
KEY_SAME_OAUTH_SIGNING_URL, KEY_THUMBOR_ENABLED, KEY_THUMBOR_ADDRESS, KEY_THUMBOR_SECURITY_KEY -> {
|
||||
val editor = preferences.edit()
|
||||
editor.putLong(KEY_API_LAST_CHANGE, System.currentTimeMillis())
|
||||
editor.apply()
|
||||
preferences[apiLastChangeKey] = System.currentTimeMillis()
|
||||
}
|
||||
KEY_EMOJI_SUPPORT -> {
|
||||
externalThemeManager.reloadEmojiPreferences()
|
||||
|
@ -321,11 +312,11 @@ class TwidereApplication : Application(), Constants, OnSharedPreferenceChangeLis
|
|||
}
|
||||
KEY_PROFILE_IMAGE_STYLE -> {
|
||||
Config.markChanged(this, VALUE_THEME_NAME_LIGHT, VALUE_THEME_NAME_DARK)
|
||||
profileImageViewViewProcessor!!.setStyle(Utils.getProfileImageStyle(preferences.getString(key, null)))
|
||||
profileImageViewViewProcessor.setStyle(Utils.getProfileImageStyle(preferences.getString(key, null)))
|
||||
}
|
||||
KEY_THEME_FONT_FAMILY -> {
|
||||
Config.markChanged(this, VALUE_THEME_NAME_LIGHT, VALUE_THEME_NAME_DARK)
|
||||
fontFamilyTagProcessor!!.setFontFamily(ThemeUtils.getThemeFontFamily(preferences))
|
||||
fontFamilyTagProcessor.setFontFamily(ThemeUtils.getThemeFontFamily(preferences))
|
||||
}
|
||||
KEY_THEME_COLOR -> {
|
||||
val themeColor = preferences.getInt(key, ContextCompat.getColor(this,
|
||||
|
|
|
@ -4,10 +4,9 @@ import android.content.SharedPreferences
|
|||
import android.os.Build
|
||||
import android.text.TextUtils
|
||||
import org.mariotaku.kpreferences.*
|
||||
import org.mariotaku.twidere.BuildConfig
|
||||
import org.mariotaku.twidere.Constants.KEY_NO_CLOSE_AFTER_TWEET_SENT
|
||||
import org.mariotaku.twidere.TwidereConstants.*
|
||||
import org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_ATTACH_PRECISE_LOCATION
|
||||
import org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_SETTINGS_WIZARD_COMPLETED
|
||||
import org.mariotaku.twidere.extension.getNonEmptyString
|
||||
import org.mariotaku.twidere.model.CustomAPIConfig
|
||||
import org.mariotaku.twidere.model.account.cred.Credentials
|
||||
|
@ -37,6 +36,9 @@ val noCloseAfterTweetSentKey = KBooleanKey(KEY_NO_CLOSE_AFTER_TWEET_SENT, false)
|
|||
val loadItemLimitKey = KIntKey(KEY_LOAD_ITEM_LIMIT, DEFAULT_LOAD_ITEM_LIMIT)
|
||||
val defaultFeatureLastUpdated = KLongKey("default_feature_last_updated", -1)
|
||||
val drawerTutorialCompleted = KBooleanKey(KEY_SETTINGS_WIZARD_COMPLETED, false)
|
||||
val stopAutoRefreshWhenBatteryLowKey = KBooleanKey(KEY_STOP_AUTO_REFRESH_WHEN_BATTERY_LOW, true)
|
||||
val apiLastChangeKey = KLongKey(KEY_API_LAST_CHANGE, -1)
|
||||
val bugReportsKey = KBooleanKey(KEY_BUG_REPORTS, BuildConfig.DEBUG)
|
||||
|
||||
object defaultAPIConfigKey : KPreferenceKey<CustomAPIConfig> {
|
||||
override fun contains(preferences: SharedPreferences): Boolean {
|
||||
|
|
|
@ -41,9 +41,7 @@ import android.os.Looper
|
|||
import android.support.design.widget.NavigationView
|
||||
import android.support.v4.app.LoaderManager.LoaderCallbacks
|
||||
import android.support.v4.content.AsyncTaskLoader
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.support.v4.content.Loader
|
||||
import android.support.v4.graphics.drawable.DrawableCompat
|
||||
import android.support.v4.view.MenuItemCompat
|
||||
import android.support.v4.view.ViewPager
|
||||
import android.support.v7.view.SupportMenuInflater
|
||||
|
@ -55,7 +53,10 @@ import android.view.View.OnClickListener
|
|||
import android.view.animation.DecelerateInterpolator
|
||||
import android.widget.ImageView
|
||||
import kotlinx.android.synthetic.main.header_drawer_account_selector.view.*
|
||||
import org.mariotaku.ktextension.*
|
||||
import org.mariotaku.ktextension.addOnAccountsUpdatedListenerSafe
|
||||
import org.mariotaku.ktextension.convert
|
||||
import org.mariotaku.ktextension.removeOnAccountsUpdatedListenerSafe
|
||||
import org.mariotaku.ktextension.setItemAvailability
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.TwidereConstants.*
|
||||
import org.mariotaku.twidere.activity.*
|
||||
|
@ -79,9 +80,9 @@ import java.util.*
|
|||
|
||||
class AccountsDashboardFragment : BaseSupportFragment(), LoaderCallbacks<AccountsInfo>, OnSharedPreferenceChangeListener, OnClickListener, KeyboardShortcutCallback, NavigationView.OnNavigationItemSelectedListener {
|
||||
|
||||
private val mSystemWindowsInsets = Rect()
|
||||
private val systemWindowsInsets = Rect()
|
||||
|
||||
private var accountsAdapter: AccountSelectorAdapter? = null
|
||||
private lateinit var accountsAdapter: AccountSelectorAdapter
|
||||
|
||||
private val hasNextAccountIndicator by lazy { accountsHeader.hasNextAccountIndicator }
|
||||
private val hasPrevAccountIndicator by lazy { accountsHeader.hasPrevAccountIndicator }
|
||||
|
@ -113,7 +114,7 @@ class AccountsDashboardFragment : BaseSupportFragment(), LoaderCallbacks<Account
|
|||
accountsSelector.adapter = accountsAdapter
|
||||
accountsSelector.addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener() {
|
||||
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
|
||||
val adapter = accountsAdapter ?: return
|
||||
val adapter = accountsAdapter
|
||||
val pagePosition = position + positionOffset
|
||||
val pageCount = adapter.count
|
||||
val visiblePages = 1 / adapter.getPageWidth(position)
|
||||
|
@ -145,7 +146,7 @@ class AccountsDashboardFragment : BaseSupportFragment(), LoaderCallbacks<Account
|
|||
} else {
|
||||
when (item.itemId) {
|
||||
R.id.compose -> {
|
||||
val account = accountsAdapter!!.selectedAccount ?: return@OnMenuItemClickListener true
|
||||
val account = accountsAdapter.selectedAccount ?: return@OnMenuItemClickListener true
|
||||
val composeIntent = Intent(INTENT_ACTION_COMPOSE)
|
||||
composeIntent.setClass(activity, ComposeActivity::class.java)
|
||||
composeIntent.putExtra(EXTRA_ACCOUNT_KEY, account.key)
|
||||
|
@ -225,7 +226,7 @@ class AccountsDashboardFragment : BaseSupportFragment(), LoaderCallbacks<Account
|
|||
override fun onClick(v: View) {
|
||||
when (v.id) {
|
||||
R.id.profileContainer -> {
|
||||
val account = accountsAdapter!!.selectedAccount ?: return
|
||||
val account = accountsAdapter.selectedAccount ?: return
|
||||
val activity = activity
|
||||
if (account.user != null) {
|
||||
IntentUtils.openUserProfile(activity, account.user!!, null,
|
||||
|
@ -262,18 +263,18 @@ class AccountsDashboardFragment : BaseSupportFragment(), LoaderCallbacks<Account
|
|||
profileContainer.visibility = View.INVISIBLE
|
||||
}
|
||||
useStarsForLikes = preferences.getBoolean(KEY_I_WANT_MY_STARS_BACK)
|
||||
accountsAdapter!!.accounts = accounts
|
||||
accountsAdapter.accounts = accounts
|
||||
val defaultKey = preferences.getString(KEY_DEFAULT_ACCOUNT_KEY, null)?.convert(UserKey::valueOf)
|
||||
?: accounts.firstOrNull { it.activated }?.key
|
||||
val defaultAccount = accounts.firstOrNull { it.key.maybeEquals(defaultKey) }
|
||||
accountsAdapter!!.selectedAccount = defaultAccount
|
||||
accountsAdapter.selectedAccount = defaultAccount
|
||||
|
||||
if (accountActionProvider != null) {
|
||||
accountActionProvider!!.isExclusive = false
|
||||
accountActionProvider!!.accounts = accounts
|
||||
}
|
||||
updateAccountActions()
|
||||
val currentAccount = accountsAdapter!!.selectedAccount
|
||||
val currentAccount = accountsAdapter.selectedAccount
|
||||
if (currentAccount != null) {
|
||||
displayAccountBanner(currentAccount)
|
||||
displayCurrentAccount(null)
|
||||
|
@ -297,7 +298,7 @@ class AccountsDashboardFragment : BaseSupportFragment(), LoaderCallbacks<Account
|
|||
}
|
||||
|
||||
override fun fitSystemWindows(insets: Rect) {
|
||||
mSystemWindowsInsets.set(insets)
|
||||
systemWindowsInsets.set(insets)
|
||||
updateSystemWindowsInsets()
|
||||
}
|
||||
|
||||
|
@ -316,11 +317,10 @@ class AccountsDashboardFragment : BaseSupportFragment(), LoaderCallbacks<Account
|
|||
internal fun updateAccountActions() {
|
||||
val activity = activity as HomeActivity
|
||||
val tabs = activity.tabs
|
||||
val account = accountsAdapter!!.selectedAccount ?: return
|
||||
val account = accountsAdapter.selectedAccount ?: return
|
||||
var hasDmTab = false
|
||||
var hasInteractionsTab = false
|
||||
for (tab in tabs) {
|
||||
if (tab.type == null) continue
|
||||
when (tab.type) {
|
||||
CustomTabType.DIRECT_MESSAGES -> {
|
||||
if (!hasDmTab) {
|
||||
|
@ -338,17 +338,8 @@ class AccountsDashboardFragment : BaseSupportFragment(), LoaderCallbacks<Account
|
|||
menu.setItemAvailability(R.id.interactions, !hasInteractionsTab)
|
||||
menu.setItemAvailability(R.id.messages, !hasDmTab)
|
||||
|
||||
if (useStarsForLikes) {
|
||||
menu.setMenuItemTitle(R.id.favorites, R.string.favorites)
|
||||
val icon = ContextCompat.getDrawable(context, R.drawable.ic_action_star)
|
||||
DrawableCompat.setTintList(icon, navigationView.itemIconTintList)
|
||||
menu.setMenuItemIcon(R.id.favorites, icon)
|
||||
} else {
|
||||
menu.setMenuItemTitle(R.id.favorites, R.string.likes)
|
||||
val icon = ContextCompat.getDrawable(context, R.drawable.ic_action_heart)
|
||||
DrawableCompat.setTintList(icon, navigationView.itemIconTintList)
|
||||
menu.setMenuItemIcon(R.id.favorites, icon)
|
||||
}
|
||||
menu.setItemAvailability(R.id.favorites, useStarsForLikes)
|
||||
menu.setItemAvailability(R.id.likes, !useStarsForLikes)
|
||||
var hasLists = false
|
||||
var hasGroups = false
|
||||
var hasPublicTimeline = false
|
||||
|
@ -434,7 +425,7 @@ class AccountsDashboardFragment : BaseSupportFragment(), LoaderCallbacks<Account
|
|||
val profileDrawable = profileImageView.drawable
|
||||
clickedDrawable = clickedImageView.drawable
|
||||
clickedColors = clickedImageView.borderColors
|
||||
val oldSelectedAccount = accountsAdapter!!.selectedAccount ?: return
|
||||
val oldSelectedAccount = accountsAdapter.selectedAccount ?: return
|
||||
mediaLoader.displayDashboardProfileImage(clickedImageView,
|
||||
oldSelectedAccount, profileDrawable)
|
||||
clickedImageView.setBorderColors(*profileImageView.borderColors)
|
||||
|
@ -460,7 +451,7 @@ class AccountsDashboardFragment : BaseSupportFragment(), LoaderCallbacks<Account
|
|||
preferences.edit()
|
||||
.putString(KEY_DEFAULT_ACCOUNT_KEY, account.key.toString())
|
||||
.apply()
|
||||
accountsAdapter!!.selectedAccount = account
|
||||
accountsAdapter.selectedAccount = account
|
||||
updateAccountActions()
|
||||
displayCurrentAccount(clickedDrawable)
|
||||
snapshotView.visibility = View.INVISIBLE
|
||||
|
@ -493,7 +484,7 @@ class AccountsDashboardFragment : BaseSupportFragment(), LoaderCallbacks<Account
|
|||
}
|
||||
|
||||
private fun displayCurrentAccount(profileImageSnapshot: Drawable?) {
|
||||
val account = accountsAdapter!!.selectedAccount ?: return
|
||||
val account = accountsAdapter.selectedAccount ?: return
|
||||
accountProfileNameView.text = account.user.name
|
||||
accountProfileScreenNameView.text = String.format("@%s", account.user.screen_name)
|
||||
mediaLoader.displayDashboardProfileImage(accountProfileImageView, account,
|
||||
|
@ -506,7 +497,7 @@ class AccountsDashboardFragment : BaseSupportFragment(), LoaderCallbacks<Account
|
|||
}
|
||||
|
||||
override fun onNavigationItemSelected(item: MenuItem): Boolean {
|
||||
val account = accountsAdapter!!.selectedAccount ?: return false
|
||||
val account = accountsAdapter.selectedAccount ?: return false
|
||||
when (item.itemId) {
|
||||
R.id.search -> {
|
||||
val intent = Intent(activity, QuickSearchBarActivity::class.java)
|
||||
|
@ -520,9 +511,9 @@ class AccountsDashboardFragment : BaseSupportFragment(), LoaderCallbacks<Account
|
|||
composeIntent.putExtra(EXTRA_ACCOUNT_KEY, account.key)
|
||||
startActivity(composeIntent)
|
||||
}
|
||||
R.id.favorites -> {
|
||||
IntentUtils.openUserFavorites(activity, account.key,
|
||||
account.key, account.user.screen_name)
|
||||
R.id.likes, R.id.favorites -> {
|
||||
IntentUtils.openUserFavorites(activity, account.key, account.key,
|
||||
account.user.screen_name)
|
||||
}
|
||||
R.id.lists -> {
|
||||
IntentUtils.openUserLists(activity, account.key,
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.service
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.annotation.TargetApi
|
||||
import android.app.job.JobParameters
|
||||
import android.app.job.JobService
|
||||
import android.os.Build
|
||||
import org.mariotaku.abstask.library.AbstractTask
|
||||
import org.mariotaku.abstask.library.TaskStarter
|
||||
import org.mariotaku.kpreferences.KPreferences
|
||||
import org.mariotaku.twidere.constant.IntentConstants.*
|
||||
import org.mariotaku.twidere.constant.stopAutoRefreshWhenBatteryLowKey
|
||||
import org.mariotaku.twidere.model.AccountPreferences
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.*
|
||||
import org.mariotaku.twidere.task.GetActivitiesAboutMeTask
|
||||
import org.mariotaku.twidere.task.GetHomeTimelineTask
|
||||
import org.mariotaku.twidere.task.GetReceivedDirectMessagesTask
|
||||
import org.mariotaku.twidere.util.DataStoreUtils
|
||||
import org.mariotaku.twidere.util.Utils
|
||||
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 14/12/12.
|
||||
*/
|
||||
@SuppressLint("Registered")
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
class JobRefreshService : JobService() {
|
||||
|
||||
@Inject
|
||||
internal lateinit var preferences: KPreferences
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
GeneralComponentHelper.build(this).inject(this)
|
||||
}
|
||||
|
||||
override fun onStartJob(params: JobParameters): Boolean {
|
||||
if (!Utils.isBatteryOkay(this) && preferences[stopAutoRefreshWhenBatteryLowKey]) {
|
||||
// Low battery, don't refresh
|
||||
return false
|
||||
}
|
||||
|
||||
val task = createJobTask(params) ?: return false
|
||||
task.callback = {
|
||||
this.jobFinished(params, true)
|
||||
}
|
||||
TaskStarter.execute(task)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onStopJob(params: JobParameters): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
internal fun createJobTask(params: JobParameters): AbstractTask<*, *, () -> Unit>? {
|
||||
when (params.extras?.getString(EXTRA_ACTION)) {
|
||||
BROADCAST_REFRESH_HOME_TIMELINE -> {
|
||||
val task = GetHomeTimelineTask(this)
|
||||
task.params = RefreshService.AutoRefreshTaskParam(this, AccountPreferences::isAutoRefreshHomeTimelineEnabled) { accountKeys ->
|
||||
DataStoreUtils.getNewestStatusIds(this, Statuses.CONTENT_URI, accountKeys)
|
||||
}
|
||||
return task
|
||||
}
|
||||
BROADCAST_REFRESH_NOTIFICATIONS -> {
|
||||
val task = GetActivitiesAboutMeTask(this)
|
||||
task.params = RefreshService.AutoRefreshTaskParam(this, AccountPreferences::isAutoRefreshMentionsEnabled) { accountKeys ->
|
||||
DataStoreUtils.getNewestActivityMaxPositions(this, Activities.AboutMe.CONTENT_URI, accountKeys)
|
||||
}
|
||||
return task
|
||||
}
|
||||
BROADCAST_REFRESH_DIRECT_MESSAGES -> {
|
||||
val task = GetReceivedDirectMessagesTask(this)
|
||||
task.params = RefreshService.AutoRefreshTaskParam(this, AccountPreferences::isAutoRefreshDirectMessagesEnabled) { accountKeys ->
|
||||
DataStoreUtils.getNewestMessageIds(this, DirectMessages.Inbox.CONTENT_URI, accountKeys)
|
||||
}
|
||||
return task
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
|
@ -0,0 +1,304 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.service
|
||||
|
||||
import android.app.AlarmManager
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.IBinder
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import edu.tsinghua.hotmobi.model.BatteryRecord
|
||||
import edu.tsinghua.hotmobi.model.ScreenEvent
|
||||
import org.apache.commons.lang3.math.NumberUtils
|
||||
import org.mariotaku.twidere.BuildConfig
|
||||
import org.mariotaku.twidere.Constants
|
||||
import org.mariotaku.twidere.TwidereConstants.LOGTAG
|
||||
import org.mariotaku.twidere.constant.IntentConstants.*
|
||||
import org.mariotaku.twidere.constant.SharedPreferenceConstants.*
|
||||
import org.mariotaku.twidere.model.AccountPreferences
|
||||
import org.mariotaku.twidere.model.SimpleRefreshTaskParam
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.*
|
||||
import org.mariotaku.twidere.receiver.PowerStateReceiver
|
||||
import org.mariotaku.twidere.util.AsyncTwitterWrapper
|
||||
import org.mariotaku.twidere.util.DataStoreUtils
|
||||
import org.mariotaku.twidere.util.SharedPreferencesWrapper
|
||||
import org.mariotaku.twidere.util.Utils
|
||||
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper
|
||||
import javax.inject.Inject
|
||||
|
||||
class RefreshService : Service(), Constants {
|
||||
|
||||
@Inject
|
||||
internal lateinit var preferences: SharedPreferencesWrapper
|
||||
@Inject
|
||||
internal lateinit var twitterWrapper: AsyncTwitterWrapper
|
||||
|
||||
private lateinit var alarmManager: AlarmManager
|
||||
private var pendingRefreshHomeTimelineIntent: PendingIntent? = null
|
||||
private var pendingRefreshMentionsIntent: PendingIntent? = null
|
||||
private var pendingRefreshDirectMessagesIntent: PendingIntent? = null
|
||||
private var pendingRefreshTrendsIntent: PendingIntent? = null
|
||||
|
||||
private val mStateReceiver = object : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val action = intent.action
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(LOGTAG, String.format("Refresh service received action %s", action))
|
||||
}
|
||||
when (action) {
|
||||
BROADCAST_RESCHEDULE_HOME_TIMELINE_REFRESHING -> {
|
||||
rescheduleHomeTimelineRefreshing()
|
||||
}
|
||||
BROADCAST_RESCHEDULE_MENTIONS_REFRESHING -> {
|
||||
rescheduleMentionsRefreshing()
|
||||
}
|
||||
BROADCAST_RESCHEDULE_DIRECT_MESSAGES_REFRESHING -> {
|
||||
rescheduleDirectMessagesRefreshing()
|
||||
}
|
||||
BROADCAST_RESCHEDULE_TRENDS_REFRESHING -> {
|
||||
rescheduleTrendsRefreshing()
|
||||
}
|
||||
BROADCAST_REFRESH_HOME_TIMELINE -> {
|
||||
if (isAutoRefreshAllowed) {
|
||||
twitterWrapper.getHomeTimelineAsync(AutoRefreshTaskParam(context,
|
||||
AccountPreferences::isAutoRefreshHomeTimelineEnabled) { accountKeys ->
|
||||
DataStoreUtils.getNewestStatusIds(context, Statuses.CONTENT_URI, accountKeys)
|
||||
})
|
||||
}
|
||||
}
|
||||
BROADCAST_REFRESH_NOTIFICATIONS -> {
|
||||
if (isAutoRefreshAllowed) {
|
||||
twitterWrapper.getActivitiesAboutMeAsync(AutoRefreshTaskParam(context,
|
||||
AccountPreferences::isAutoRefreshMentionsEnabled) { accountKeys ->
|
||||
DataStoreUtils.getNewestActivityMaxPositions(context,
|
||||
Activities.AboutMe.CONTENT_URI, accountKeys)
|
||||
})
|
||||
}
|
||||
}
|
||||
BROADCAST_REFRESH_DIRECT_MESSAGES -> {
|
||||
if (isAutoRefreshAllowed) {
|
||||
twitterWrapper.getReceivedDirectMessagesAsync(AutoRefreshTaskParam(context,
|
||||
AccountPreferences::isAutoRefreshDirectMessagesEnabled) { accountKeys ->
|
||||
DataStoreUtils.getNewestMessageIds(context,
|
||||
DirectMessages.Inbox.CONTENT_URI, accountKeys)
|
||||
})
|
||||
}
|
||||
}
|
||||
BROADCAST_REFRESH_TRENDS -> {
|
||||
if (isAutoRefreshAllowed) {
|
||||
val prefs = AccountPreferences.getAccountPreferences(context,
|
||||
DataStoreUtils.getAccountKeys(context)).filter(AccountPreferences::isAutoRefreshEnabled)
|
||||
getLocalTrends(prefs.filter(AccountPreferences::isAutoRefreshTrendsEnabled)
|
||||
.map(AccountPreferences::getAccountKey).toTypedArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private val mPowerStateReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
when (intent.action) {
|
||||
Intent.ACTION_BATTERY_CHANGED -> {
|
||||
BatteryRecord.log(context, intent)
|
||||
}
|
||||
else -> {
|
||||
BatteryRecord.log(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val mScreenStateReceiver = object : BroadcastReceiver() {
|
||||
var mPresentTime: Long = -1
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
when (intent.action) {
|
||||
Intent.ACTION_SCREEN_ON -> {
|
||||
ScreenEvent.log(context, ScreenEvent.Action.ON, presentDuration)
|
||||
}
|
||||
Intent.ACTION_SCREEN_OFF -> {
|
||||
ScreenEvent.log(context, ScreenEvent.Action.OFF, presentDuration)
|
||||
mPresentTime = -1
|
||||
}
|
||||
Intent.ACTION_USER_PRESENT -> {
|
||||
mPresentTime = SystemClock.elapsedRealtime()
|
||||
ScreenEvent.log(context, ScreenEvent.Action.PRESENT, -1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val presentDuration: Long
|
||||
get() {
|
||||
if (mPresentTime < 0) return -1
|
||||
return SystemClock.elapsedRealtime() - mPresentTime
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
GeneralComponentHelper.build(this).inject(this)
|
||||
alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||
pendingRefreshHomeTimelineIntent = PendingIntent.getBroadcast(this, 0, Intent(
|
||||
BROADCAST_REFRESH_HOME_TIMELINE), 0)
|
||||
pendingRefreshMentionsIntent = PendingIntent.getBroadcast(this, 0, Intent(BROADCAST_REFRESH_NOTIFICATIONS), 0)
|
||||
pendingRefreshDirectMessagesIntent = PendingIntent.getBroadcast(this, 0, Intent(
|
||||
BROADCAST_REFRESH_DIRECT_MESSAGES), 0)
|
||||
pendingRefreshTrendsIntent = PendingIntent.getBroadcast(this, 0, Intent(BROADCAST_REFRESH_TRENDS), 0)
|
||||
val refreshFilter = IntentFilter(BROADCAST_NOTIFICATION_DELETED)
|
||||
refreshFilter.addAction(BROADCAST_REFRESH_HOME_TIMELINE)
|
||||
refreshFilter.addAction(BROADCAST_REFRESH_NOTIFICATIONS)
|
||||
refreshFilter.addAction(BROADCAST_REFRESH_DIRECT_MESSAGES)
|
||||
refreshFilter.addAction(BROADCAST_RESCHEDULE_HOME_TIMELINE_REFRESHING)
|
||||
refreshFilter.addAction(BROADCAST_RESCHEDULE_MENTIONS_REFRESHING)
|
||||
refreshFilter.addAction(BROADCAST_RESCHEDULE_DIRECT_MESSAGES_REFRESHING)
|
||||
registerReceiver(mStateReceiver, refreshFilter)
|
||||
val batteryFilter = IntentFilter()
|
||||
batteryFilter.addAction(Intent.ACTION_BATTERY_CHANGED)
|
||||
batteryFilter.addAction(Intent.ACTION_BATTERY_OKAY)
|
||||
batteryFilter.addAction(Intent.ACTION_BATTERY_LOW)
|
||||
batteryFilter.addAction(Intent.ACTION_POWER_CONNECTED)
|
||||
batteryFilter.addAction(Intent.ACTION_POWER_DISCONNECTED)
|
||||
val screenFilter = IntentFilter()
|
||||
screenFilter.addAction(Intent.ACTION_SCREEN_ON)
|
||||
screenFilter.addAction(Intent.ACTION_SCREEN_OFF)
|
||||
screenFilter.addAction(Intent.ACTION_USER_PRESENT)
|
||||
registerReceiver(mPowerStateReceiver, batteryFilter)
|
||||
registerReceiver(mScreenStateReceiver, screenFilter)
|
||||
PowerStateReceiver.serviceReceiverStarted = true
|
||||
if (Utils.hasAutoRefreshAccounts(this)) {
|
||||
startAutoRefresh()
|
||||
} else {
|
||||
stopSelf()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
PowerStateReceiver.serviceReceiverStarted = false
|
||||
unregisterReceiver(mScreenStateReceiver)
|
||||
unregisterReceiver(mPowerStateReceiver)
|
||||
unregisterReceiver(mStateReceiver)
|
||||
if (Utils.hasAutoRefreshAccounts(this)) {
|
||||
// Auto refresh enabled, so I will try to start service after it was
|
||||
// stopped.
|
||||
startService(Intent(this, javaClass))
|
||||
}
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
protected val isAutoRefreshAllowed: Boolean
|
||||
get() = Utils.isNetworkAvailable(this) && (Utils.isBatteryOkay(this) || !Utils.shouldStopAutoRefreshOnBatteryLow(this))
|
||||
|
||||
private fun getLocalTrends(accountIds: Array<UserKey>) {
|
||||
val account_id = Utils.getDefaultAccountKey(this)
|
||||
val woeid = preferences.getInt(KEY_LOCAL_TRENDS_WOEID, 1)
|
||||
twitterWrapper.getLocalTrendsAsync(account_id, woeid)
|
||||
}
|
||||
|
||||
private val refreshInterval: Long
|
||||
get() {
|
||||
val prefValue = NumberUtils.toInt(preferences.getString(KEY_REFRESH_INTERVAL, DEFAULT_REFRESH_INTERVAL), -1)
|
||||
return (Math.max(prefValue, 3) * 60 * 1000).toLong()
|
||||
}
|
||||
|
||||
private fun rescheduleDirectMessagesRefreshing() {
|
||||
alarmManager.cancel(pendingRefreshDirectMessagesIntent)
|
||||
val refreshInterval = refreshInterval
|
||||
if (refreshInterval > 0) {
|
||||
alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + refreshInterval,
|
||||
refreshInterval, pendingRefreshDirectMessagesIntent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun rescheduleHomeTimelineRefreshing() {
|
||||
alarmManager.cancel(pendingRefreshHomeTimelineIntent)
|
||||
val refreshInterval = refreshInterval
|
||||
if (refreshInterval > 0) {
|
||||
alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + refreshInterval,
|
||||
refreshInterval, pendingRefreshHomeTimelineIntent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun rescheduleMentionsRefreshing() {
|
||||
alarmManager.cancel(pendingRefreshMentionsIntent)
|
||||
val refreshInterval = refreshInterval
|
||||
if (refreshInterval > 0) {
|
||||
alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + refreshInterval,
|
||||
refreshInterval, pendingRefreshMentionsIntent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun rescheduleTrendsRefreshing() {
|
||||
alarmManager.cancel(pendingRefreshTrendsIntent)
|
||||
val refreshInterval = refreshInterval
|
||||
if (refreshInterval > 0) {
|
||||
alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + refreshInterval,
|
||||
refreshInterval, pendingRefreshTrendsIntent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startAutoRefresh(): Boolean {
|
||||
stopAutoRefresh()
|
||||
val refreshInterval = refreshInterval
|
||||
if (refreshInterval <= 0) return false
|
||||
rescheduleHomeTimelineRefreshing()
|
||||
rescheduleMentionsRefreshing()
|
||||
rescheduleDirectMessagesRefreshing()
|
||||
rescheduleTrendsRefreshing()
|
||||
return true
|
||||
}
|
||||
|
||||
private fun stopAutoRefresh() {
|
||||
alarmManager.cancel(pendingRefreshHomeTimelineIntent)
|
||||
alarmManager.cancel(pendingRefreshMentionsIntent)
|
||||
alarmManager.cancel(pendingRefreshDirectMessagesIntent)
|
||||
alarmManager.cancel(pendingRefreshTrendsIntent)
|
||||
}
|
||||
|
||||
class AutoRefreshTaskParam(
|
||||
val context: Context,
|
||||
val refreshable: (AccountPreferences) -> Boolean,
|
||||
val getSinceIds: (Array<UserKey>) -> Array<String?>?
|
||||
) : SimpleRefreshTaskParam() {
|
||||
|
||||
override fun getAccountKeysWorker(): Array<UserKey> {
|
||||
val prefs = AccountPreferences.getAccountPreferences(context,
|
||||
DataStoreUtils.getAccountKeys(context)).filter(AccountPreferences::isAutoRefreshEnabled)
|
||||
return prefs.filter(refreshable)
|
||||
.map(AccountPreferences::getAccountKey).toTypedArray()
|
||||
}
|
||||
|
||||
override val sinceIds: Array<String?>?
|
||||
get() = getSinceIds(accountKeys)
|
||||
}
|
||||
|
||||
}
|
|
@ -40,9 +40,7 @@ class GetActivitiesAboutMeTask(context: Context) : GetActivitiesTask(context) {
|
|||
}
|
||||
|
||||
@Throws(MicroBlogException::class)
|
||||
override fun getActivities(twitter: MicroBlog,
|
||||
details: AccountDetails,
|
||||
paging: Paging): ResponseList<Activity> {
|
||||
override fun getActivities(twitter: MicroBlog, details: AccountDetails, paging: Paging): ResponseList<Activity> {
|
||||
if (details.isOfficial(context)) {
|
||||
return twitter.getActivitiesAboutMe(paging)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
package org.mariotaku.twidere.task
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import com.squareup.otto.Bus
|
||||
import org.apache.commons.lang3.math.NumberUtils
|
||||
import org.mariotaku.abstask.library.AbstractTask
|
||||
import org.mariotaku.kpreferences.get
|
||||
import org.mariotaku.microblog.library.MicroBlog
|
||||
import org.mariotaku.microblog.library.MicroBlogException
|
||||
import org.mariotaku.microblog.library.twitter.model.DirectMessage
|
||||
import org.mariotaku.microblog.library.twitter.model.ErrorInfo
|
||||
import org.mariotaku.microblog.library.twitter.model.Paging
|
||||
import org.mariotaku.microblog.library.twitter.model.ResponseList
|
||||
import org.mariotaku.twidere.BuildConfig
|
||||
import org.mariotaku.twidere.Constants
|
||||
import org.mariotaku.twidere.TwidereConstants
|
||||
import org.mariotaku.twidere.constant.loadItemLimitKey
|
||||
import org.mariotaku.twidere.model.RefreshTaskParam
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.model.message.GetMessagesTaskEvent
|
||||
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/2/14.
|
||||
*/
|
||||
abstract class GetDirectMessagesTask(protected val context: Context) : AbstractTask<RefreshTaskParam,
|
||||
List<TwitterWrapper.MessageListResponse>, () -> Unit>(), Constants {
|
||||
@Inject
|
||||
protected lateinit var errorInfoStore: ErrorInfoStore
|
||||
@Inject
|
||||
protected lateinit var preferences: SharedPreferencesWrapper
|
||||
@Inject
|
||||
protected lateinit var bus: Bus
|
||||
|
||||
init {
|
||||
GeneralComponentHelper.build(context).inject(this)
|
||||
}
|
||||
|
||||
@Throws(MicroBlogException::class)
|
||||
abstract fun getDirectMessages(twitter: MicroBlog, paging: Paging): ResponseList<DirectMessage>
|
||||
|
||||
protected abstract val databaseUri: Uri
|
||||
|
||||
protected abstract val isOutgoing: Boolean
|
||||
|
||||
public override fun doLongOperation(param: RefreshTaskParam): List<TwitterWrapper.MessageListResponse> {
|
||||
val accountKeys = param.accountKeys
|
||||
val sinceIds = param.sinceIds
|
||||
val maxIds = param.maxIds
|
||||
val result = ArrayList<TwitterWrapper.MessageListResponse>()
|
||||
var idx = 0
|
||||
val loadItemLimit = preferences[loadItemLimitKey]
|
||||
for (accountKey in accountKeys) {
|
||||
val twitter = MicroBlogAPIFactory.getInstance(context, accountKey) ?: continue
|
||||
try {
|
||||
val paging = Paging()
|
||||
paging.setCount(loadItemLimit)
|
||||
var maxId: String? = null
|
||||
var sinceId: String? = null
|
||||
if (maxIds != null && maxIds[idx] != null) {
|
||||
maxId = maxIds[idx]
|
||||
paging.setMaxId(maxId)
|
||||
}
|
||||
if (sinceIds != null && sinceIds[idx] != null) {
|
||||
sinceId = sinceIds[idx]
|
||||
val sinceIdLong = NumberUtils.toLong(sinceId, -1)
|
||||
//TODO handle non-twitter case
|
||||
if (sinceIdLong != -1L) {
|
||||
paging.sinceId((sinceIdLong - 1).toString())
|
||||
} else {
|
||||
paging.sinceId(sinceId)
|
||||
}
|
||||
if (maxIds == null || sinceIds[idx] == null) {
|
||||
paging.setLatestResults(true)
|
||||
}
|
||||
}
|
||||
val messages = getDirectMessages(twitter, paging)
|
||||
result.add(TwitterWrapper.MessageListResponse(accountKey, maxId, sinceId, messages))
|
||||
storeMessages(accountKey, messages, isOutgoing, true)
|
||||
errorInfoStore.remove(ErrorInfoStore.KEY_DIRECT_MESSAGES, accountKey)
|
||||
} catch (e: MicroBlogException) {
|
||||
if (e.errorCode == ErrorInfo.NO_DIRECT_MESSAGE_PERMISSION) {
|
||||
errorInfoStore.put(ErrorInfoStore.KEY_DIRECT_MESSAGES, accountKey,
|
||||
ErrorInfoStore.CODE_NO_DM_PERMISSION)
|
||||
} else if (e.isCausedByNetworkIssue) {
|
||||
errorInfoStore.put(ErrorInfoStore.KEY_DIRECT_MESSAGES, accountKey,
|
||||
ErrorInfoStore.CODE_NETWORK_ERROR)
|
||||
}
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.w(TwidereConstants.LOGTAG, e)
|
||||
}
|
||||
result.add(TwitterWrapper.MessageListResponse(accountKey, e))
|
||||
}
|
||||
|
||||
idx++
|
||||
}
|
||||
return result
|
||||
|
||||
}
|
||||
|
||||
private fun storeMessages(accountKey: UserKey, messages: List<DirectMessage>?, isOutgoing: Boolean, notify: Boolean): Boolean {
|
||||
if (messages == null) return true
|
||||
val uri = databaseUri
|
||||
val valuesArray = messages.map { ContentValuesCreator.createDirectMessage(it, accountKey, isOutgoing) }
|
||||
|
||||
// Delete all rows conflicting before new data inserted.
|
||||
// final Expression deleteWhere = Expression.and(Expression.equals(DirectMessages.ACCOUNT_ID, accountKey),
|
||||
// Expression.in(new Column(DirectMessages.MESSAGE_ID), new RawItemArray(messageIds)));
|
||||
// final Uri deleteUri = UriUtils.appendQueryParameters(uri, QUERY_PARAM_NOTIFY, false);
|
||||
// mResolver.delete(deleteUri, deleteWhere.getSQL(), null);
|
||||
|
||||
|
||||
// Insert previously fetched items.
|
||||
val insertUri = UriUtils.appendQueryParameters(uri, TwidereConstants.QUERY_PARAM_NOTIFY, notify)
|
||||
ContentResolverUtils.bulkInsert(context.contentResolver, insertUri, valuesArray)
|
||||
return false
|
||||
}
|
||||
|
||||
override fun beforeExecute() {
|
||||
bus.post(GetMessagesTaskEvent(databaseUri, true, null))
|
||||
}
|
||||
|
||||
override fun afterExecute(handler: (() -> Unit)?, result: List<TwitterWrapper.MessageListResponse>?) {
|
||||
bus.post(GetMessagesTaskEvent(databaseUri, false, AsyncTwitterWrapper.getException(result)))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package org.mariotaku.twidere.task
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
|
||||
import org.mariotaku.microblog.library.MicroBlog
|
||||
import org.mariotaku.microblog.library.MicroBlogException
|
||||
import org.mariotaku.microblog.library.twitter.model.Trends
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.CachedTrends
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/2/24.
|
||||
*/
|
||||
class GetLocalTrendsTask(context: Context, accountKey: UserKey, private val woeid: Int) : GetTrendsTask(context, accountKey) {
|
||||
|
||||
@Throws(MicroBlogException::class)
|
||||
override fun getTrends(twitter: MicroBlog): List<Trends> {
|
||||
return twitter.getLocationTrends(woeid)
|
||||
}
|
||||
|
||||
override val contentUri: Uri = CachedTrends.Local.CONTENT_URI
|
||||
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package org.mariotaku.twidere.task
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import org.mariotaku.microblog.library.MicroBlog
|
||||
import org.mariotaku.microblog.library.MicroBlogException
|
||||
import org.mariotaku.microblog.library.twitter.model.DirectMessage
|
||||
import org.mariotaku.microblog.library.twitter.model.Paging
|
||||
import org.mariotaku.microblog.library.twitter.model.ResponseList
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2016/12/14.
|
||||
*/
|
||||
class GetReceivedDirectMessagesTask(context: Context) : GetDirectMessagesTask(context) {
|
||||
|
||||
@Throws(MicroBlogException::class)
|
||||
override fun getDirectMessages(twitter: MicroBlog, paging: Paging): ResponseList<DirectMessage> {
|
||||
return twitter.getDirectMessages(paging)
|
||||
}
|
||||
|
||||
override val isOutgoing: Boolean = false
|
||||
|
||||
|
||||
override val databaseUri: Uri = DirectMessages.Inbox.CONTENT_URI
|
||||
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package org.mariotaku.twidere.task
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import org.mariotaku.abstask.library.AbstractTask
|
||||
import org.mariotaku.microblog.library.MicroBlogException
|
||||
import org.mariotaku.sqliteqb.library.Expression
|
||||
import org.mariotaku.twidere.BuildConfig
|
||||
import org.mariotaku.twidere.TwidereConstants.LOGTAG
|
||||
import org.mariotaku.twidere.model.SingleResponse
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.SavedSearches
|
||||
import org.mariotaku.twidere.util.ContentValuesCreator
|
||||
import org.mariotaku.twidere.util.MicroBlogAPIFactory
|
||||
import org.mariotaku.twidere.util.content.ContentResolverUtils
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/2/13.
|
||||
*/
|
||||
class GetSavedSearchesTask(private val context: Context) : AbstractTask<Array<UserKey>, SingleResponse<Any>, Any>() {
|
||||
|
||||
public override fun doLongOperation(params: Array<UserKey>): SingleResponse<Any> {
|
||||
val cr = context.contentResolver
|
||||
for (accountKey in params) {
|
||||
val twitter = MicroBlogAPIFactory.getInstance(context, accountKey) ?: continue
|
||||
try {
|
||||
val searches = twitter.savedSearches
|
||||
val values = ContentValuesCreator.createSavedSearches(searches,
|
||||
accountKey)
|
||||
val where = Expression.equalsArgs(SavedSearches.ACCOUNT_KEY)
|
||||
val whereArgs = arrayOf(accountKey.toString())
|
||||
cr.delete(SavedSearches.CONTENT_URI, where.sql, whereArgs)
|
||||
ContentResolverUtils.bulkInsert(cr, SavedSearches.CONTENT_URI, values)
|
||||
} catch (e: MicroBlogException) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.w(LOGTAG, e)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return SingleResponse.getInstance()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package org.mariotaku.twidere.task
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import org.mariotaku.microblog.library.MicroBlog
|
||||
import org.mariotaku.microblog.library.MicroBlogException
|
||||
import org.mariotaku.microblog.library.twitter.model.DirectMessage
|
||||
import org.mariotaku.microblog.library.twitter.model.Paging
|
||||
import org.mariotaku.microblog.library.twitter.model.ResponseList
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2016/12/14.
|
||||
*/
|
||||
class GetSentDirectMessagesTask(context: Context) : GetDirectMessagesTask(context) {
|
||||
|
||||
@Throws(MicroBlogException::class)
|
||||
override fun getDirectMessages(twitter: MicroBlog, paging: Paging): ResponseList<DirectMessage> {
|
||||
return twitter.getSentDirectMessages(paging)
|
||||
}
|
||||
|
||||
override val isOutgoing: Boolean = true
|
||||
|
||||
override val databaseUri: Uri = DirectMessages.Outbox.CONTENT_URI
|
||||
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
package org.mariotaku.twidere.task
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
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.Trends
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.model.message.TrendsRefreshedEvent
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore
|
||||
import org.mariotaku.twidere.util.ContentValuesCreator
|
||||
import org.mariotaku.twidere.util.MicroBlogAPIFactory
|
||||
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/2/24.
|
||||
*/
|
||||
abstract class GetTrendsTask(private val context: Context, private val accountId: UserKey) : AbstractTask<Any, Any, Any>() {
|
||||
|
||||
@Inject
|
||||
lateinit var bus: Bus
|
||||
|
||||
init {
|
||||
GeneralComponentHelper.build(context).inject(this)
|
||||
}
|
||||
|
||||
@Throws(MicroBlogException::class)
|
||||
abstract fun getTrends(twitter: MicroBlog): List<Trends>
|
||||
|
||||
public override fun doLongOperation(param: Any): Any? {
|
||||
val twitter = MicroBlogAPIFactory.getInstance(context, accountId) ?: return null
|
||||
try {
|
||||
val trends = getTrends(twitter)
|
||||
storeTrends(context.contentResolver, contentUri, trends)
|
||||
return null
|
||||
} catch (e: MicroBlogException) {
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun afterExecute(handler: Any?, result: Any?) {
|
||||
bus.post(TrendsRefreshedEvent())
|
||||
}
|
||||
|
||||
protected abstract val contentUri: Uri
|
||||
|
||||
private fun storeTrends(cr: ContentResolver, uri: Uri, trendsList: List<Trends>) {
|
||||
val hashtags = ArrayList<String>()
|
||||
val hashtagValues = ArrayList<ContentValues>()
|
||||
if (trendsList.isNotEmpty()) {
|
||||
val valuesArray = ContentValuesCreator.createTrends(trendsList)
|
||||
for (values in valuesArray) {
|
||||
val hashtag = values.getAsString(TwidereDataStore.CachedTrends.NAME).replaceFirst("#".toRegex(), "")
|
||||
if (hashtags.contains(hashtag)) {
|
||||
continue
|
||||
}
|
||||
hashtags.add(hashtag)
|
||||
val hashtagValue = ContentValues()
|
||||
hashtagValue.put(TwidereDataStore.CachedHashtags.NAME, hashtag)
|
||||
hashtagValues.add(hashtagValue)
|
||||
}
|
||||
cr.delete(uri, null, null)
|
||||
ContentResolverUtils.bulkInsert(cr, uri, valuesArray)
|
||||
ContentResolverUtils.bulkDelete(cr, TwidereDataStore.CachedHashtags.CONTENT_URI, TwidereDataStore.CachedHashtags.NAME, hashtags, null)
|
||||
ContentResolverUtils.bulkInsert(cr, TwidereDataStore.CachedHashtags.CONTENT_URI,
|
||||
hashtagValues.toTypedArray())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* 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.task
|
||||
|
||||
import android.app.Activity
|
||||
import android.media.MediaScannerConnection
|
||||
import android.net.Uri
|
||||
import android.os.Environment
|
||||
import android.widget.Toast
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.provider.CacheProvider
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 15/12/28.
|
||||
*/
|
||||
class SaveMediaToGalleryTask(activity: Activity, source: Uri, destination: File, type: String) : ProgressSaveFileTask(activity, source, destination, CacheProvider.CacheFileTypeCallback(activity, type)) {
|
||||
|
||||
override fun onFileSaved(savedFile: File, mimeType: String?) {
|
||||
val context = context ?: return
|
||||
MediaScannerConnection.scanFile(context, arrayOf(savedFile.path),
|
||||
arrayOf(mimeType), null)
|
||||
Toast.makeText(context, R.string.saved_to_gallery, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onFileSaveFailed() {
|
||||
val context = context ?: return
|
||||
Toast.makeText(context, R.string.error_occurred, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun create(activity: Activity, source: Uri,
|
||||
@CacheProvider.Type type: String): SaveFileTask {
|
||||
val pubDir: File
|
||||
when (type) {
|
||||
CacheProvider.Type.VIDEO -> {
|
||||
pubDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES)
|
||||
}
|
||||
CacheProvider.Type.IMAGE -> {
|
||||
pubDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
|
||||
}
|
||||
else -> {
|
||||
pubDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
||||
}
|
||||
}
|
||||
val saveDir = File(pubDir, "Twidere")
|
||||
return SaveMediaToGalleryTask(activity, source, saveDir, type)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -9,6 +9,7 @@ import android.support.annotation.UiThread
|
|||
import android.util.Log
|
||||
import com.squareup.otto.Bus
|
||||
import org.mariotaku.abstask.library.AbstractTask
|
||||
import org.mariotaku.kpreferences.KPreferences
|
||||
import org.mariotaku.microblog.library.MicroBlog
|
||||
import org.mariotaku.microblog.library.MicroBlogException
|
||||
import org.mariotaku.microblog.library.twitter.model.Activity
|
||||
|
@ -19,7 +20,7 @@ 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.constant.loadItemLimitKey
|
||||
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
|
||||
import org.mariotaku.twidere.model.AccountDetails
|
||||
import org.mariotaku.twidere.model.RefreshTaskParam
|
||||
|
@ -37,9 +38,9 @@ import javax.inject.Inject
|
|||
/**
|
||||
* Created by mariotaku on 16/1/4.
|
||||
*/
|
||||
abstract class GetActivitiesTask(protected val context: Context) : AbstractTask<RefreshTaskParam, Any, Any>(), Constants {
|
||||
abstract class GetActivitiesTask(protected val context: Context) : AbstractTask<RefreshTaskParam, Any, () -> Unit>(), Constants {
|
||||
@Inject
|
||||
lateinit var preferences: SharedPreferencesWrapper
|
||||
lateinit var preferences: KPreferences
|
||||
@Inject
|
||||
lateinit var bus: Bus
|
||||
@Inject
|
||||
|
@ -60,12 +61,11 @@ abstract class GetActivitiesTask(protected val context: Context) : AbstractTask<
|
|||
val maxSortIds = param.maxSortIds
|
||||
val sinceIds = param.sinceIds
|
||||
val cr = context.contentResolver
|
||||
val loadItemLimit = preferences.getInt(KEY_LOAD_ITEM_LIMIT)
|
||||
val loadItemLimit = preferences[loadItemLimitKey]
|
||||
var saveReadPosition = false
|
||||
for (i in accountIds.indices) {
|
||||
val accountKey = accountIds[i]
|
||||
val noItemsBefore = DataStoreUtils.getActivitiesCount(context, contentUri,
|
||||
accountKey) <= 0
|
||||
val noItemsBefore = DataStoreUtils.getActivitiesCount(context, contentUri, accountKey) <= 0
|
||||
val credentials = AccountUtils.getAccountDetails(AccountManager.get(context), accountKey) ?: continue
|
||||
val microBlog = credentials.newMicroBlogInstance(context = context, cls = MicroBlog::class.java)
|
||||
val paging = Paging()
|
||||
|
@ -123,7 +123,7 @@ abstract class GetActivitiesTask(protected val context: Context) : AbstractTask<
|
|||
private fun storeActivities(cr: ContentResolver, loadItemLimit: Int, details: AccountDetails,
|
||||
noItemsBefore: Boolean, activities: ResponseList<Activity>,
|
||||
sinceId: String?, maxId: String?, notify: Boolean) {
|
||||
val deleteBound = LongArray(2, { return@LongArray -1 })
|
||||
val deleteBound = LongArray(2) { -1 }
|
||||
val valuesList = ArrayList<ContentValues>()
|
||||
var minIdx = -1
|
||||
var minPositionKey: Long = -1
|
||||
|
@ -190,15 +190,12 @@ abstract class GetActivitiesTask(protected val context: Context) : AbstractTask<
|
|||
}
|
||||
}
|
||||
|
||||
protected abstract fun saveReadPosition(accountKey: UserKey,
|
||||
details: AccountDetails, twitter: MicroBlog)
|
||||
protected abstract fun saveReadPosition(accountKey: UserKey, details: AccountDetails, twitter: MicroBlog)
|
||||
|
||||
@Throws(MicroBlogException::class)
|
||||
protected abstract fun getActivities(twitter: MicroBlog,
|
||||
details: AccountDetails,
|
||||
paging: Paging): ResponseList<Activity>
|
||||
protected abstract fun getActivities(twitter: MicroBlog, details: AccountDetails, paging: Paging): ResponseList<Activity>
|
||||
|
||||
public override fun afterExecute(handler: Any?, result: Any?) {
|
||||
public override fun afterExecute(handler: (() -> Unit)?, result: Any?) {
|
||||
context.contentResolver.notifyChange(contentUri, null)
|
||||
bus.post(GetActivitiesTaskEvent(contentUri, false, null))
|
||||
}
|
||||
|
|
|
@ -45,7 +45,8 @@ 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 {
|
||||
abstract class GetStatusesTask(protected val context: Context) :
|
||||
AbstractTask<RefreshTaskParam, List<TwitterWrapper.StatusListResponse>, () -> Unit>(), Constants {
|
||||
@Inject
|
||||
lateinit var preferences: KPreferences
|
||||
@Inject
|
||||
|
@ -68,9 +69,10 @@ abstract class GetStatusesTask(protected val context: Context) : AbstractTask<Re
|
|||
|
||||
protected abstract val timelineType: String
|
||||
|
||||
public override fun afterExecute(handler: Any?, result: List<TwitterWrapper.StatusListResponse>?) {
|
||||
public override fun afterExecute(handler: (() -> Unit)?, result: List<TwitterWrapper.StatusListResponse>?) {
|
||||
context.contentResolver.notifyChange(contentUri, null)
|
||||
bus.post(GetStatusesTaskEvent(contentUri, false, AsyncTwitterWrapper.getException(result)))
|
||||
handler?.invoke()
|
||||
}
|
||||
|
||||
override fun beforeExecute() {
|
||||
|
|
|
@ -36,6 +36,7 @@ import org.mariotaku.twidere.preference.KeyboardShortcutPreference
|
|||
import org.mariotaku.twidere.provider.CacheProvider
|
||||
import org.mariotaku.twidere.provider.TwidereDataProvider
|
||||
import org.mariotaku.twidere.service.BackgroundOperationService
|
||||
import org.mariotaku.twidere.service.JobRefreshService
|
||||
import org.mariotaku.twidere.service.RefreshService
|
||||
import org.mariotaku.twidere.task.*
|
||||
import org.mariotaku.twidere.task.twitter.GetActivitiesTask
|
||||
|
@ -110,6 +111,8 @@ interface GeneralComponent {
|
|||
|
||||
fun inject(task: GetStatusesTask)
|
||||
|
||||
fun inject(service: JobRefreshService)
|
||||
|
||||
fun inject(task: GetActivitiesTask)
|
||||
|
||||
fun inject(task: GetDirectMessagesTask)
|
||||
|
|
|
@ -17,6 +17,12 @@
|
|||
android:title="@string/direct_messages"/>
|
||||
<item
|
||||
android:id="@id/favorites"
|
||||
android:enabled="false"
|
||||
android:icon="@drawable/ic_action_star"
|
||||
android:title="@string/favorites"
|
||||
android:visible="false"/>
|
||||
<item
|
||||
android:id="@id/likes"
|
||||
android:icon="@drawable/ic_action_heart"
|
||||
android:title="@string/likes"/>
|
||||
<item
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<bool name="use_job_refresh_service">true</bool>
|
||||
<bool name="use_legacy_refresh_service">false</bool>
|
||||
</resources>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<bool name="is_debug">false</bool>
|
||||
<bool name="use_job_refresh_service">false</bool>
|
||||
<bool name="use_legacy_refresh_service">true</bool>
|
||||
</resources>
|
|
@ -6,6 +6,5 @@
|
|||
<string name="default_profile_image_style">round</string>
|
||||
<integer name="default_tab_columns">1</integer>
|
||||
<integer name="min_database_item_limit">50</integer>
|
||||
<bool name="is_debug">false</bool>
|
||||
|
||||
</resources>
|
|
@ -57,6 +57,7 @@
|
|||
<item name="delete_from_list" type="id"/>
|
||||
<item name="statuses" type="id"/>
|
||||
<item name="favorites" type="id"/>
|
||||
<item name="likes" type="id"/>
|
||||
<item name="lists" type="id"/>
|
||||
<item name="groups" type="id"/>
|
||||
<item name="public_timeline" type="id"/>
|
||||
|
|
Loading…
Reference in New Issue