parent
ada7965a75
commit
d7a34d82a1
|
@ -20,6 +20,7 @@
|
|||
package org.mariotaku.twidere.model;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.mariotaku.twidere.util.TwidereArrayUtils;
|
||||
|
||||
|
@ -69,8 +70,8 @@ public class StringLongPair {
|
|||
return key + ":" + value;
|
||||
}
|
||||
|
||||
public static StringLongPair valueOf(String s) throws NumberFormatException {
|
||||
if (s == null) return null;
|
||||
@NonNull
|
||||
public static StringLongPair valueOf(@NonNull String s) throws NumberFormatException {
|
||||
final String[] segs = s.split(":");
|
||||
if (segs.length != 2) throw new NumberFormatException();
|
||||
return new StringLongPair(segs[0], Long.parseLong(segs[1]));
|
||||
|
|
|
@ -1,194 +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.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.mariotaku.twidere.annotation.CustomTabType;
|
||||
import org.mariotaku.twidere.annotation.NotificationType;
|
||||
import org.mariotaku.twidere.annotation.ReadPositionTag;
|
||||
import org.mariotaku.twidere.model.StringLongPair;
|
||||
import org.mariotaku.twidere.util.collection.CompactHashSet;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import static org.mariotaku.twidere.TwidereConstants.TIMELINE_POSITIONS_PREFERENCES_NAME;
|
||||
|
||||
public class ReadStateManager {
|
||||
|
||||
private final SharedPreferencesWrapper mPreferences;
|
||||
|
||||
public ReadStateManager(final Context context) {
|
||||
mPreferences = SharedPreferencesWrapper.getInstance(context,
|
||||
TIMELINE_POSITIONS_PREFERENCES_NAME, Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
public long getPosition(final String key) {
|
||||
if (TextUtils.isEmpty(key)) return -1;
|
||||
return mPreferences.getLong(key, -1);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public StringLongPair[] getPositionPairs(final String key) {
|
||||
if (TextUtils.isEmpty(key)) return new StringLongPair[0];
|
||||
final Set<String> set = mPreferences.getStringSet(key, null);
|
||||
if (set == null) return new StringLongPair[0];
|
||||
final StringLongPair[] pairs = new StringLongPair[set.size()];
|
||||
int count = 0;
|
||||
for (String entry : set.toArray(new String[set.size()])) {
|
||||
try {
|
||||
pairs[count++] = StringLongPair.valueOf(entry);
|
||||
} catch (NumberFormatException e) {
|
||||
return new StringLongPair[0];
|
||||
}
|
||||
}
|
||||
return pairs;
|
||||
}
|
||||
|
||||
public long getPosition(final String key, final String keyId) {
|
||||
if (TextUtils.isEmpty(key)) return -1;
|
||||
final Set<String> set = mPreferences.getStringSet(key, null);
|
||||
if (set == null) return -1;
|
||||
final String prefix = keyId + ":";
|
||||
for (String entry : set) {
|
||||
if (entry.startsWith(prefix)) return StringLongPair.valueOf(entry).getValue();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
|
||||
mPreferences.registerOnSharedPreferenceChangeListener(listener);
|
||||
}
|
||||
|
||||
public boolean setPosition(String key, String keyId, long position) {
|
||||
return setPosition(key, keyId, position, false);
|
||||
}
|
||||
|
||||
public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
|
||||
mPreferences.unregisterOnSharedPreferenceChangeListener(listener);
|
||||
}
|
||||
|
||||
|
||||
public boolean setPosition(final String key, final String keyId, final long position, boolean acceptOlder) {
|
||||
if (TextUtils.isEmpty(key)) return false;
|
||||
Set<String> set = mPreferences.getStringSet(key, null);
|
||||
if (set == null) {
|
||||
set = new CompactHashSet<>();
|
||||
}
|
||||
String keyValue = null;
|
||||
final String prefix = keyId + ":";
|
||||
for (String entry : set) {
|
||||
if (entry.startsWith(prefix)) {
|
||||
keyValue = entry;
|
||||
break;
|
||||
}
|
||||
}
|
||||
final StringLongPair pair;
|
||||
if (keyValue != null) {
|
||||
// Found value
|
||||
pair = StringLongPair.valueOf(keyValue);
|
||||
if (!acceptOlder && pair.getValue() > position) return false;
|
||||
set.remove(keyValue);
|
||||
pair.setValue(position);
|
||||
} else {
|
||||
pair = new StringLongPair(keyId, position);
|
||||
}
|
||||
set.add(pair.toString());
|
||||
final SharedPreferences.Editor editor = mPreferences.edit();
|
||||
editor.putStringSet(key, set);
|
||||
editor.apply();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public boolean setPositionPairs(final String key, @Nullable final StringLongPair[] pairs) {
|
||||
if (TextUtils.isEmpty(key)) return false;
|
||||
final SharedPreferences.Editor editor = mPreferences.edit();
|
||||
if (pairs == null) {
|
||||
editor.remove(key);
|
||||
} else {
|
||||
final Set<String> set = new CompactHashSet<>();
|
||||
for (StringLongPair pair : pairs) {
|
||||
set.add(pair.toString());
|
||||
}
|
||||
editor.putStringSet(key, set);
|
||||
}
|
||||
editor.apply();
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean setPosition(final String key, final long position) {
|
||||
return setPosition(key, position, false);
|
||||
}
|
||||
|
||||
public boolean setPosition(final String key, final long position, boolean acceptOlder) {
|
||||
if (TextUtils.isEmpty(key) || !acceptOlder && getPosition(key) >= position) return false;
|
||||
final SharedPreferences.Editor editor = mPreferences.edit();
|
||||
editor.putLong(key, position);
|
||||
editor.apply();
|
||||
return true;
|
||||
}
|
||||
|
||||
public interface OnReadStateChangeListener {
|
||||
void onReadStateChanged();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@ReadPositionTag
|
||||
public static String getReadPositionTagForNotificationType(@NotificationType String notificationType) {
|
||||
if (notificationType == null) return null;
|
||||
switch (notificationType) {
|
||||
case NotificationType.HOME_TIMELINE: {
|
||||
return ReadPositionTag.HOME_TIMELINE;
|
||||
}
|
||||
case NotificationType.DIRECT_MESSAGES: {
|
||||
return ReadPositionTag.DIRECT_MESSAGES;
|
||||
}
|
||||
case NotificationType.INTERACTIONS: {
|
||||
return ReadPositionTag.ACTIVITIES_ABOUT_ME;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@ReadPositionTag
|
||||
public static String getReadPositionTagForTabType(@CustomTabType String tabType) {
|
||||
if (tabType == null) return null;
|
||||
switch (tabType) {
|
||||
case CustomTabType.HOME_TIMELINE: {
|
||||
return ReadPositionTag.HOME_TIMELINE;
|
||||
}
|
||||
case CustomTabType.NOTIFICATIONS_TIMELINE: {
|
||||
return ReadPositionTag.ACTIVITIES_ABOUT_ME;
|
||||
}
|
||||
case CustomTabType.DIRECT_MESSAGES: {
|
||||
return ReadPositionTag.DIRECT_MESSAGES;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -838,10 +838,9 @@ public final class Utils implements Constants {
|
|||
return accountKeys[0];
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static String getReadPositionTagWithAccount(@Nullable final String tag,
|
||||
@NonNull
|
||||
public static String getReadPositionTagWithAccount(@NonNull final String tag,
|
||||
@Nullable final UserKey accountKey) {
|
||||
if (tag == null) return null;
|
||||
if (accountKey == null) return tag;
|
||||
return tag + "_" + accountKey;
|
||||
}
|
||||
|
|
|
@ -112,13 +112,6 @@ abstract class AbsActivitiesFragment protected constructor() : AbsContentListRec
|
|||
}
|
||||
}
|
||||
|
||||
private val onScrollListener = object : OnScrollListener() {
|
||||
override fun onScrollStateChanged(recyclerView: RecyclerView?, newState: Int) {
|
||||
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
|
||||
saveReadPosition()
|
||||
}
|
||||
}
|
||||
}
|
||||
private var navigationHelper: RecyclerViewNavigationHelper? = null
|
||||
private var pauseOnScrollListener: OnScrollListener? = null
|
||||
private var activeHotMobiScrollTracker: OnScrollListener? = null
|
||||
|
@ -213,14 +206,6 @@ abstract class AbsActivitiesFragment protected constructor() : AbsContentListRec
|
|||
return onCreateActivitiesLoader(activity, args, fromUser)
|
||||
}
|
||||
|
||||
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
|
||||
super.setUserVisibleHint(isVisibleToUser)
|
||||
|
||||
if (isVisibleToUser) {
|
||||
saveReadPosition()
|
||||
}
|
||||
}
|
||||
|
||||
protected fun saveReadPosition() {
|
||||
val layoutManager = layoutManager
|
||||
if (layoutManager != null) {
|
||||
|
@ -380,7 +365,6 @@ abstract class AbsActivitiesFragment protected constructor() : AbsContentListRec
|
|||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
recyclerView.addOnScrollListener(onScrollListener)
|
||||
recyclerView.addOnScrollListener(pauseOnScrollListener)
|
||||
val task = object : AbstractTask<Any?, Boolean, RecyclerView>() {
|
||||
public override fun doLongOperation(params: Any?): Boolean {
|
||||
|
@ -411,7 +395,6 @@ abstract class AbsActivitiesFragment protected constructor() : AbsContentListRec
|
|||
}
|
||||
activeHotMobiScrollTracker = null
|
||||
recyclerView.removeOnScrollListener(pauseOnScrollListener)
|
||||
recyclerView.removeOnScrollListener(onScrollListener)
|
||||
if (userVisibleHint) {
|
||||
saveReadPosition()
|
||||
}
|
||||
|
@ -489,17 +472,21 @@ abstract class AbsActivitiesFragment protected constructor() : AbsContentListRec
|
|||
protected abstract fun onLoadingFinished()
|
||||
|
||||
protected fun saveReadPosition(position: Int) {
|
||||
if (context == null) return
|
||||
if (host == null) return
|
||||
if (position == RecyclerView.NO_POSITION) return
|
||||
val item = adapter!!.getActivity(position) ?: return
|
||||
|
||||
var positionUpdated = false
|
||||
readPositionTag?.let {
|
||||
for (accountKey in accountKeys) {
|
||||
val tag = Utils.getReadPositionTagWithAccount(readPositionTag, accountKey)
|
||||
val tag = Utils.getReadPositionTagWithAccount(it, accountKey)
|
||||
if (readStateManager.setPosition(tag, item.timestamp)) {
|
||||
positionUpdated = true
|
||||
}
|
||||
}
|
||||
}
|
||||
currentReadPositionTag?.let {
|
||||
readStateManager.setPosition(it, item.timestamp, true)
|
||||
}
|
||||
|
||||
if (positionUpdated) {
|
||||
twitterWrapper.setActivitiesAboutMeUnreadAsync(accountKeys, item.timestamp)
|
||||
|
|
|
@ -305,14 +305,6 @@ abstract class AbsStatusesFragment protected constructor() :
|
|||
return onCreateStatusesLoader(activity, args, fromUser)
|
||||
}
|
||||
|
||||
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
|
||||
super.setUserVisibleHint(isVisibleToUser)
|
||||
|
||||
if (isVisibleToUser) {
|
||||
saveReadPosition()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLoadFinished(loader: Loader<List<ParcelableStatus>?>, data: List<ParcelableStatus>?) {
|
||||
val adapter = adapter ?: return
|
||||
val rememberPosition = preferences.getBoolean(SharedPreferenceConstants.KEY_REMEMBER_POSITION, false)
|
||||
|
@ -508,15 +500,21 @@ abstract class AbsStatusesFragment protected constructor() :
|
|||
|
||||
|
||||
protected fun saveReadPosition(position: Int) {
|
||||
if (host == null) return
|
||||
if (position == RecyclerView.NO_POSITION) return
|
||||
val adapter = adapter
|
||||
val status = adapter!!.getStatus(position) ?: return
|
||||
val adapter = adapter ?: return
|
||||
val status = adapter.getStatus(position) ?: return
|
||||
val positionKey = if (status.position_key > 0) status.position_key else status.timestamp
|
||||
readPositionTagWithArguments?.let {
|
||||
for (accountKey in accountKeys) {
|
||||
val tag = Utils.getReadPositionTagWithAccount(readPositionTagWithArguments, accountKey)
|
||||
val tag = Utils.getReadPositionTagWithAccount(it, accountKey)
|
||||
readStateManager.setPosition(tag, positionKey)
|
||||
}
|
||||
}
|
||||
currentReadPositionTag?.let {
|
||||
readStateManager.setPosition(it, positionKey, true)
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract fun hasMoreData(data: List<ParcelableStatus>?): Boolean
|
||||
|
||||
|
|
|
@ -70,9 +70,10 @@ class ImagePageFragment : SubsampleImageViewerFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
override fun setupImageView(imageView: SubsamplingScaleImageView?) {
|
||||
imageView!!.maxScale = resources.displayMetrics.density
|
||||
override fun setupImageView(imageView: SubsamplingScaleImageView) {
|
||||
imageView.maxScale = resources.displayMetrics.density
|
||||
imageView.setBitmapDecoderClass(PreviewBitmapDecoder::class.java)
|
||||
imageView.setParallelLoadingEnabled(true)
|
||||
}
|
||||
|
||||
override fun getImageSource(data: CacheDownloadLoader.Result): ImageSource {
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
* 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.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||
import android.text.TextUtils
|
||||
import org.mariotaku.twidere.TwidereConstants.TIMELINE_POSITIONS_PREFERENCES_NAME
|
||||
import org.mariotaku.twidere.annotation.CustomTabType
|
||||
import org.mariotaku.twidere.annotation.NotificationType
|
||||
import org.mariotaku.twidere.annotation.ReadPositionTag
|
||||
import org.mariotaku.twidere.model.StringLongPair
|
||||
import org.mariotaku.twidere.util.collection.CompactHashSet
|
||||
|
||||
class ReadStateManager(context: Context) {
|
||||
|
||||
private val preferences: SharedPreferencesWrapper
|
||||
|
||||
init {
|
||||
preferences = SharedPreferencesWrapper.getInstance(context,
|
||||
TIMELINE_POSITIONS_PREFERENCES_NAME, Context.MODE_PRIVATE)
|
||||
}
|
||||
|
||||
fun getPosition(key: String): Long {
|
||||
if (TextUtils.isEmpty(key)) return -1
|
||||
return preferences.getLong(key, -1)
|
||||
}
|
||||
|
||||
fun getPositionPairs(key: String): Array<StringLongPair> {
|
||||
if (TextUtils.isEmpty(key)) return emptyArray()
|
||||
val set = preferences.getStringSet(key, null) ?: return emptyArray()
|
||||
try {
|
||||
return set.map { StringLongPair.valueOf(it) }.toTypedArray()
|
||||
} catch (e: NumberFormatException) {
|
||||
return emptyArray()
|
||||
}
|
||||
}
|
||||
|
||||
fun getPosition(key: String, keyId: String): Long {
|
||||
if (TextUtils.isEmpty(key)) return -1
|
||||
val set = preferences.getStringSet(key, null) ?: return -1
|
||||
val prefix = keyId + ":"
|
||||
val first = set.firstOrNull { it.startsWith(prefix) } ?: return -1
|
||||
return StringLongPair.valueOf(first).value
|
||||
}
|
||||
|
||||
fun registerOnSharedPreferenceChangeListener(listener: OnSharedPreferenceChangeListener) {
|
||||
preferences.registerOnSharedPreferenceChangeListener(listener)
|
||||
}
|
||||
|
||||
fun unregisterOnSharedPreferenceChangeListener(listener: OnSharedPreferenceChangeListener) {
|
||||
preferences.unregisterOnSharedPreferenceChangeListener(listener)
|
||||
}
|
||||
|
||||
@JvmOverloads fun setPosition(key: String, keyId: String, position: Long, acceptOlder: Boolean = false): Boolean {
|
||||
if (TextUtils.isEmpty(key)) return false
|
||||
val set: MutableSet<String> = preferences.getStringSet(key, null) ?: CompactHashSet<String>()
|
||||
val prefix = keyId + ":"
|
||||
val keyValue: String? = set.firstOrNull { it.startsWith(prefix) }
|
||||
val pair: StringLongPair
|
||||
if (keyValue != null) {
|
||||
// Found value
|
||||
pair = StringLongPair.valueOf(keyValue)
|
||||
if (!acceptOlder && pair.value > position) return false
|
||||
set.remove(keyValue)
|
||||
pair.value = position
|
||||
} else {
|
||||
pair = StringLongPair(keyId, position)
|
||||
}
|
||||
set.add(pair.toString())
|
||||
val editor = preferences.edit()
|
||||
editor.putStringSet(key, set)
|
||||
editor.apply()
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
fun setPositionPairs(key: String, pairs: Array<StringLongPair>?): Boolean {
|
||||
if (TextUtils.isEmpty(key)) return false
|
||||
val editor = preferences.edit()
|
||||
if (pairs == null) {
|
||||
editor.remove(key)
|
||||
} else {
|
||||
val set = CompactHashSet<String>()
|
||||
for (pair in pairs) {
|
||||
set.add(pair.toString())
|
||||
}
|
||||
editor.putStringSet(key, set)
|
||||
}
|
||||
editor.apply()
|
||||
return true
|
||||
}
|
||||
|
||||
@JvmOverloads fun setPosition(key: String, position: Long, acceptOlder: Boolean = false): Boolean {
|
||||
if (TextUtils.isEmpty(key) || !acceptOlder && getPosition(key) >= position) return false
|
||||
val editor = preferences.edit()
|
||||
editor.putLong(key, position)
|
||||
editor.apply()
|
||||
return true
|
||||
}
|
||||
|
||||
interface OnReadStateChangeListener {
|
||||
fun onReadStateChanged()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@ReadPositionTag
|
||||
fun getReadPositionTagForNotificationType(@NotificationType notificationType: String?): String? {
|
||||
if (notificationType == null) return null
|
||||
when (notificationType) {
|
||||
NotificationType.HOME_TIMELINE -> {
|
||||
return ReadPositionTag.HOME_TIMELINE
|
||||
}
|
||||
NotificationType.DIRECT_MESSAGES -> {
|
||||
return ReadPositionTag.DIRECT_MESSAGES
|
||||
}
|
||||
NotificationType.INTERACTIONS -> {
|
||||
return ReadPositionTag.ACTIVITIES_ABOUT_ME
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@ReadPositionTag
|
||||
fun getReadPositionTagForTabType(@CustomTabType tabType: String?): String? {
|
||||
if (tabType == null) return null
|
||||
when (tabType) {
|
||||
CustomTabType.HOME_TIMELINE -> {
|
||||
return ReadPositionTag.HOME_TIMELINE
|
||||
}
|
||||
CustomTabType.NOTIFICATIONS_TIMELINE -> {
|
||||
return ReadPositionTag.ACTIVITIES_ABOUT_ME
|
||||
}
|
||||
CustomTabType.DIRECT_MESSAGES -> {
|
||||
return ReadPositionTag.DIRECT_MESSAGES
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue