mirror of
https://github.com/TwidereProject/Twidere-Android
synced 2025-02-01 09:16:47 +01:00
implementing user stream
This commit is contained in:
parent
0ad6692efa
commit
112e8fdb7d
@ -58,6 +58,9 @@ public abstract class FanfouUserStreamCallback implements RawCallback<MicroBlogE
|
||||
final CRLFLineReader reader = new CRLFLineReader(new InputStreamReader(response.getBody().stream(), "UTF-8"));
|
||||
try {
|
||||
for (String line; (line = reader.readLine()) != null && !disconnected; ) {
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
break;
|
||||
}
|
||||
if (!connected) {
|
||||
onConnected();
|
||||
connected = true;
|
||||
|
@ -21,7 +21,6 @@ package org.mariotaku.microblog.library.twitter.callback;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.bluelinelabs.logansquare.LoganSquare;
|
||||
|
||||
@ -66,6 +65,9 @@ public abstract class UserStreamCallback implements RawCallback<MicroBlogExcepti
|
||||
final CRLFLineReader reader = new CRLFLineReader(new InputStreamReader(response.getBody().stream(), "UTF-8"));
|
||||
try {
|
||||
for (String line; (line = reader.readLine()) != null && !disconnected; ) {
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
break;
|
||||
}
|
||||
if (!connected) {
|
||||
onConnected();
|
||||
connected = true;
|
||||
|
@ -13,6 +13,5 @@ class UtilsTest {
|
||||
@Test
|
||||
fun testHasAutoRefreshAccounts() {
|
||||
val context = InstrumentationRegistry.getTargetContext()
|
||||
Utils.hasAutoRefreshAccounts(context)
|
||||
}
|
||||
}
|
@ -30,8 +30,8 @@ import com.squareup.leakcanary.RefWatcher;
|
||||
|
||||
import org.mariotaku.twidere.BuildConfig;
|
||||
import org.mariotaku.twidere.util.net.NoIntercept;
|
||||
import org.mariotaku.twidere.util.stetho.AccountsDumper;
|
||||
import org.mariotaku.twidere.util.stetho.UserStreamDumper;
|
||||
import org.mariotaku.twidere.util.stetho.AccountsDumperPlugin;
|
||||
import org.mariotaku.twidere.util.stetho.UserStreamDumperPlugin;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@ -69,8 +69,8 @@ public class DebugModeUtils {
|
||||
@Override
|
||||
public Iterable<DumperPlugin> get() {
|
||||
return new Stetho.DefaultDumperPluginsBuilder(application)
|
||||
.provide(new AccountsDumper(application))
|
||||
.provide(new UserStreamDumper(application))
|
||||
.provide(new AccountsDumperPlugin(application))
|
||||
.provide(new UserStreamDumperPlugin(application))
|
||||
.finish();
|
||||
}
|
||||
})
|
||||
|
@ -65,7 +65,7 @@ import javax.crypto.spec.SecretKeySpec
|
||||
* Created by mariotaku on 2017/3/6.
|
||||
*/
|
||||
|
||||
class AccountsDumper(val context: Context) : DumperPlugin {
|
||||
class AccountsDumperPlugin(val context: Context) : DumperPlugin {
|
||||
|
||||
override fun getName() = "accounts"
|
||||
|
@ -48,7 +48,7 @@ import org.mariotaku.twidere.util.streaming.TwitterTimelineStreamCallback
|
||||
/**
|
||||
* Created by mariotaku on 2017/3/9.
|
||||
*/
|
||||
class UserStreamDumper(val context: Context) : DumperPlugin {
|
||||
class UserStreamDumperPlugin(val context: Context) : DumperPlugin {
|
||||
|
||||
private val syntax = "$name <account_key> [-ti]"
|
||||
|
@ -98,7 +98,7 @@ public class SessionEvent extends BaseEvent implements Parcelable {
|
||||
|
||||
public void dumpPreferences(Context context) {
|
||||
final HashMap<String, String> preferences = new HashMap<>();
|
||||
for (AccountPreferences pref : AccountPreferences.getAccountPreferences(context, DataStoreUtils.INSTANCE.getAccountKeys(context))) {
|
||||
for (AccountPreferences pref : AccountPreferences.Companion.getAccountPreferences(context, DataStoreUtils.INSTANCE.getAccountKeys(context))) {
|
||||
final UserKey accountKey = pref.getAccountKey();
|
||||
preferences.put("notification_" + accountKey + "_home", String.valueOf(pref.isHomeTimelineNotificationEnabled()));
|
||||
preferences.put("notification_" + accountKey + "_interactions", String.valueOf(pref.isInteractionsNotificationEnabled()));
|
||||
|
@ -1,192 +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.model;
|
||||
|
||||
import android.accounts.AccountManager;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.mariotaku.twidere.Constants;
|
||||
import org.mariotaku.twidere.R;
|
||||
import org.mariotaku.twidere.model.util.AccountUtils;
|
||||
|
||||
public class AccountPreferences implements Constants {
|
||||
|
||||
private final Context mContext;
|
||||
private final UserKey mAccountKey;
|
||||
private final SharedPreferences mPreferences;
|
||||
|
||||
public AccountPreferences(final Context context, final UserKey accountKey) {
|
||||
mContext = context;
|
||||
mAccountKey = accountKey;
|
||||
final String name = ACCOUNT_PREFERENCES_NAME_PREFIX + accountKey;
|
||||
mPreferences = context.getSharedPreferences(name, Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
public UserKey getAccountKey() {
|
||||
return mAccountKey;
|
||||
}
|
||||
|
||||
public int getDefaultNotificationLightColor() {
|
||||
final AccountDetails a = AccountUtils.getAccountDetails(AccountManager.get(mContext), mAccountKey, true);
|
||||
if (a != null) {
|
||||
return a.color;
|
||||
} else {
|
||||
return ContextCompat.getColor(mContext, R.color.branding_color);
|
||||
}
|
||||
}
|
||||
|
||||
public int getDirectMessagesNotificationType() {
|
||||
return mPreferences.getInt(KEY_NOTIFICATION_TYPE_DIRECT_MESSAGES, DEFAULT_NOTIFICATION_TYPE_DIRECT_MESSAGES);
|
||||
}
|
||||
|
||||
public int getHomeTimelineNotificationType() {
|
||||
return mPreferences.getInt(KEY_NOTIFICATION_TYPE_HOME, DEFAULT_NOTIFICATION_TYPE_HOME);
|
||||
}
|
||||
|
||||
public int getMentionsNotificationType() {
|
||||
return mPreferences.getInt(KEY_NOTIFICATION_TYPE_MENTIONS, DEFAULT_NOTIFICATION_TYPE_MENTIONS);
|
||||
}
|
||||
|
||||
public int getNotificationLightColor() {
|
||||
return mPreferences.getInt(KEY_NOTIFICATION_LIGHT_COLOR, getDefaultNotificationLightColor());
|
||||
}
|
||||
|
||||
public Uri getNotificationRingtone() {
|
||||
final String ringtone = mPreferences.getString(KEY_NOTIFICATION_RINGTONE, null);
|
||||
if (TextUtils.isEmpty(ringtone)) {
|
||||
return RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
|
||||
} else {
|
||||
return Uri.parse(ringtone);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAutoRefreshDirectMessagesEnabled() {
|
||||
return mPreferences.getBoolean(KEY_AUTO_REFRESH_DIRECT_MESSAGES, DEFAULT_AUTO_REFRESH_DIRECT_MESSAGES);
|
||||
}
|
||||
|
||||
public boolean isAutoRefreshEnabled() {
|
||||
return mPreferences.getBoolean(KEY_AUTO_REFRESH, mPreferences.getBoolean(KEY_DEFAULT_AUTO_REFRESH, false));
|
||||
}
|
||||
|
||||
public boolean isAutoRefreshHomeTimelineEnabled() {
|
||||
return mPreferences.getBoolean(KEY_AUTO_REFRESH_HOME_TIMELINE, DEFAULT_AUTO_REFRESH_HOME_TIMELINE);
|
||||
}
|
||||
|
||||
public boolean isAutoRefreshMentionsEnabled() {
|
||||
return mPreferences.getBoolean(KEY_AUTO_REFRESH_MENTIONS, DEFAULT_AUTO_REFRESH_MENTIONS);
|
||||
}
|
||||
|
||||
public boolean isAutoRefreshTrendsEnabled() {
|
||||
return mPreferences.getBoolean(KEY_AUTO_REFRESH_TRENDS, DEFAULT_AUTO_REFRESH_TRENDS);
|
||||
}
|
||||
|
||||
public boolean isStreamingEnabled() {
|
||||
return mPreferences.getBoolean(KEY_ENABLE_STREAMING, false);
|
||||
}
|
||||
|
||||
public boolean isDirectMessagesNotificationEnabled() {
|
||||
return mPreferences.getBoolean(KEY_DIRECT_MESSAGES_NOTIFICATION, DEFAULT_DIRECT_MESSAGES_NOTIFICATION);
|
||||
}
|
||||
|
||||
public boolean isHomeTimelineNotificationEnabled() {
|
||||
return mPreferences.getBoolean(KEY_HOME_TIMELINE_NOTIFICATION, DEFAULT_HOME_TIMELINE_NOTIFICATION);
|
||||
}
|
||||
|
||||
public boolean isInteractionsNotificationEnabled() {
|
||||
return mPreferences.getBoolean(KEY_MENTIONS_NOTIFICATION, DEFAULT_MENTIONS_NOTIFICATION);
|
||||
}
|
||||
|
||||
public boolean isNotificationFollowingOnly() {
|
||||
return mPreferences.getBoolean(KEY_NOTIFICATION_FOLLOWING_ONLY, false);
|
||||
}
|
||||
|
||||
public boolean isNotificationMentionsOnly() {
|
||||
return mPreferences.getBoolean(KEY_NOTIFICATION_MENTIONS_ONLY, false);
|
||||
}
|
||||
|
||||
public boolean isNotificationEnabled() {
|
||||
return mPreferences.getBoolean(KEY_NOTIFICATION, DEFAULT_NOTIFICATION);
|
||||
}
|
||||
|
||||
public static AccountPreferences getAccountPreferences(final AccountPreferences[] prefs, final UserKey accountKey) {
|
||||
for (final AccountPreferences pref : prefs) {
|
||||
if (pref.getAccountKey() == accountKey) return pref;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static AccountPreferences[] getAccountPreferences(final Context context, final UserKey[] accountKeys) {
|
||||
if (context == null || accountKeys == null) return null;
|
||||
final AccountPreferences[] preferences = new AccountPreferences[accountKeys.length];
|
||||
for (int i = 0, j = preferences.length; i < j; i++) {
|
||||
preferences[i] = new AccountPreferences(context, accountKeys[i]);
|
||||
}
|
||||
return preferences;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static UserKey[] getAutoRefreshEnabledAccountIds(final Context context, final UserKey[] accountKeys) {
|
||||
if (context == null || accountKeys == null) return new UserKey[0];
|
||||
final UserKey[] temp = new UserKey[accountKeys.length];
|
||||
int i = 0;
|
||||
for (final UserKey accountKey : accountKeys) {
|
||||
if (new AccountPreferences(context, accountKey).isAutoRefreshEnabled()) {
|
||||
temp[i++] = accountKey;
|
||||
}
|
||||
}
|
||||
final UserKey[] enabledIds = new UserKey[i];
|
||||
System.arraycopy(temp, 0, enabledIds, 0, i);
|
||||
return enabledIds;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static AccountPreferences[] getNotificationEnabledPreferences(final Context context, final UserKey[] accountKeys) {
|
||||
if (context == null || accountKeys == null) return new AccountPreferences[0];
|
||||
final AccountPreferences[] temp = new AccountPreferences[accountKeys.length];
|
||||
int i = 0;
|
||||
for (final UserKey accountKey : accountKeys) {
|
||||
final AccountPreferences preference = new AccountPreferences(context, accountKey);
|
||||
if (preference.isNotificationEnabled()) {
|
||||
temp[i++] = preference;
|
||||
}
|
||||
}
|
||||
final AccountPreferences[] enabledIds = new AccountPreferences[i];
|
||||
System.arraycopy(temp, 0, enabledIds, 0, i);
|
||||
return enabledIds;
|
||||
}
|
||||
|
||||
public static boolean isNotificationHasLight(final int flags) {
|
||||
return (flags & VALUE_NOTIFICATION_FLAG_LIGHT) != 0;
|
||||
}
|
||||
|
||||
public static boolean isNotificationHasRingtone(final int flags) {
|
||||
return (flags & VALUE_NOTIFICATION_FLAG_RINGTONE) != 0;
|
||||
}
|
||||
|
||||
public static boolean isNotificationHasVibration(final int flags) {
|
||||
return (flags & VALUE_NOTIFICATION_FLAG_VIBRATION) != 0;
|
||||
}
|
||||
}
|
@ -634,11 +634,6 @@ public final class Utils implements Constants {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static boolean hasAutoRefreshAccounts(final Context context) {
|
||||
final UserKey[] accountKeys = DataStoreUtils.INSTANCE.getAccountKeys(context);
|
||||
return !ArrayUtils.isEmpty(AccountPreferences.getAutoRefreshEnabledAccountIds(context, accountKeys));
|
||||
}
|
||||
|
||||
public static boolean isBatteryOkay(final Context context) {
|
||||
if (context == null) return false;
|
||||
final Context app = context.getApplicationContext();
|
||||
|
@ -271,9 +271,7 @@ class HomeActivity : BaseActivity(), OnClickListener, OnPageChangeListener, Supp
|
||||
val initialTabPosition = handleIntent(intent, savedInstanceState == null)
|
||||
setTabPosition(initialTabPosition)
|
||||
|
||||
if (Utils.isStreamingEnabled()) {
|
||||
startService(Intent(this, StreamingService::class.java))
|
||||
}
|
||||
startService(Intent(this, StreamingService::class.java))
|
||||
|
||||
if (!showDrawerTutorial() && !kPreferences[defaultAutoRefreshAskedKey]) {
|
||||
showAutoRefreshConfirm()
|
||||
@ -313,10 +311,12 @@ class HomeActivity : BaseActivity(), OnClickListener, OnPageChangeListener, Supp
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
stopService(Intent(this, StreamingService::class.java))
|
||||
if (isFinishing) {
|
||||
// Stop only when exiting explicitly
|
||||
stopService(Intent(this, StreamingService::class.java))
|
||||
}
|
||||
|
||||
// Delete unused items in databases.
|
||||
|
||||
val context = applicationContext
|
||||
TaskStarter.execute(object : AbstractTask<Any?, Any?, Any?>() {
|
||||
override fun doLongOperation(params: Any?): Any? {
|
||||
|
@ -32,7 +32,6 @@ import org.mariotaku.twidere.extension.model.user
|
||||
import org.mariotaku.twidere.model.*
|
||||
import org.mariotaku.twidere.util.Utils
|
||||
import org.mariotaku.twidere.util.glide.RoundedRectTransformation
|
||||
import org.mariotaku.twidere.view.ShapedImageView
|
||||
|
||||
fun RequestManager.loadProfileImage(context: Context, url: String?, @ImageShapeStyle style: Int,
|
||||
cornerRadius: Float = 0f, cornerRadiusRatio: Float = 0f, size: String? = null): DrawableRequestBuilder<String?> {
|
||||
@ -142,15 +141,13 @@ internal inline fun <T> configureLoadProfileImage(context: Context, @ImageShapeS
|
||||
val builder = create()
|
||||
builder.diskCacheStrategy(DiskCacheStrategy.RESULT)
|
||||
builder.dontAnimate()
|
||||
if (!ShapedImageView.OUTLINE_DRAW) {
|
||||
when (shapeStyle) {
|
||||
ImageShapeStyle.SHAPE_CIRCLE -> {
|
||||
builder.bitmapTransform(CropCircleTransformation(context))
|
||||
}
|
||||
ImageShapeStyle.SHAPE_RECTANGLE -> {
|
||||
builder.bitmapTransform(RoundedRectTransformation(context, cornerRadius,
|
||||
cornerRadiusRatio))
|
||||
}
|
||||
when (shapeStyle) {
|
||||
ImageShapeStyle.SHAPE_CIRCLE -> {
|
||||
builder.bitmapTransform(CropCircleTransformation(context))
|
||||
}
|
||||
ImageShapeStyle.SHAPE_RECTANGLE -> {
|
||||
builder.bitmapTransform(RoundedRectTransformation(context, cornerRadius,
|
||||
cornerRadiusRatio))
|
||||
}
|
||||
}
|
||||
return builder
|
||||
|
@ -88,4 +88,8 @@ val Array<AccountDetails>.textLimit: Int
|
||||
}
|
||||
}
|
||||
return limit
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val AccountDetails.isStreamingSupported: Boolean
|
||||
get() = type == AccountType.TWITTER
|
@ -20,6 +20,7 @@
|
||||
package org.mariotaku.twidere.fragment
|
||||
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_ENABLE_STREAMING
|
||||
|
||||
class AccountStreamingSettingsFragment : BaseAccountPreferenceFragment() {
|
||||
|
||||
@ -28,6 +29,6 @@ class AccountStreamingSettingsFragment : BaseAccountPreferenceFragment() {
|
||||
|
||||
override val switchPreferenceDefault: Boolean = false
|
||||
|
||||
override val switchPreferenceKey: String? = "streaming"
|
||||
override val switchPreferenceKey: String? = KEY_ENABLE_STREAMING
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.model
|
||||
|
||||
import android.accounts.AccountManager
|
||||
import android.content.Context
|
||||
import android.media.RingtoneManager
|
||||
import android.net.Uri
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.text.TextUtils
|
||||
import org.mariotaku.ktextension.contains
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.TwidereConstants.ACCOUNT_PREFERENCES_NAME_PREFIX
|
||||
import org.mariotaku.twidere.constant.SharedPreferenceConstants.*
|
||||
import org.mariotaku.twidere.model.util.AccountUtils
|
||||
|
||||
class AccountPreferences(private val context: Context, val accountKey: UserKey) {
|
||||
private val preferences = context.getSharedPreferences("$ACCOUNT_PREFERENCES_NAME_PREFIX$accountKey", Context.MODE_PRIVATE)
|
||||
|
||||
val defaultNotificationLightColor: Int
|
||||
get() {
|
||||
val a = AccountUtils.getAccountDetails(AccountManager.get(context), accountKey, true)
|
||||
if (a != null) {
|
||||
return a.color
|
||||
} else {
|
||||
return ContextCompat.getColor(context, R.color.branding_color)
|
||||
}
|
||||
}
|
||||
|
||||
val directMessagesNotificationType: Int
|
||||
get() = preferences.getInt(KEY_NOTIFICATION_TYPE_DIRECT_MESSAGES, DEFAULT_NOTIFICATION_TYPE_DIRECT_MESSAGES)
|
||||
|
||||
val homeTimelineNotificationType: Int
|
||||
get() = preferences.getInt(KEY_NOTIFICATION_TYPE_HOME, DEFAULT_NOTIFICATION_TYPE_HOME)
|
||||
|
||||
val mentionsNotificationType: Int
|
||||
get() = preferences.getInt(KEY_NOTIFICATION_TYPE_MENTIONS, DEFAULT_NOTIFICATION_TYPE_MENTIONS)
|
||||
|
||||
val notificationLightColor: Int
|
||||
get() = preferences.getInt(KEY_NOTIFICATION_LIGHT_COLOR, defaultNotificationLightColor)
|
||||
|
||||
val notificationRingtone: Uri
|
||||
get() {
|
||||
val ringtone = preferences.getString(KEY_NOTIFICATION_RINGTONE, null)
|
||||
if (TextUtils.isEmpty(ringtone)) {
|
||||
return RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
|
||||
} else {
|
||||
return Uri.parse(ringtone)
|
||||
}
|
||||
}
|
||||
|
||||
val isAutoRefreshDirectMessagesEnabled: Boolean
|
||||
get() = preferences.getBoolean(KEY_AUTO_REFRESH_DIRECT_MESSAGES, DEFAULT_AUTO_REFRESH_DIRECT_MESSAGES)
|
||||
|
||||
val isAutoRefreshEnabled: Boolean
|
||||
get() = preferences.getBoolean(KEY_AUTO_REFRESH, preferences.getBoolean(KEY_DEFAULT_AUTO_REFRESH, false))
|
||||
|
||||
val isAutoRefreshHomeTimelineEnabled: Boolean
|
||||
get() = preferences.getBoolean(KEY_AUTO_REFRESH_HOME_TIMELINE, DEFAULT_AUTO_REFRESH_HOME_TIMELINE)
|
||||
|
||||
val isAutoRefreshMentionsEnabled: Boolean
|
||||
get() = preferences.getBoolean(KEY_AUTO_REFRESH_MENTIONS, DEFAULT_AUTO_REFRESH_MENTIONS)
|
||||
|
||||
val isAutoRefreshTrendsEnabled: Boolean
|
||||
get() = preferences.getBoolean(KEY_AUTO_REFRESH_TRENDS, DEFAULT_AUTO_REFRESH_TRENDS)
|
||||
|
||||
val isStreamingEnabled: Boolean
|
||||
get() = preferences.getBoolean(KEY_ENABLE_STREAMING, false)
|
||||
|
||||
val isDirectMessagesNotificationEnabled: Boolean
|
||||
get() = preferences.getBoolean(KEY_DIRECT_MESSAGES_NOTIFICATION, DEFAULT_DIRECT_MESSAGES_NOTIFICATION)
|
||||
|
||||
val isHomeTimelineNotificationEnabled: Boolean
|
||||
get() = preferences.getBoolean(KEY_HOME_TIMELINE_NOTIFICATION, DEFAULT_HOME_TIMELINE_NOTIFICATION)
|
||||
|
||||
val isInteractionsNotificationEnabled: Boolean
|
||||
get() = preferences.getBoolean(KEY_MENTIONS_NOTIFICATION, DEFAULT_MENTIONS_NOTIFICATION)
|
||||
|
||||
val isNotificationFollowingOnly: Boolean
|
||||
get() = preferences.getBoolean(KEY_NOTIFICATION_FOLLOWING_ONLY, false)
|
||||
|
||||
val isNotificationMentionsOnly: Boolean
|
||||
get() = preferences.getBoolean(KEY_NOTIFICATION_MENTIONS_ONLY, false)
|
||||
|
||||
val isNotificationEnabled: Boolean
|
||||
get() = preferences.getBoolean(KEY_NOTIFICATION, DEFAULT_NOTIFICATION)
|
||||
|
||||
companion object {
|
||||
|
||||
fun getAccountPreferences(context: Context, accountKeys: Array<UserKey>): Array<AccountPreferences> {
|
||||
return Array(accountKeys.size) { AccountPreferences(context, accountKeys[it]) }
|
||||
}
|
||||
|
||||
fun isNotificationHasLight(flags: Int): Boolean {
|
||||
return VALUE_NOTIFICATION_FLAG_LIGHT in flags
|
||||
}
|
||||
|
||||
fun isNotificationHasRingtone(flags: Int): Boolean {
|
||||
return VALUE_NOTIFICATION_FLAG_RINGTONE in flags
|
||||
}
|
||||
|
||||
fun isNotificationHasVibration(flags: Int): Boolean {
|
||||
return VALUE_NOTIFICATION_FLAG_VIBRATION in flags
|
||||
}
|
||||
}
|
||||
}
|
@ -89,7 +89,7 @@ abstract class AccountsListPreference(context: Context, attrs: AttributeSet? = n
|
||||
|
||||
init {
|
||||
GeneralComponentHelper.build(context).inject(this)
|
||||
val switchPreferenceName = ACCOUNT_PREFERENCES_NAME_PREFIX + account.key
|
||||
val switchPreferenceName = "$ACCOUNT_PREFERENCES_NAME_PREFIX${account.key}"
|
||||
switchPreference = context.getSharedPreferences(switchPreferenceName, Context.MODE_PRIVATE)
|
||||
switchPreference.registerOnSharedPreferenceChangeListener(this)
|
||||
title = account.user.name
|
||||
|
@ -21,16 +21,18 @@ package org.mariotaku.twidere.preference
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import org.mariotaku.ktextension.set
|
||||
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_ACCOUNT
|
||||
import org.mariotaku.twidere.extension.model.isStreamingSupported
|
||||
import org.mariotaku.twidere.fragment.AccountStreamingSettingsFragment
|
||||
import org.mariotaku.twidere.model.AccountDetails
|
||||
|
||||
class StreamingAccountsListPreference(context: Context, attrs: AttributeSet? = null) : AccountsListPreference(context, attrs) {
|
||||
|
||||
override fun setupPreference(preference: AccountsListPreference.AccountItemPreference, account: AccountDetails) {
|
||||
preference.isEnabled = account.isStreamingSupported
|
||||
preference.fragment = AccountStreamingSettingsFragment::class.java.name
|
||||
val args = preference.extras
|
||||
args.putParcelable(EXTRA_ACCOUNT, account)
|
||||
preference.extras[EXTRA_ACCOUNT] = account
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -508,9 +508,8 @@ class TwidereDataProvider : ContentProvider(), LazyLoadCallback {
|
||||
TABLE_ID_STATUSES -> {
|
||||
if (!uri.getBooleanQueryParameter(QUERY_PARAM_SHOW_NOTIFICATION, true)) return
|
||||
backgroundExecutor.execute {
|
||||
val prefs = AccountPreferences.getNotificationEnabledPreferences(context,
|
||||
DataStoreUtils.getAccountKeys(context))
|
||||
prefs.filter(AccountPreferences::isHomeTimelineNotificationEnabled).forEach {
|
||||
val prefs = AccountPreferences.getAccountPreferences(context, DataStoreUtils.getAccountKeys(context))
|
||||
prefs.filter { it.isNotificationEnabled && it.isHomeTimelineNotificationEnabled }.forEach {
|
||||
val positionTag = getPositionTag(CustomTabType.HOME_TIMELINE, it.accountKey)
|
||||
contentNotificationManager.showTimeline(it, positionTag)
|
||||
}
|
||||
@ -520,9 +519,8 @@ class TwidereDataProvider : ContentProvider(), LazyLoadCallback {
|
||||
TABLE_ID_ACTIVITIES_ABOUT_ME -> {
|
||||
if (!uri.getBooleanQueryParameter(QUERY_PARAM_SHOW_NOTIFICATION, true)) return
|
||||
backgroundExecutor.execute {
|
||||
val prefs = AccountPreferences.getNotificationEnabledPreferences(context,
|
||||
DataStoreUtils.getAccountKeys(context))
|
||||
prefs.filter(AccountPreferences::isInteractionsNotificationEnabled).forEach {
|
||||
val prefs = AccountPreferences.getAccountPreferences(context, DataStoreUtils.getAccountKeys(context))
|
||||
prefs.filter { it.isNotificationEnabled && it.isInteractionsNotificationEnabled }.forEach {
|
||||
val positionTag = getPositionTag(ReadPositionTag.ACTIVITIES_ABOUT_ME, it.accountKey)
|
||||
contentNotificationManager.showInteractions(it, positionTag)
|
||||
}
|
||||
@ -532,9 +530,8 @@ class TwidereDataProvider : ContentProvider(), LazyLoadCallback {
|
||||
TABLE_ID_MESSAGES_CONVERSATIONS -> {
|
||||
if (!uri.getBooleanQueryParameter(QUERY_PARAM_SHOW_NOTIFICATION, true)) return
|
||||
backgroundExecutor.execute {
|
||||
val prefs = AccountPreferences.getNotificationEnabledPreferences(context,
|
||||
DataStoreUtils.getAccountKeys(context))
|
||||
prefs.filter(AccountPreferences::isDirectMessagesNotificationEnabled).forEach {
|
||||
val prefs = AccountPreferences.getAccountPreferences(context, DataStoreUtils.getAccountKeys(context))
|
||||
prefs.filter { it.isNotificationEnabled && it.isDirectMessagesNotificationEnabled }.forEach {
|
||||
contentNotificationManager.showMessages(it)
|
||||
}
|
||||
notifyUnreadCountChanged(NOTIFICATION_ID_DIRECT_MESSAGES)
|
||||
|
@ -2,158 +2,277 @@ package org.mariotaku.twidere.service
|
||||
|
||||
import android.accounts.AccountManager
|
||||
import android.accounts.OnAccountsUpdateListener
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.support.v4.app.NotificationCompat
|
||||
import android.support.v4.util.SimpleArrayMap
|
||||
import android.util.Log
|
||||
import org.apache.commons.lang3.concurrent.BasicThreadFactory
|
||||
import org.mariotaku.abstask.library.TaskStarter
|
||||
import org.mariotaku.ktextension.addOnAccountsUpdatedListenerSafe
|
||||
import org.mariotaku.ktextension.removeOnAccountsUpdatedListenerSafe
|
||||
import org.mariotaku.library.objectcursor.ObjectCursor
|
||||
import org.mariotaku.microblog.library.MicroBlogException
|
||||
import org.mariotaku.microblog.library.twitter.TwitterUserStream
|
||||
import org.mariotaku.microblog.library.twitter.annotation.StreamWith
|
||||
import org.mariotaku.microblog.library.twitter.callback.UserStreamCallback
|
||||
import org.mariotaku.microblog.library.twitter.model.Activity
|
||||
import org.mariotaku.microblog.library.twitter.model.DirectMessage
|
||||
import org.mariotaku.microblog.library.twitter.model.Status
|
||||
import org.mariotaku.twidere.BuildConfig
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.TwidereConstants.LOGTAG
|
||||
import org.mariotaku.twidere.activity.SettingsActivity
|
||||
import org.mariotaku.twidere.annotation.AccountType
|
||||
import org.mariotaku.twidere.extension.model.isOfficial
|
||||
import org.mariotaku.twidere.extension.model.isStreamingSupported
|
||||
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
|
||||
import org.mariotaku.twidere.model.AccountDetails
|
||||
import org.mariotaku.twidere.model.AccountPreferences
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.model.account.cred.OAuthCredentials
|
||||
import org.mariotaku.twidere.model.*
|
||||
import org.mariotaku.twidere.model.util.AccountUtils
|
||||
import org.mariotaku.twidere.model.util.ParcelableActivityUtils
|
||||
import org.mariotaku.twidere.model.util.ParcelableStatusUtils
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Activities
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
|
||||
import org.mariotaku.twidere.task.twitter.GetActivitiesAboutMeTask
|
||||
import org.mariotaku.twidere.task.twitter.message.GetMessagesTask
|
||||
import org.mariotaku.twidere.util.DataStoreUtils
|
||||
import org.mariotaku.twidere.util.DebugLog
|
||||
import org.mariotaku.twidere.util.TwidereArrayUtils
|
||||
import org.mariotaku.twidere.util.NotificationManagerWrapper
|
||||
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper
|
||||
import org.mariotaku.twidere.util.streaming.TwitterTimelineStreamCallback
|
||||
import java.util.*
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.Future
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
class StreamingService : Service() {
|
||||
|
||||
private val callbacks = SimpleArrayMap<UserKey, UserStreamCallback>()
|
||||
@Inject
|
||||
internal lateinit var notificationManager: NotificationManagerWrapper
|
||||
internal lateinit var threadPoolExecutor: ExecutorService
|
||||
internal lateinit var handler: Handler
|
||||
|
||||
private var notificationManager: NotificationManager? = null
|
||||
|
||||
private var accountKeys: Array<UserKey>? = null
|
||||
private val stateMap = WeakHashMap<UserKey, Future<*>>()
|
||||
|
||||
private val accountChangeObserver = OnAccountsUpdateListener {
|
||||
if (!TwidereArrayUtils.contentMatch(accountKeys, DataStoreUtils.getActivatedAccountKeys(this@StreamingService))) {
|
||||
initStreaming()
|
||||
}
|
||||
setupStreaming()
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
DebugLog.d(LOGTAG, "Stream service started.")
|
||||
initStreaming()
|
||||
GeneralComponentHelper.build(this).inject(this)
|
||||
threadPoolExecutor = Executors.newCachedThreadPool(BasicThreadFactory.Builder().priority(Thread.NORM_PRIORITY - 1).build())
|
||||
handler = Handler(Looper.getMainLooper())
|
||||
AccountManager.get(this).addOnAccountsUpdatedListenerSafe(accountChangeObserver, updateImmediately = false)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
clearTwitterInstances()
|
||||
threadPoolExecutor.shutdown()
|
||||
stateMap.clear()
|
||||
removeNotification()
|
||||
AccountManager.get(this).removeOnAccountsUpdatedListenerSafe(accountChangeObserver)
|
||||
DebugLog.d(LOGTAG, "Stream service stopped.")
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
if (setupStreaming()) {
|
||||
return START_STICKY
|
||||
}
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent) = throw UnsupportedOperationException()
|
||||
|
||||
private fun setupStreaming(): Boolean {
|
||||
if (updateStreamingInstances()) {
|
||||
showNotification()
|
||||
return true
|
||||
} else {
|
||||
stopSelf()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private val Future<*>.isRunning: Boolean
|
||||
get() = !isCancelled && !isDone
|
||||
|
||||
private fun updateStreamingInstances(): Boolean {
|
||||
val am = AccountManager.get(this)
|
||||
val supportedAccounts = AccountUtils.getAllAccountDetails(am, true).filter { it.isStreamingSupported }
|
||||
val enabledPrefs = supportedAccounts.map { AccountPreferences(this, it.key) }
|
||||
val enabledAccounts = supportedAccounts.filter { account ->
|
||||
return@filter enabledPrefs.any {
|
||||
account.key == it.accountKey
|
||||
}
|
||||
}
|
||||
|
||||
if (enabledAccounts.isEmpty()) return false
|
||||
|
||||
// Remove all disabled instances
|
||||
stateMap.forEach { k, v ->
|
||||
if (enabledAccounts.none { k == it.key } && v.isRunning) {
|
||||
v.cancel(true)
|
||||
}
|
||||
}
|
||||
// Add instances if not running
|
||||
enabledAccounts.forEach { account ->
|
||||
val existing = stateMap[account.key]
|
||||
if (existing == null || !existing.isRunning) {
|
||||
val runnable = account.newStreamingRunnable() ?: return@forEach
|
||||
stateMap[account.key] = threadPoolExecutor.submit(runnable)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun showNotification() {
|
||||
val intent = Intent(this, SettingsActivity::class.java)
|
||||
val contentIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
val contentTitle = getString(R.string.app_name)
|
||||
val contentText = getString(R.string.timeline_streaming_running)
|
||||
val builder = NotificationCompat.Builder(this)
|
||||
builder.setOngoing(true)
|
||||
builder.setSmallIcon(R.drawable.ic_stat_streaming)
|
||||
builder.setContentTitle(contentTitle)
|
||||
builder.setContentText(contentText)
|
||||
builder.setContentIntent(contentIntent)
|
||||
builder.setCategory(NotificationCompat.CATEGORY_STATUS)
|
||||
builder.priority = NotificationCompat.PRIORITY_MIN
|
||||
startForeground(NOTIFICATION_SERVICE_STARTED, builder.build())
|
||||
}
|
||||
|
||||
private fun removeNotification() {
|
||||
stopForeground(true)
|
||||
}
|
||||
|
||||
private fun AccountDetails.newStreamingRunnable(): StreamingRunnable<*>? {
|
||||
when (type) {
|
||||
AccountType.TWITTER -> {
|
||||
return TwitterStreamingRunnable(this@StreamingService, handler, this)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun clearTwitterInstances() {
|
||||
var i = 0
|
||||
val j = callbacks.size()
|
||||
while (i < j) {
|
||||
Thread(ShutdownStreamTwitterRunnable(callbacks.valueAt(i))).start()
|
||||
i++
|
||||
}
|
||||
callbacks.clear()
|
||||
notificationManager!!.cancel(NOTIFICATION_SERVICE_STARTED)
|
||||
}
|
||||
|
||||
private fun initStreaming() {
|
||||
if (!BuildConfig.DEBUG) return
|
||||
setTwitterInstances()
|
||||
updateStreamState()
|
||||
}
|
||||
|
||||
private fun setTwitterInstances(): Boolean {
|
||||
val accountsList = AccountUtils.getAllAccountDetails(AccountManager.get(this), true).filter { it.credentials is OAuthCredentials }
|
||||
val accountKeys = accountsList.map { it.key }.toTypedArray()
|
||||
val activatedPreferences = AccountPreferences.getAccountPreferences(this, accountKeys)
|
||||
DebugLog.d(LOGTAG, "Setting up twitter stream instances")
|
||||
this.accountKeys = accountKeys
|
||||
clearTwitterInstances()
|
||||
var result = false
|
||||
accountsList.forEachIndexed { i, account ->
|
||||
val preferences = activatedPreferences[i]
|
||||
if (!preferences.isStreamingEnabled) {
|
||||
return@forEachIndexed
|
||||
}
|
||||
val twitter = account.newMicroBlogInstance(context = this, cls = TwitterUserStream::class.java)
|
||||
val callback = TwidereUserStreamCallback(this, account)
|
||||
callbacks.put(account.key, callback)
|
||||
object : Thread() {
|
||||
override fun run() {
|
||||
twitter.getUserStream(StreamWith.USER, callback)
|
||||
Log.d(LOGTAG, String.format("Stream %s disconnected", account.key))
|
||||
callbacks.remove(account.key)
|
||||
updateStreamState()
|
||||
}
|
||||
}.start()
|
||||
result = result or true
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun updateStreamState() {
|
||||
if (callbacks.size() > 0) {
|
||||
val intent = Intent(this, SettingsActivity::class.java)
|
||||
val contentIntent = PendingIntent.getActivity(this, 0, intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
val contentTitle = getString(R.string.app_name)
|
||||
val contentText = getString(R.string.timeline_streaming_running)
|
||||
val builder = NotificationCompat.Builder(this)
|
||||
builder.setOngoing(true)
|
||||
builder.setSmallIcon(R.drawable.ic_stat_refresh)
|
||||
builder.setContentTitle(contentTitle)
|
||||
builder.setContentText(contentText)
|
||||
builder.setContentIntent(contentIntent)
|
||||
notificationManager!!.notify(NOTIFICATION_SERVICE_STARTED, builder.build())
|
||||
} else {
|
||||
notificationManager!!.cancel(NOTIFICATION_SERVICE_STARTED)
|
||||
}
|
||||
}
|
||||
|
||||
internal class ShutdownStreamTwitterRunnable(private val callback: UserStreamCallback?) : Runnable {
|
||||
internal abstract class StreamingRunnable<T>(val context: Context, val account: AccountDetails) : Runnable {
|
||||
|
||||
override fun run() {
|
||||
if (callback == null) return
|
||||
Log.d(LOGTAG, "Disconnecting stream")
|
||||
callback.disconnect()
|
||||
val instance = createStreamingInstance()
|
||||
while (!Thread.currentThread().isInterrupted) {
|
||||
try {
|
||||
instance.beginStreaming()
|
||||
} catch (e: MicroBlogException) {
|
||||
|
||||
}
|
||||
Thread.sleep(TimeUnit.MINUTES.toMillis(1))
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun createStreamingInstance(): T
|
||||
|
||||
abstract fun T.beginStreaming()
|
||||
}
|
||||
|
||||
internal class TwidereUserStreamCallback(
|
||||
private val context: Context,
|
||||
private val account: AccountDetails
|
||||
) : TwitterTimelineStreamCallback(account.key.id) {
|
||||
override fun onHomeTimeline(status: Status): Boolean = true
|
||||
internal class TwitterStreamingRunnable(context: Context, val handler: Handler, account: AccountDetails) :
|
||||
StreamingRunnable<TwitterUserStream>(context, account) {
|
||||
|
||||
override fun onActivityAboutMe(activity: Activity): Boolean = true
|
||||
private val profileImageSize = context.getString(R.string.profile_image_size)
|
||||
private val isOfficial = account.isOfficial(context)
|
||||
|
||||
override fun onDirectMessage(directMessage: DirectMessage): Boolean = true
|
||||
private var canGetInteractions: Boolean = true
|
||||
private var canGetMessages: Boolean = true
|
||||
|
||||
private var statusStreamStarted: Boolean = false
|
||||
private val mentionsStreamStarted: Boolean = false
|
||||
private val interactionsTimeoutRunnable = Runnable {
|
||||
canGetInteractions = true
|
||||
}
|
||||
|
||||
private val messagesTimeoutRunnable = Runnable {
|
||||
canGetMessages = true
|
||||
}
|
||||
|
||||
val callback = object : TwitterTimelineStreamCallback(account.key.id) {
|
||||
private var homeInsertGap = false
|
||||
private var interactionsInsertGap = false
|
||||
override fun onConnected(): Boolean {
|
||||
homeInsertGap = true
|
||||
interactionsInsertGap = true
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onHomeTimeline(status: Status): Boolean {
|
||||
val values = ObjectCursor.valuesCreatorFrom(ParcelableStatus::class.java)
|
||||
.create(ParcelableStatusUtils.fromStatus(status, account.key, homeInsertGap,
|
||||
profileImageSize))
|
||||
context.contentResolver.insert(Statuses.CONTENT_URI, values)
|
||||
homeInsertGap = false
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onActivityAboutMe(activity: Activity): Boolean {
|
||||
if (isOfficial) {
|
||||
// Wait for 30 seconds to avoid rate limit
|
||||
if (canGetInteractions) {
|
||||
getInteractions()
|
||||
canGetInteractions = false
|
||||
handler.postDelayed(interactionsTimeoutRunnable, TimeUnit.SECONDS.toMillis(30))
|
||||
}
|
||||
} else {
|
||||
val values = ObjectCursor.valuesCreatorFrom(ParcelableActivity::class.java)
|
||||
.create(ParcelableActivityUtils.fromActivity(activity, account.key,
|
||||
interactionsInsertGap, profileImageSize))
|
||||
context.contentResolver.insert(Activities.AboutMe.CONTENT_URI, values)
|
||||
interactionsInsertGap = false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onDirectMessage(directMessage: DirectMessage): Boolean {
|
||||
if (canGetMessages) {
|
||||
getMessages()
|
||||
canGetMessages = false
|
||||
val timeout = TimeUnit.SECONDS.toMillis(if (isOfficial) 30 else 90)
|
||||
handler.postDelayed(messagesTimeoutRunnable, timeout)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun getInteractions() {
|
||||
val task = GetActivitiesAboutMeTask(context)
|
||||
task.params = object : SimpleRefreshTaskParam() {
|
||||
override val accountKeys: Array<UserKey> = arrayOf(account.key)
|
||||
|
||||
override val sinceIds: Array<String?>?
|
||||
get() = DataStoreUtils.getNewestActivityMaxPositions(context,
|
||||
Activities.AboutMe.CONTENT_URI, arrayOf(account.key))
|
||||
|
||||
override val sinceSortIds: LongArray?
|
||||
get() = DataStoreUtils.getNewestActivityMaxSortPositions(context,
|
||||
Activities.AboutMe.CONTENT_URI, arrayOf(account.key))
|
||||
|
||||
override val hasSinceIds: Boolean = true
|
||||
|
||||
}
|
||||
TaskStarter.execute(task)
|
||||
}
|
||||
|
||||
private fun getMessages() {
|
||||
val task = GetMessagesTask(context)
|
||||
task.params = object : GetMessagesTask.RefreshMessagesTaskParam(context) {
|
||||
override val accountKeys: Array<UserKey> = arrayOf(account.key)
|
||||
|
||||
override val hasSinceIds: Boolean = true
|
||||
}
|
||||
TaskStarter.execute(task)
|
||||
}
|
||||
}
|
||||
|
||||
override fun createStreamingInstance(): TwitterUserStream {
|
||||
return account.newMicroBlogInstance(context, cls = TwitterUserStream::class.java)
|
||||
}
|
||||
|
||||
override fun TwitterUserStream.beginStreaming() {
|
||||
getUserStream(StreamWith.USER, callback)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -164,3 +283,4 @@ class StreamingService : Service() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -15,10 +15,10 @@ 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.Statuses
|
||||
import org.mariotaku.twidere.task.filter.RefreshFiltersSubscriptionsTask
|
||||
import org.mariotaku.twidere.task.twitter.GetActivitiesAboutMeTask
|
||||
import org.mariotaku.twidere.task.twitter.GetHomeTimelineTask
|
||||
import org.mariotaku.twidere.task.twitter.message.GetMessagesTask
|
||||
import org.mariotaku.twidere.task.filter.RefreshFiltersSubscriptionsTask
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/1/6.
|
||||
@ -74,7 +74,7 @@ class TaskServiceRunner(
|
||||
override val accountKeys: Array<UserKey> by lazy {
|
||||
AccountPreferences.getAccountPreferences(context, DataStoreUtils.getAccountKeys(context)).filter {
|
||||
it.isAutoRefreshEnabled && it.isAutoRefreshDirectMessagesEnabled
|
||||
}.map(AccountPreferences::getAccountKey).toTypedArray()
|
||||
}.map(AccountPreferences::accountKey).toTypedArray()
|
||||
}
|
||||
}
|
||||
return task
|
||||
@ -100,7 +100,7 @@ class TaskServiceRunner(
|
||||
override val accountKeys: Array<UserKey> by lazy {
|
||||
return@lazy AccountPreferences.getAccountPreferences(context, DataStoreUtils.getAccountKeys(context)).filter {
|
||||
it.isAutoRefreshEnabled && refreshable(it)
|
||||
}.map(AccountPreferences::getAccountKey).toTypedArray()
|
||||
}.map(AccountPreferences::accountKey).toTypedArray()
|
||||
}
|
||||
|
||||
override val sinceIds: Array<String?>?
|
||||
|
@ -41,6 +41,7 @@ import org.mariotaku.twidere.provider.TwidereDataProvider
|
||||
import org.mariotaku.twidere.service.BaseIntentService
|
||||
import org.mariotaku.twidere.service.JobTaskService
|
||||
import org.mariotaku.twidere.service.LegacyTaskService
|
||||
import org.mariotaku.twidere.service.StreamingService
|
||||
import org.mariotaku.twidere.task.BaseAbstractTask
|
||||
import org.mariotaku.twidere.task.ManagedAsyncTask
|
||||
import org.mariotaku.twidere.text.util.EmojiEditableFactory
|
||||
@ -141,4 +142,6 @@ interface GeneralComponent {
|
||||
fun inject(controller: PremiumDashboardActivity.ExtraFeatureViewController)
|
||||
|
||||
fun inject(fragment: ExoPlayerPageFragment)
|
||||
|
||||
fun inject(service: StreamingService)
|
||||
}
|
||||
|
@ -130,9 +130,9 @@ abstract class TwitterTimelineStreamCallback(val accountId: String) : SimpleUser
|
||||
return false
|
||||
}
|
||||
|
||||
override abstract fun onDirectMessage(directMessage: DirectMessage): Boolean
|
||||
|
||||
protected abstract fun onHomeTimeline(status: Status): Boolean
|
||||
|
||||
protected abstract fun onActivityAboutMe(activity: Activity): Boolean
|
||||
|
||||
override abstract fun onDirectMessage(directMessage: DirectMessage): Boolean
|
||||
}
|
||||
|
@ -780,14 +780,18 @@
|
||||
<string name="preference_randomize_account_rename_accounts_confirm">Rename existing accounts?</string>
|
||||
<string name="preference_summary_auto_refresh_compatibility_mode">Enable for faster refresh interval, increases power usage on Android 5.0+</string>
|
||||
<string name="preference_summary_auto_refresh_power_saving">Stop auto refresh when battery is low</string>
|
||||
<string name="preference_summary_background_streaming">Press HOME instead of BACK to keep streaming open</string>
|
||||
<string name="preference_summary_chrome_custom_tab">Open links with in-app browser (Powered by Chrome)</string>
|
||||
<string name="preference_summary_database_item_limit">Upper limit of items stored in databases for each account, set to a smaller value to save space and increase loading speed.</string>
|
||||
<string name="preference_summary_media_preload_non_metered_network">Preload media only on free networks like Wi-Fi</string>
|
||||
<string name="preference_summary_streaming_non_metered_network">Streaming only on free networks like Wi-Fi</string>
|
||||
<string name="preference_summary_streaming_power_saving">Streaming only when charging</string>
|
||||
<string name="preference_summary_trends_location">Now you can set location separately in tab settings.</string>
|
||||
<string name="preference_title_accounts">Accounts</string>
|
||||
<string name="preference_title_advanced">Advanced</string>
|
||||
<string name="preference_title_auto_refresh_compatibility_mode">Compatibility mode</string>
|
||||
<string name="preference_title_auto_refresh_power_saving">Power saving mode</string>
|
||||
<string name="preference_title_background_streaming">Background streaming</string>
|
||||
<string name="preference_title_chrome_custom_tab">In-app browser</string>
|
||||
<string name="preference_title_database_item_limit">Database size limit</string>
|
||||
<string name="preference_title_filter_manage_subscriptions">Manage</string>
|
||||
@ -800,6 +804,8 @@
|
||||
<string name="preference_title_multi_column_tabs">Multi column tabs</string>
|
||||
<string name="preference_title_portrait">Portrait</string>
|
||||
<string name="preference_title_storage">Storage</string>
|
||||
<string name="preference_title_streaming_non_metered_network">Streaming on free network</string>
|
||||
<string name="preference_title_streaming_power_saving">Power saving mode</string>
|
||||
<string name="preference_title_tablet_mode">Tablet mode</string>
|
||||
<string name="preference_title_text_size">Text size</string>
|
||||
<string name="preference_title_translate">Translate</string>
|
||||
|
@ -9,24 +9,28 @@
|
||||
android:key="cat_accounts"
|
||||
android:title="@string/preference_title_accounts"
|
||||
app:switchDefault="false"
|
||||
app:switchKey="streaming"/>
|
||||
app:switchKey="enable_streaming"/>
|
||||
|
||||
<org.mariotaku.twidere.preference.TintedPreferenceCategory
|
||||
android:key="cat_general"
|
||||
android:title="@string/general">
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="combined_notifications"
|
||||
android:summaryOff="@string/combined_notifications_summary_off"
|
||||
android:summaryOn="@string/combined_notifications_summary_on"
|
||||
android:title="@string/combined_notifications"/>
|
||||
android:defaultValue="true"
|
||||
android:key="streaming_power_saving"
|
||||
android:summary="@string/preference_summary_streaming_power_saving"
|
||||
android:title="@string/preference_title_streaming_power_saving"/>
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="pebble_notifications"
|
||||
android:summary="@string/pebble_notifications_summary"
|
||||
android:title="@string/pebble_notifications"/>
|
||||
android:defaultValue="true"
|
||||
android:key="streaming_non_metered_network"
|
||||
android:summary="@string/preference_summary_streaming_non_metered_network"
|
||||
android:title="@string/preference_title_streaming_non_metered_network"/>
|
||||
|
||||
<Preference
|
||||
android:key="background_streaming_hint"
|
||||
android:summary="@string/preference_summary_background_streaming"
|
||||
android:title="@string/preference_title_background_streaming"/>
|
||||
</org.mariotaku.twidere.preference.TintedPreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
@ -7,9 +7,9 @@
|
||||
<g id="Action-Icons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="ic_action_streaming-mdpi">
|
||||
<rect id="Rectangle-2" x="4" y="4" width="24" height="24"></rect>
|
||||
<path d="M17.873855,14.2816795 L22,5 L10,17.7183205 L14.126145,17.7183205 L10,27 L22,14.2816795 L17.873855,14.2816795 Z" id="Triangle" fill="#FFFFFF"></path>
|
||||
<path d="M15,3 L17,3 L17,8 L15,8 L15,3 Z M29,15 L29,17 L24,17 L24,15 L29,15 Z M17,29 L15,29 L15,24 L17,24 L17,29 Z M3,17 L3,15 L8,15 L8,17 L3,17 Z" id="Rectangle" fill="#FFFFFF"></path>
|
||||
<path d="M23.7781746,6.80761184 L25.1923882,8.22182541 L22.363961,11.0502525 L20.9497475,9.63603897 L23.7781746,6.80761184 Z M25.1923882,23.7781746 L23.7781746,25.1923882 L20.9497475,22.363961 L22.363961,20.9497475 L25.1923882,23.7781746 Z M8.22182541,25.1923882 L6.80761184,23.7781746 L9.63603897,20.9497475 L11.0502525,22.363961 L8.22182541,25.1923882 Z M6.80761184,8.22182541 L8.22182541,6.80761184 L11.0502525,9.63603897 L9.63603897,11.0502525 L6.80761184,8.22182541 Z" id="Rectangle-3" fill="#FFFFFF"></path>
|
||||
<path d="M17.6036432,14.2816795 L21,5 L10,17.7183205 L14.3963568,17.7183205 L11,27 L22,14.2816795 L17.6036432,14.2816795 Z" id="Triangle" fill="#FFFFFF"></path>
|
||||
<path d="M15.25,3 L16.75,3 L16.75,8 L15.25,8 L15.25,3 Z M29,15.25 L29,16.75 L24,16.75 L24,15.25 L29,15.25 Z M16.75,29 L15.25,29 L15.25,24 L16.75,24 L16.75,29 Z M3,16.75 L3,15.25 L8,15.25 L8,16.75 L3,16.75 Z" id="Rectangle-4" fill="#FFFFFF"></path>
|
||||
<path d="M24.6620581,6.27728176 L25.7227182,7.33794193 L23.6013979,9.45926227 L22.5407377,8.3986021 L24.6620581,6.27728176 Z M25.7227182,24.6620581 L24.6620581,25.7227182 L22.5407377,23.6013979 L23.6013979,22.5407377 L25.7227182,24.6620581 Z M7.33794193,25.7227182 L6.27728176,24.6620581 L8.3986021,22.5407377 L9.45926227,23.6013979 L7.33794193,25.7227182 Z M6.27728176,7.33794193 L7.33794193,6.27728176 L9.45926227,8.3986021 L8.3986021,9.45926227 L6.27728176,7.33794193 Z" id="Rectangle-3" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
15
twidere/src/main/svg/drawable/ic_stat_streaming-mdpi.svg
Normal file
15
twidere/src/main/svg/drawable/ic_stat_streaming-mdpi.svg
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 42 (36781) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>ic_stat_streaming-mdpi</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Miscellaneous" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="ic_stat_streaming-mdpi">
|
||||
<rect id="Rectangle-2" x="2.76923077" y="2.76923077" width="18.4615385" height="18.4615385"></rect>
|
||||
<path d="M13.2335717,10.678215 L15.8461538,3.53846154 L7.38461538,13.321785 L10.7664283,13.321785 L8.15384615,20.4615385 L16.6153846,10.678215 L13.2335717,10.678215 Z" id="Triangle" fill="#FFFFFF"></path>
|
||||
<path d="M11.4230769,2 L12.5769231,2 L12.5769231,5.84615385 L11.4230769,5.84615385 L11.4230769,2 Z M22,11.4230769 L22,12.5769231 L18.1538462,12.5769231 L18.1538462,11.4230769 L22,11.4230769 Z M12.5769231,22 L11.4230769,22 L11.4230769,18.1538462 L12.5769231,18.1538462 L12.5769231,22 Z M2,12.5769231 L2,11.4230769 L5.84615385,11.4230769 L5.84615385,12.5769231 L2,12.5769231 Z" id="Rectangle-4" fill="#FFFFFF"></path>
|
||||
<path d="M18.6631216,4.52098597 L19.479014,5.33687841 L17.8472292,6.96866329 L17.0313367,6.15277085 L18.6631216,4.52098597 Z M19.479014,18.6631216 L18.6631216,19.479014 L17.0313367,17.8472292 L17.8472292,17.0313367 L19.479014,18.6631216 Z M5.33687841,19.479014 L4.52098597,18.6631216 L6.15277085,17.0313367 L6.96866329,17.8472292 L5.33687841,19.479014 Z M4.52098597,5.33687841 L5.33687841,4.52098597 L6.96866329,6.15277085 L6.15277085,6.96866329 L4.52098597,5.33687841 Z" id="Rectangle-3" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
Loading…
x
Reference in New Issue
Block a user