mirror of
https://github.com/TwidereProject/Twidere-Android
synced 2025-02-17 04:00:48 +01:00
improved load more position remembering
This commit is contained in:
parent
0de5011e80
commit
d0c05a2600
@ -21,6 +21,7 @@ package org.mariotaku.twidere.model;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.IntDef;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
@ -353,6 +354,11 @@ public class ParcelableStatus implements Parcelable, Comparable<ParcelableStatus
|
||||
@ParcelableThisPlease
|
||||
public boolean is_pinned_status;
|
||||
|
||||
@FilterFlags
|
||||
@CursorField(Statuses.FILTER_FLAGS)
|
||||
@ParcelableThisPlease
|
||||
public long filter_flags;
|
||||
|
||||
public ParcelableStatus() {
|
||||
}
|
||||
|
||||
@ -557,4 +563,35 @@ public class ParcelableStatus implements Parcelable, Comparable<ParcelableStatus
|
||||
ParcelableStatus$ExtrasParcelablePlease.writeToParcel(this, dest, flags);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flags for filtering some kind of tweet.
|
||||
* We use bitwise operations against string comparisons because it's much faster.
|
||||
* <p>
|
||||
* DO NOT CHANGE ONCE DEFINED!
|
||||
*/
|
||||
@IntDef(value = {
|
||||
FilterFlags.QUOTE_NOT_AVAILABLE,
|
||||
FilterFlags.BLOCKING_USER,
|
||||
FilterFlags.BLOCKED_BY_USER
|
||||
}, flag = true)
|
||||
public @interface FilterFlags {
|
||||
/**
|
||||
* Original tweet of a quote tweet is unavailable.
|
||||
* Happens when:
|
||||
* <p>
|
||||
* <li/>You were blocked by this user
|
||||
* <li/>You blocked/muted this user
|
||||
* <li/>Original tweet was marked sensitive and your account settings blocked them
|
||||
*/
|
||||
long QUOTE_NOT_AVAILABLE = 0x1;
|
||||
/**
|
||||
* Original author of a quote/retweet was blocked by you
|
||||
*/
|
||||
long BLOCKING_USER = 0x2;
|
||||
/**
|
||||
* You were blocked by original author of a quote/retweet
|
||||
*/
|
||||
long BLOCKED_BY_USER = 0x4;
|
||||
}
|
||||
}
|
||||
|
@ -852,6 +852,7 @@ public interface TwidereDataStore {
|
||||
String RETWEET_USER_NICKNAME = "retweet_user_nickname";
|
||||
String IN_REPLY_TO_USER_NICKNAME = "in_reply_to_user_nickname";
|
||||
|
||||
String FILTER_FLAGS = "filter_flags";
|
||||
|
||||
String DEFAULT_SORT_ORDER = STATUS_TIMESTAMP + " DESC, " + SORT_ID + " DESC, " + STATUS_ID
|
||||
+ " DESC";
|
||||
|
@ -173,7 +173,7 @@ dependencies {
|
||||
compile "com.github.mariotaku.CommonsLibrary:text:$mariotaku_commons_library_version"
|
||||
compile "com.github.mariotaku.CommonsLibrary:text-kotlin:$mariotaku_commons_library_version"
|
||||
compile 'com.github.mariotaku:KPreferences:0.9.5'
|
||||
compile 'com.github.mariotaku:Chameleon:0.9.1'
|
||||
compile 'com.github.mariotaku:Chameleon:0.9.2'
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
compile 'nl.komponents.kovenant:kovenant:3.3.0'
|
||||
compile 'nl.komponents.kovenant:kovenant-android:3.3.0'
|
||||
|
@ -108,33 +108,11 @@ class GoogleMapFragment : SupportMapFragment(), Constants, IMapFragment, IBaseFr
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override val extraConfiguration: Bundle? = null
|
||||
override val tabPosition: Int = -1
|
||||
override val tabId: Long = -1L
|
||||
|
||||
override fun requestFitSystemWindows() {
|
||||
val activity = activity
|
||||
val parentFragment = parentFragment
|
||||
val callback: IBaseFragment.SystemWindowsInsetsCallback
|
||||
if (parentFragment is IBaseFragment.SystemWindowsInsetsCallback) {
|
||||
callback = parentFragment
|
||||
} else if (activity is IBaseFragment.SystemWindowsInsetsCallback) {
|
||||
callback = activity
|
||||
} else {
|
||||
return
|
||||
}
|
||||
val insets = Rect()
|
||||
if (callback.getSystemWindowsInsets(insets)) {
|
||||
fitSystemWindows(insets)
|
||||
}
|
||||
}
|
||||
|
||||
override fun executeAfterFragmentResumed(action: (IBaseFragment) -> Unit) {
|
||||
actionHelper.executeAfterFragmentResumed(action)
|
||||
}
|
||||
|
||||
private fun fitSystemWindows(insets: Rect) {
|
||||
override fun fitSystemWindows(insets: Rect) {
|
||||
val view = view
|
||||
view?.setPadding(insets.left, insets.top, insets.right, insets.bottom)
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ import static org.mariotaku.twidere.annotation.PreferenceType.STRING;
|
||||
public interface Constants extends TwidereConstants {
|
||||
|
||||
String DATABASES_NAME = "twidere.sqlite";
|
||||
int DATABASES_VERSION = 154;
|
||||
int DATABASES_VERSION = 155;
|
||||
|
||||
int MENU_GROUP_STATUS_EXTENSION = 10;
|
||||
int MENU_GROUP_COMPOSE_EXTENSION = 11;
|
||||
|
@ -1,83 +0,0 @@
|
||||
package org.mariotaku.twidere.util;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.widget.AbsListView;
|
||||
|
||||
import org.mariotaku.twidere.util.support.ViewSupport;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/3/1.
|
||||
*/
|
||||
public class ListViewScrollHandler extends ContentScrollHandler implements AbsListView.OnScrollListener,
|
||||
ListScrollDistanceCalculator.ScrollDistanceListener {
|
||||
private final ListScrollDistanceCalculator mCalculator;
|
||||
@Nullable
|
||||
private AbsListView.OnScrollListener mOnScrollListener;
|
||||
private int mDy;
|
||||
private int mOldState = AbsListView.OnScrollListener.SCROLL_STATE_IDLE;
|
||||
|
||||
public ListViewScrollHandler(@NonNull ContentListSupport contentListSupport, @Nullable ViewCallback viewCallback) {
|
||||
super(contentListSupport, viewCallback);
|
||||
mCalculator = new ListScrollDistanceCalculator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrollStateChanged(AbsListView view, int scrollState) {
|
||||
mCalculator.onScrollStateChanged(view, scrollState);
|
||||
mCalculator.setScrollDistanceListener(this);
|
||||
handleScrollStateChanged(scrollState, SCROLL_STATE_IDLE);
|
||||
if (mOnScrollListener != null) {
|
||||
mOnScrollListener.onScrollStateChanged(view, scrollState);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
|
||||
mCalculator.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
|
||||
final int scrollState = getScrollState();
|
||||
handleScroll(mDy, scrollState, mOldState, SCROLL_STATE_IDLE);
|
||||
if (mOnScrollListener != null) {
|
||||
mOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public AbsListView.OnScrollListener getOnScrollListener() {
|
||||
return mOnScrollListener;
|
||||
}
|
||||
|
||||
public void setOnScrollListener(@Nullable AbsListView.OnScrollListener onScrollListener) {
|
||||
mOnScrollListener = onScrollListener;
|
||||
}
|
||||
|
||||
public int getTotalScrollDistance() {
|
||||
return mCalculator.getTotalScrollDistance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrollDistanceChanged(int delta, int total) {
|
||||
mDy = -delta;
|
||||
final int scrollState = getScrollState();
|
||||
handleScroll(mDy, scrollState, mOldState, SCROLL_STATE_IDLE);
|
||||
mOldState = scrollState;
|
||||
}
|
||||
|
||||
public static class ListViewCallback implements ViewCallback {
|
||||
private final AbsListView listView;
|
||||
|
||||
public ListViewCallback(AbsListView listView) {
|
||||
this.listView = listView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getComputingLayout() {
|
||||
return ViewSupport.isInLayout(listView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void post(@NonNull Runnable runnable) {
|
||||
listView.post(runnable);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package org.mariotaku.ktextension
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2016/12/24.
|
||||
*/
|
||||
|
||||
fun Collection<*>?.isNotNullOrEmpty(): Boolean {
|
||||
return this != null && this.isNotEmpty()
|
||||
}
|
||||
|
||||
fun Collection<*>?.isNullOrEmpty(): Boolean {
|
||||
return this == null || this.isEmpty()
|
||||
}
|
@ -11,4 +11,8 @@ inline fun <T> configure(receiver: T, block: T.() -> Unit): T {
|
||||
|
||||
inline fun <F, T> F.convert(block: (F) -> T): T {
|
||||
return block(this)
|
||||
}
|
||||
|
||||
fun rangeOfSize(start: Int, size: Int): IntRange {
|
||||
return IntRange(start, start + size)
|
||||
}
|
@ -714,11 +714,10 @@ class SignInActivity : BaseActivity(), OnClickListener, TextWatcher {
|
||||
builder.setPositiveButton(android.R.string.ok) { dialog, which ->
|
||||
val editConsumerKey = (dialog as Dialog).findViewById(R.id.editConsumerKey) as EditText
|
||||
val editConsumerSecret = dialog.findViewById(R.id.editConsumerSecret) as EditText
|
||||
val prefs = SharedPreferencesWrapper.getInstance(activity, SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)
|
||||
val editor = prefs.edit()
|
||||
editor.putString(KEY_CONSUMER_KEY, ParseUtils.parseString(editConsumerKey.text))
|
||||
editor.putString(KEY_CONSUMER_SECRET, ParseUtils.parseString(editConsumerSecret.text))
|
||||
editor.apply()
|
||||
val apiConfig = kPreferences[defaultAPIConfigKey]
|
||||
apiConfig.consumerKey = editConsumerKey.text.toString()
|
||||
apiConfig.consumerSecret = editConsumerSecret.text.toString()
|
||||
kPreferences[defaultAPIConfigKey] = apiConfig
|
||||
}
|
||||
val dialog = builder.create()
|
||||
dialog.setOnShowListener(DialogInterface.OnShowListener { dialog ->
|
||||
@ -727,9 +726,9 @@ class SignInActivity : BaseActivity(), OnClickListener, TextWatcher {
|
||||
val editConsumerSecret = dialog.findViewById(R.id.editConsumerSecret) as MaterialEditText
|
||||
editConsumerKey.addValidator(ConsumerKeySecretValidator(getString(R.string.invalid_consumer_key)))
|
||||
editConsumerSecret.addValidator(ConsumerKeySecretValidator(getString(R.string.invalid_consumer_secret)))
|
||||
val prefs = SharedPreferencesWrapper.getInstance(activity, SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)
|
||||
editConsumerKey.setText(prefs.getString(KEY_CONSUMER_KEY, null))
|
||||
editConsumerSecret.setText(prefs.getString(KEY_CONSUMER_SECRET, null))
|
||||
val apiConfig = kPreferences[defaultAPIConfigKey]
|
||||
editConsumerKey.setText(apiConfig.consumerKey)
|
||||
editConsumerSecret.setText(apiConfig.consumerSecret)
|
||||
})
|
||||
return dialog
|
||||
}
|
||||
|
@ -20,11 +20,13 @@
|
||||
package org.mariotaku.twidere.adapter
|
||||
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.support.v4.widget.Space
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import org.mariotaku.ktextension.findPositionByItemId
|
||||
import org.mariotaku.ktextension.rangeOfSize
|
||||
import org.mariotaku.ktextension.safeMoveToPosition
|
||||
import org.mariotaku.library.objectcursor.ObjectCursor
|
||||
import org.mariotaku.twidere.R
|
||||
@ -176,58 +178,49 @@ abstract class ParcelableStatusesAdapter(
|
||||
get() = data?.size ?: 0
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
val dataPosition = position - getItemStartPosition(2)
|
||||
if (dataPosition < 0 || dataPosition >= statusCount) return position.toLong()
|
||||
if (data is ObjectCursor) {
|
||||
val cursor = (data as ObjectCursor).cursor
|
||||
if (!cursor.safeMoveToPosition(dataPosition)) return -1
|
||||
val indices = (data as ObjectCursor).indices as ParcelableStatusCursorIndices
|
||||
return getFieldValue(position, { cursor, indices ->
|
||||
val accountKey = UserKey.valueOf(cursor.getString(indices.account_key))
|
||||
val id = cursor.getString(indices.id)
|
||||
return ParcelableStatus.calculateHashCode(accountKey, id).toLong()
|
||||
}
|
||||
return data!![dataPosition].hashCode().toLong()
|
||||
return@getFieldValue ParcelableStatus.calculateHashCode(accountKey, id).toLong()
|
||||
}, { status ->
|
||||
return@getFieldValue status.hashCode().toLong()
|
||||
}, -1L)
|
||||
}
|
||||
|
||||
override fun getStatusId(position: Int): String? {
|
||||
val dataPosition = position - getItemStartPosition(2)
|
||||
if (dataPosition < 0 || dataPosition >= rawStatusCount) return null
|
||||
if (data is ObjectCursor) {
|
||||
val cursor = (data as ObjectCursor).cursor
|
||||
if (!cursor.safeMoveToPosition(dataPosition)) return null
|
||||
val indices = (data as ObjectCursor).indices as ParcelableStatusCursorIndices
|
||||
return cursor.getString(indices.id)
|
||||
}
|
||||
return data!![dataPosition].id
|
||||
return getFieldValue<String?>(position, { cursor, indices ->
|
||||
return@getFieldValue cursor.getString(indices.id)
|
||||
}, { status ->
|
||||
return@getFieldValue status.id
|
||||
}, null)
|
||||
}
|
||||
|
||||
fun getStatusSortId(position: Int): Long {
|
||||
return getFieldValue(position, { cursor, indices ->
|
||||
return@getFieldValue cursor.getLong(indices.sort_id)
|
||||
}, { status ->
|
||||
return@getFieldValue status.sort_id
|
||||
}, -1L)
|
||||
}
|
||||
|
||||
override fun getStatusTimestamp(position: Int): Long {
|
||||
val dataPosition = position - getItemStartPosition(2)
|
||||
if (dataPosition < 0 || dataPosition >= rawStatusCount) return -1
|
||||
if (data is ObjectCursor) {
|
||||
val cursor = (data as ObjectCursor).cursor
|
||||
if (!cursor.safeMoveToPosition(dataPosition)) return -1
|
||||
val indices = (data as ObjectCursor).indices as ParcelableStatusCursorIndices
|
||||
return cursor.getLong(indices.timestamp)
|
||||
}
|
||||
return data!![dataPosition].timestamp
|
||||
return getFieldValue(position, { cursor, indices ->
|
||||
return@getFieldValue cursor.getLong(indices.timestamp)
|
||||
}, { status ->
|
||||
return@getFieldValue status.timestamp
|
||||
}, -1L)
|
||||
}
|
||||
|
||||
override fun getStatusPositionKey(position: Int): Long {
|
||||
val dataPosition = position - getItemStartPosition(2)
|
||||
if (dataPosition < 0 || dataPosition >= rawStatusCount) return -1
|
||||
if (data is ObjectCursor) {
|
||||
val cursor = (data as ObjectCursor).cursor
|
||||
if (!cursor.safeMoveToPosition(dataPosition)) return -1
|
||||
val indices = (data as ObjectCursor).indices as ParcelableStatusCursorIndices
|
||||
return getFieldValue(position, { cursor, indices ->
|
||||
val positionKey = cursor.getLong(indices.position_key)
|
||||
if (positionKey > 0) return positionKey
|
||||
return cursor.getLong(indices.timestamp)
|
||||
}
|
||||
val status = data!![dataPosition]
|
||||
val positionKey = status.position_key
|
||||
if (positionKey > 0) return positionKey
|
||||
return status.timestamp
|
||||
if (positionKey > 0) return@getFieldValue positionKey
|
||||
return@getFieldValue cursor.getLong(indices.timestamp)
|
||||
}, { status ->
|
||||
val positionKey = status.position_key
|
||||
if (positionKey > 0) return@getFieldValue positionKey
|
||||
return@getFieldValue status.timestamp
|
||||
}, -1L)
|
||||
}
|
||||
|
||||
override fun getAccountKey(position: Int): UserKey? {
|
||||
@ -295,7 +288,6 @@ abstract class ParcelableStatusesAdapter(
|
||||
throw IllegalStateException("Unknown view type " + viewType)
|
||||
}
|
||||
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
when (getItemCountIndex(position)) {
|
||||
1 -> {
|
||||
@ -318,6 +310,7 @@ abstract class ParcelableStatusesAdapter(
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected abstract fun onCreateStatusViewHolder(parent: ViewGroup): IStatusViewHolder
|
||||
|
||||
override fun addGapLoadingId(id: ObjectId) {
|
||||
@ -360,7 +353,6 @@ abstract class ParcelableStatusesAdapter(
|
||||
throw AssertionError()
|
||||
}
|
||||
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
val position = loadMoreIndicatorPosition
|
||||
itemCounts[0] = if (position and ILoadMoreSupportAdapter.START != 0L) 1 else 0
|
||||
@ -380,9 +372,28 @@ abstract class ParcelableStatusesAdapter(
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
val statusStartIndex: Int
|
||||
get() = getItemStartPosition(2)
|
||||
|
||||
private inline fun <T> getFieldValue(
|
||||
position: Int,
|
||||
readCursorValueAction: (cursor: Cursor, indices: ParcelableStatusCursorIndices) -> T,
|
||||
readStatusValueAction: (status: ParcelableStatus) -> T,
|
||||
defValue: T
|
||||
): T {
|
||||
val dataPosition = position - getItemStartPosition(2)
|
||||
if (dataPosition < 0 || dataPosition >= rawStatusCount) return defValue
|
||||
if (data is ObjectCursor) {
|
||||
val cursor = (data as ObjectCursor).cursor
|
||||
if (!cursor.safeMoveToPosition(dataPosition)) return defValue
|
||||
val indices = (data as ObjectCursor).indices as ParcelableStatusCursorIndices
|
||||
return readCursorValueAction(cursor, indices)
|
||||
}
|
||||
val status = data!![dataPosition]
|
||||
return readStatusValueAction(status)
|
||||
}
|
||||
|
||||
private fun isFiltered(position: Int): Boolean {
|
||||
if (data is ObjectCursor) return false
|
||||
return getStatus(position)!!.is_filtered
|
||||
@ -393,5 +404,17 @@ abstract class ParcelableStatusesAdapter(
|
||||
const val ITEM_VIEW_TYPE_EMPTY = 3
|
||||
}
|
||||
|
||||
fun findPositionByPositionKey(positionKey: Long): Int {
|
||||
// Assume statuses are descend sorted by id, so break at first status with id
|
||||
// lesser equals than read position
|
||||
return rangeOfSize(statusStartIndex, statusCount - 1).indexOfFirst { positionKey > 0 && getStatusPositionKey(it) <= positionKey }
|
||||
}
|
||||
|
||||
fun findPositionBySortId(sortId: Long): Int {
|
||||
// Assume statuses are descend sorted by id, so break at first status with id
|
||||
// lesser equals than read position
|
||||
return rangeOfSize(statusStartIndex, statusCount - 1).indexOfFirst { sortId > 0 && getStatusSortId(it) <= sortId }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import org.mariotaku.ktextension.toLong
|
||||
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_REMEMBER_POSITION
|
||||
import org.mariotaku.twidere.extension.getNonEmptyString
|
||||
import org.mariotaku.twidere.model.CustomAPIConfig
|
||||
import org.mariotaku.twidere.model.account.cred.Credentials
|
||||
@ -31,6 +32,7 @@ val linkHighlightOptionKey = KStringKey(KEY_LINK_HIGHLIGHT_OPTION, VALUE_LINK_HI
|
||||
val statusShortenerKey = KNullableStringKey(KEY_STATUS_SHORTENER, null)
|
||||
val mediaUploaderKey = KNullableStringKey(KEY_MEDIA_UPLOADER, null)
|
||||
val newDocumentApiKey = KBooleanKey(KEY_NEW_DOCUMENT_API, Build.VERSION.SDK_INT == Build.VERSION_CODES.M)
|
||||
val rememberPositionKey = KBooleanKey(KEY_REMEMBER_POSITION, true)
|
||||
val attachLocationKey = KBooleanKey(KEY_ATTACH_LOCATION, false)
|
||||
val attachPreciseLocationKey = KBooleanKey(KEY_ATTACH_PRECISE_LOCATION, false)
|
||||
val noCloseAfterTweetSentKey = KBooleanKey(KEY_NO_CLOSE_AFTER_TWEET_SENT, false)
|
||||
|
@ -38,7 +38,7 @@ import edu.tsinghua.hotmobi.model.MediaEvent
|
||||
import kotlinx.android.synthetic.main.fragment_content_recyclerview.*
|
||||
import org.mariotaku.kpreferences.get
|
||||
import org.mariotaku.twidere.BuildConfig
|
||||
import org.mariotaku.twidere.Constants.*
|
||||
import org.mariotaku.twidere.Constants.KEY_NEW_DOCUMENT_API
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.TwidereConstants
|
||||
import org.mariotaku.twidere.adapter.ParcelableActivitiesAdapter
|
||||
@ -52,6 +52,7 @@ import org.mariotaku.twidere.annotation.ReadPositionTag
|
||||
import org.mariotaku.twidere.constant.IntentConstants
|
||||
import org.mariotaku.twidere.constant.KeyboardShortcutConstants.*
|
||||
import org.mariotaku.twidere.constant.readFromBottomKey
|
||||
import org.mariotaku.twidere.constant.rememberPositionKey
|
||||
import org.mariotaku.twidere.extension.getAccountType
|
||||
import org.mariotaku.twidere.fragment.AbsStatusesFragment.DefaultOnLikedListener
|
||||
import org.mariotaku.twidere.loader.iface.IExtendedLoader
|
||||
@ -71,7 +72,10 @@ import org.mariotaku.twidere.view.holder.StatusViewHolder
|
||||
import org.mariotaku.twidere.view.holder.iface.IStatusViewHolder
|
||||
import java.util.*
|
||||
|
||||
abstract class AbsActivitiesFragment protected constructor() : AbsContentListRecyclerViewFragment<ParcelableActivitiesAdapter>(), LoaderCallbacks<List<ParcelableActivity>>, ParcelableActivitiesAdapter.ActivityAdapterListener, KeyboardShortcutCallback {
|
||||
abstract class AbsActivitiesFragment protected constructor() :
|
||||
AbsContentListRecyclerViewFragment<ParcelableActivitiesAdapter>(),
|
||||
LoaderCallbacks<List<ParcelableActivity>>, ParcelableActivitiesAdapter.ActivityAdapterListener,
|
||||
KeyboardShortcutCallback {
|
||||
|
||||
private lateinit var activitiesBusCallback: Any
|
||||
private lateinit var navigationHelper: RecyclerViewNavigationHelper
|
||||
@ -193,8 +197,8 @@ abstract class AbsActivitiesFragment protected constructor() : AbsContentListRec
|
||||
}
|
||||
|
||||
override fun onLoadFinished(loader: Loader<List<ParcelableActivity>>, data: List<ParcelableActivity>) {
|
||||
val rememberPosition = preferences.getBoolean(KEY_REMEMBER_POSITION, false)
|
||||
val readFromBottom = preferences.getBoolean(KEY_READ_FROM_BOTTOM, false)
|
||||
val rememberPosition = preferences[rememberPositionKey]
|
||||
val readFromBottom = preferences[readFromBottomKey]
|
||||
var lastReadId: Long
|
||||
val lastVisiblePos: Int
|
||||
val lastVisibleTop: Int
|
||||
|
@ -41,7 +41,9 @@ import org.mariotaku.twidere.util.TwidereColorUtils
|
||||
/**
|
||||
* Created by mariotaku on 15/4/16.
|
||||
*/
|
||||
abstract class AbsContentListViewFragment<A : ListAdapter> : BaseSupportFragment(), OnRefreshListener, RefreshScrollTopInterface, ControlBarOffsetListener, ContentListSupport, AbsListView.OnScrollListener {
|
||||
abstract class AbsContentListViewFragment<A : ListAdapter> : BaseSupportFragment(),
|
||||
OnRefreshListener, RefreshScrollTopInterface, ControlBarOffsetListener, ContentListSupport,
|
||||
AbsListView.OnScrollListener {
|
||||
private lateinit var scrollHandler: ListViewScrollHandler
|
||||
|
||||
override lateinit var adapter: A
|
||||
@ -105,8 +107,8 @@ abstract class AbsContentListViewFragment<A : ListAdapter> : BaseSupportFragment
|
||||
super.onDetach()
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater!!.inflate(R.layout.fragment_content_listview, container, false)
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.fragment_content_listview, container, false)
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
|
@ -37,6 +37,8 @@ import edu.tsinghua.hotmobi.HotMobiLogger
|
||||
import edu.tsinghua.hotmobi.model.MediaEvent
|
||||
import kotlinx.android.synthetic.main.fragment_content_recyclerview.*
|
||||
import org.mariotaku.kpreferences.get
|
||||
import org.mariotaku.ktextension.isNullOrEmpty
|
||||
import org.mariotaku.ktextension.rangeOfSize
|
||||
import org.mariotaku.twidere.BuildConfig
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.TwidereConstants
|
||||
@ -49,6 +51,7 @@ import org.mariotaku.twidere.constant.IntentConstants.*
|
||||
import org.mariotaku.twidere.constant.KeyboardShortcutConstants.*
|
||||
import org.mariotaku.twidere.constant.SharedPreferenceConstants
|
||||
import org.mariotaku.twidere.constant.readFromBottomKey
|
||||
import org.mariotaku.twidere.constant.rememberPositionKey
|
||||
import org.mariotaku.twidere.extension.getAccountType
|
||||
import org.mariotaku.twidere.graphic.like.LikeAnimationDrawable
|
||||
import org.mariotaku.twidere.loader.iface.IExtendedLoader
|
||||
@ -92,23 +95,25 @@ abstract class AbsStatusesFragment protected constructor() :
|
||||
protected abstract val accountKeys: Array<UserKey>
|
||||
|
||||
protected var adapterData: List<ParcelableStatus>?
|
||||
get() {
|
||||
return adapter.getData()
|
||||
}
|
||||
get() = adapter.getData()
|
||||
set(data) {
|
||||
adapter.setData(data)
|
||||
}
|
||||
|
||||
@ReadPositionTag
|
||||
protected open val readPositionTag: String?
|
||||
@ReadPositionTag
|
||||
get() = null
|
||||
|
||||
protected open val readPositionTagWithArguments: String?
|
||||
get() = readPositionTag
|
||||
private val currentReadPositionTag: String?
|
||||
get() {
|
||||
if (readPositionTag == null || tabId < 0) return null
|
||||
return "${readPositionTag}_${tabId}_current"
|
||||
}
|
||||
|
||||
protected open val useSortIdAsReadPosition: Boolean = true
|
||||
|
||||
/**
|
||||
* Used for 'restore position' feature
|
||||
*/
|
||||
protected open val currentReadPositionTag: String?
|
||||
get() = if (readPositionTag == null || tabId < 0) null else "${readPositionTag}_${tabId}_current"
|
||||
|
||||
override val extraContentPadding: Rect
|
||||
get() {
|
||||
@ -121,11 +126,10 @@ abstract class AbsStatusesFragment protected constructor() :
|
||||
|
||||
|
||||
// Fragment life cycles
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
statusesBusCallback = createMessageBusCallback()
|
||||
scrollListener?.reversed = preferences[readFromBottomKey]
|
||||
scrollListener.reversed = preferences[readFromBottomKey]
|
||||
adapter.statusClickListener = this
|
||||
registerForContextMenu(recyclerView)
|
||||
navigationHelper = RecyclerViewNavigationHelper(recyclerView, layoutManager, adapter, this)
|
||||
@ -244,45 +248,58 @@ abstract class AbsStatusesFragment protected constructor() :
|
||||
super.setUserVisibleHint(isVisibleToUser)
|
||||
}
|
||||
|
||||
/**
|
||||
* Statuses loaded, update adapter data & restore load position
|
||||
*
|
||||
* Steps:
|
||||
* 1. Save current read position if not first load (adapter data is not empty)
|
||||
* 1.1 If readFromBottom is true, save position on screen bottom
|
||||
* 2. Change adapter data
|
||||
* 3. Restore adapter data
|
||||
* 3.1 If lastVisible was last item, keep lastVisibleItem position (load more)
|
||||
* 3.2 Else, if readFromBottom is true:
|
||||
* 3.1.1 If position was first, keep lastVisibleItem position (pull refresh)
|
||||
* 3.1.2 Else, keep lastVisibleItem position
|
||||
* 3.2 If readFromBottom is false:
|
||||
* 3.2.1 If position was first, set new position to 0 (pull refresh)
|
||||
* 3.2.2 Else, keep firstVisibleItem position (gap clicked)
|
||||
*/
|
||||
override fun onLoadFinished(loader: Loader<List<ParcelableStatus>?>, data: List<ParcelableStatus>?) {
|
||||
val rememberPosition = preferences.getBoolean(SharedPreferenceConstants.KEY_REMEMBER_POSITION, false)
|
||||
val readFromBottom = preferences.getBoolean(SharedPreferenceConstants.KEY_READ_FROM_BOTTOM, false)
|
||||
var lastReadPositionKey: Long
|
||||
val lastVisiblePos: Int
|
||||
val lastVisibleTop: Int
|
||||
val tag = currentReadPositionTag
|
||||
val layoutManager = layoutManager
|
||||
if (readFromBottom) {
|
||||
lastVisiblePos = layoutManager.findLastVisibleItemPosition()
|
||||
} else {
|
||||
lastVisiblePos = layoutManager.findFirstVisibleItemPosition()
|
||||
}
|
||||
if (lastVisiblePos != RecyclerView.NO_POSITION && lastVisiblePos < adapter.itemCount) {
|
||||
val statusStartIndex = adapter.statusStartIndex
|
||||
val statusEndIndex = statusStartIndex + adapter.statusCount
|
||||
val lastItemIndex = Math.min(statusEndIndex, lastVisiblePos)
|
||||
lastReadPositionKey = adapter.getStatusPositionKey(lastItemIndex)
|
||||
val positionView = layoutManager.findViewByPosition(lastItemIndex)
|
||||
lastVisibleTop = positionView?.top ?: 0
|
||||
} else if (rememberPosition && tag != null) {
|
||||
lastReadPositionKey = readStateManager.getPosition(tag)
|
||||
lastVisibleTop = 0
|
||||
} else {
|
||||
lastReadPositionKey = -1
|
||||
lastVisibleTop = 0
|
||||
}
|
||||
adapterData = data
|
||||
val statusStartIndex = adapter.statusStartIndex
|
||||
// The last status is statusEndExclusiveIndex - 1
|
||||
val statusEndExclusiveIndex = statusStartIndex + adapter.statusCount
|
||||
if (statusEndExclusiveIndex >= 0 && rememberPosition && tag != null) {
|
||||
val lastPositionKey = adapter.getStatusPositionKey(statusEndExclusiveIndex - 1)
|
||||
// Status corresponds to last read id was deleted, use last item id instead
|
||||
if (lastPositionKey != -1L && lastReadPositionKey > 0 && lastReadPositionKey < lastPositionKey) {
|
||||
lastReadPositionKey = lastPositionKey
|
||||
val rememberPosition = preferences[rememberPositionKey]
|
||||
val readPositionTag = currentReadPositionTag
|
||||
val readFromBottom = preferences[readFromBottomKey]
|
||||
val firstLoad = adapterData.isNullOrEmpty()
|
||||
|
||||
var lastReadId: Long = -1
|
||||
var lastReadViewTop: Int = 0
|
||||
var loadMore = false
|
||||
// 1. Save current read position if not first load
|
||||
if (!firstLoad) {
|
||||
val lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition()
|
||||
val statusRange = rangeOfSize(adapter.statusStartIndex, adapter.statusCount - 1)
|
||||
val lastReadPosition = if (readFromBottom) {
|
||||
lastVisibleItemPosition
|
||||
} else {
|
||||
layoutManager.findFirstVisibleItemPosition()
|
||||
}.coerceIn(statusRange)
|
||||
lastReadId = if (useSortIdAsReadPosition) {
|
||||
adapter.getStatusSortId(lastReadPosition)
|
||||
} else {
|
||||
adapter.getStatusPositionKey(lastReadPosition)
|
||||
}
|
||||
lastReadViewTop = layoutManager.findViewByPosition(lastReadPosition)?.top ?: 0
|
||||
loadMore = lastVisibleItemPosition >= statusRange.endInclusive
|
||||
} else if (rememberPosition && readPositionTag != null) {
|
||||
lastReadId = readStateManager.getPosition(readPositionTag)
|
||||
lastReadViewTop = 0
|
||||
}
|
||||
// 2. Change adapter data
|
||||
adapterData = data
|
||||
|
||||
refreshEnabled = true
|
||||
|
||||
var restorePosition = -1
|
||||
|
||||
if (loader !is IExtendedLoader || loader.fromUser) {
|
||||
if (hasMoreData(data)) {
|
||||
adapter.loadMoreSupportedPosition = ILoadMoreSupportAdapter.END
|
||||
@ -291,26 +308,23 @@ abstract class AbsStatusesFragment protected constructor() :
|
||||
adapter.loadMoreSupportedPosition = ILoadMoreSupportAdapter.NONE
|
||||
onHasMoreDataChanged(false)
|
||||
}
|
||||
var pos = -1
|
||||
for (i in statusStartIndex..statusEndExclusiveIndex - 1) {
|
||||
// Assume statuses are descend sorted by id, so break at first status with id
|
||||
// lesser equals than read position
|
||||
if (lastReadPositionKey != -1L && adapter.getStatusPositionKey(i) <= lastReadPositionKey) {
|
||||
pos = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if (pos != -1 && adapter.isStatus(pos) && (readFromBottom || lastVisiblePos != 0)) {
|
||||
if (layoutManager.height == 0) {
|
||||
// RecyclerView has not currently laid out, ignore padding.
|
||||
layoutManager.scrollToPositionWithOffset(pos, lastVisibleTop)
|
||||
} else {
|
||||
layoutManager.scrollToPositionWithOffset(pos, lastVisibleTop - layoutManager.paddingTop)
|
||||
}
|
||||
restorePosition = if (useSortIdAsReadPosition) {
|
||||
adapter.findPositionBySortId(lastReadId)
|
||||
} else {
|
||||
adapter.findPositionByPositionKey(lastReadId)
|
||||
}
|
||||
} else {
|
||||
onHasMoreDataChanged(false)
|
||||
}
|
||||
if (restorePosition != -1 && adapter.isStatus(restorePosition) && (loadMore || readFromBottom)) {
|
||||
if (layoutManager.height == 0) {
|
||||
// RecyclerView has not currently laid out, ignore padding.
|
||||
layoutManager.scrollToPositionWithOffset(restorePosition, lastReadViewTop)
|
||||
} else {
|
||||
layoutManager.scrollToPositionWithOffset(restorePosition, lastReadViewTop - layoutManager.paddingTop)
|
||||
}
|
||||
}
|
||||
|
||||
if (loader is IExtendedLoader) {
|
||||
loader.fromUser = false
|
||||
}
|
||||
|
@ -33,7 +33,9 @@ import org.mariotaku.twidere.view.TabPagerIndicator
|
||||
/**
|
||||
* Created by mariotaku on 16/3/16.
|
||||
*/
|
||||
abstract class AbsToolbarTabPagesFragment : BaseSupportFragment(), RefreshScrollTopInterface, SupportFragmentCallback, IBaseFragment.SystemWindowsInsetsCallback, ControlBarOffsetListener, HideUiOnScroll, OnPageChangeListener, IToolBarSupportFragment, KeyboardShortcutCallback {
|
||||
abstract class AbsToolbarTabPagesFragment : BaseSupportFragment(), RefreshScrollTopInterface,
|
||||
SupportFragmentCallback, IBaseFragment.SystemWindowsInsetsCallback, ControlBarOffsetListener,
|
||||
HideUiOnScroll, OnPageChangeListener, IToolBarSupportFragment, KeyboardShortcutCallback {
|
||||
|
||||
private var pagerAdapter: SupportTabsAdapter? = null
|
||||
override val toolbar: Toolbar
|
||||
|
@ -22,6 +22,7 @@ package org.mariotaku.twidere.fragment
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Rect
|
||||
import android.media.RingtoneManager
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
@ -29,15 +30,14 @@ import android.provider.Settings
|
||||
import android.support.v7.preference.Preference
|
||||
import android.support.v7.preference.PreferenceFragmentCompat
|
||||
import org.mariotaku.kpreferences.KPreferences
|
||||
|
||||
import org.mariotaku.twidere.fragment.iface.IBaseFragment
|
||||
import org.mariotaku.twidere.preference.RingtonePreference
|
||||
import org.mariotaku.twidere.util.KeyboardShortcutsHandler
|
||||
import org.mariotaku.twidere.util.UserColorNameManager
|
||||
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper
|
||||
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class BasePreferenceFragment : PreferenceFragmentCompat() {
|
||||
abstract class BasePreferenceFragment : PreferenceFragmentCompat(), IBaseFragment {
|
||||
private var ringtonePreferenceKey: String? = null
|
||||
|
||||
@Inject
|
||||
@ -47,10 +47,7 @@ abstract class BasePreferenceFragment : PreferenceFragmentCompat() {
|
||||
@Inject
|
||||
lateinit var kPreferences: KPreferences
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
GeneralComponentHelper.build(context).inject(this)
|
||||
}
|
||||
private val actionHelper = IBaseFragment.ActionHelper(this)
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
if (savedInstanceState != null) {
|
||||
@ -59,11 +56,21 @@ abstract class BasePreferenceFragment : PreferenceFragmentCompat() {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle?) {
|
||||
outState!!.putString(EXTRA_RINGTONE_PREFERENCE_KEY, ringtonePreferenceKey)
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
GeneralComponentHelper.build(context).inject(this)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
outState.putString(EXTRA_RINGTONE_PREFERENCE_KEY, ringtonePreferenceKey)
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
override fun onViewStateRestored(savedInstanceState: Bundle?) {
|
||||
super.onViewStateRestored(savedInstanceState)
|
||||
requestFitSystemWindows()
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
when (requestCode) {
|
||||
REQUEST_PICK_RINGTONE -> {
|
||||
@ -109,6 +116,14 @@ abstract class BasePreferenceFragment : PreferenceFragmentCompat() {
|
||||
return super.onPreferenceTreeClick(preference)
|
||||
}
|
||||
|
||||
override fun executeAfterFragmentResumed(action: (IBaseFragment) -> Unit) {
|
||||
actionHelper.executeAfterFragmentResumed(action)
|
||||
}
|
||||
|
||||
override fun fitSystemWindows(insets: Rect) {
|
||||
listView.setPadding(insets.left, insets.top, insets.right, insets.bottom)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val REQUEST_PICK_RINGTONE = 301
|
||||
|
@ -20,12 +20,10 @@
|
||||
package org.mariotaku.twidere.fragment
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Rect
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.Fragment
|
||||
import android.support.v4.text.BidiFormatter
|
||||
import com.squareup.otto.Bus
|
||||
import org.mariotaku.twidere.constant.IntentConstants
|
||||
import org.mariotaku.twidere.fragment.iface.IBaseFragment
|
||||
import org.mariotaku.twidere.util.*
|
||||
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper
|
||||
@ -71,40 +69,6 @@ open class BaseSupportFragment : Fragment(), IBaseFragment {
|
||||
GeneralComponentHelper.build(context!!).inject(this)
|
||||
}
|
||||
|
||||
override val extraConfiguration: Bundle?
|
||||
get() {
|
||||
return null
|
||||
}
|
||||
|
||||
override val tabPosition: Int
|
||||
get() {
|
||||
val args = arguments ?: return -1
|
||||
return args.getInt(IntentConstants.EXTRA_TAB_POSITION, -1)
|
||||
}
|
||||
|
||||
override val tabId: Long
|
||||
get() {
|
||||
val args = arguments ?: return -1L
|
||||
return args.getLong(IntentConstants.EXTRA_TAB_ID, -1L)
|
||||
}
|
||||
|
||||
override fun requestFitSystemWindows() {
|
||||
val activity = activity
|
||||
val parentFragment = parentFragment
|
||||
val callback: IBaseFragment.SystemWindowsInsetsCallback
|
||||
if (parentFragment is IBaseFragment.SystemWindowsInsetsCallback) {
|
||||
callback = parentFragment
|
||||
} else if (activity is IBaseFragment.SystemWindowsInsetsCallback) {
|
||||
callback = activity
|
||||
} else {
|
||||
return
|
||||
}
|
||||
val insets = Rect()
|
||||
if (callback.getSystemWindowsInsets(insets)) {
|
||||
fitSystemWindows(insets)
|
||||
}
|
||||
}
|
||||
|
||||
override fun executeAfterFragmentResumed(action: (IBaseFragment) -> Unit) {
|
||||
actionHelper.executeAfterFragmentResumed(action)
|
||||
}
|
||||
@ -124,8 +88,4 @@ open class BaseSupportFragment : Fragment(), IBaseFragment {
|
||||
DebugModeUtils.watchReferenceLeak(this)
|
||||
}
|
||||
|
||||
protected open fun fitSystemWindows(insets: Rect) {
|
||||
val view = view
|
||||
view?.setPadding(insets.left, insets.top, insets.right, insets.bottom)
|
||||
}
|
||||
}
|
||||
|
@ -69,6 +69,8 @@ abstract class CursorStatusesFragment : AbsStatusesFragment() {
|
||||
set(value) {
|
||||
super.refreshing = value
|
||||
}
|
||||
override val useSortIdAsReadPosition: Boolean
|
||||
get() = false
|
||||
|
||||
override fun onStatusesLoaded(loader: Loader<List<ParcelableStatus>?>, data: List<ParcelableStatus>?) {
|
||||
showContentOrError()
|
||||
|
@ -0,0 +1,14 @@
|
||||
package org.mariotaku.twidere.fragment
|
||||
|
||||
import android.os.Bundle
|
||||
import org.mariotaku.twidere.Constants.SHARED_PREFERENCES_NAME
|
||||
import org.mariotaku.twidere.R
|
||||
|
||||
class FilterSettingsFragment : BasePreferenceFragment() {
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
preferenceManager.sharedPreferencesName = SHARED_PREFERENCES_NAME
|
||||
addPreferencesFromResource(R.xml.preferences_filters)
|
||||
}
|
||||
|
||||
}
|
@ -36,6 +36,8 @@ class FiltersFragment : AbsToolbarTabPagesFragment() {
|
||||
adapter.addTab(cls = FilteredKeywordsFragment::class.java, name = getString(R.string.keywords))
|
||||
adapter.addTab(cls = FilteredSourcesFragment::class.java, name = getString(R.string.sources))
|
||||
adapter.addTab(cls = FilteredLinksFragment::class.java, name = getString(R.string.links))
|
||||
adapter.addTab(cls = FilterSettingsFragment::class.java, name = getString(R.string.settings))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -22,10 +22,7 @@ package org.mariotaku.twidere.fragment
|
||||
import android.accounts.AccountManager
|
||||
import android.app.Activity
|
||||
import android.app.Dialog
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.*
|
||||
import android.graphics.Color
|
||||
import android.graphics.Rect
|
||||
import android.nfc.NdefMessage
|
||||
@ -72,6 +69,7 @@ import kotlinx.android.synthetic.main.adapter_item_status_count_label.view.*
|
||||
import kotlinx.android.synthetic.main.fragment_status.*
|
||||
import kotlinx.android.synthetic.main.header_status_common.view.*
|
||||
import kotlinx.android.synthetic.main.layout_content_fragment_common.*
|
||||
import org.mariotaku.kpreferences.get
|
||||
import org.mariotaku.ktextension.findPositionByItemId
|
||||
import org.mariotaku.microblog.library.MicroBlogException
|
||||
import org.mariotaku.microblog.library.twitter.model.Paging
|
||||
@ -92,6 +90,7 @@ import org.mariotaku.twidere.annotation.AccountType
|
||||
import org.mariotaku.twidere.annotation.Referral
|
||||
import org.mariotaku.twidere.constant.KeyboardShortcutConstants.*
|
||||
import org.mariotaku.twidere.constant.SharedPreferenceConstants
|
||||
import org.mariotaku.twidere.constant.newDocumentApiKey
|
||||
import org.mariotaku.twidere.extension.getAccountType
|
||||
import org.mariotaku.twidere.loader.ConversationLoader
|
||||
import org.mariotaku.twidere.loader.ParcelableStatusLoader
|
||||
@ -1381,7 +1380,7 @@ class StatusFragment : BaseSupportFragment(), LoaderCallbacks<SingleResponse<Par
|
||||
context: Context,
|
||||
manager: MultiSelectManager,
|
||||
private val adapter: StatusAdapter,
|
||||
preferences: SharedPreferencesWrapper
|
||||
preferences: SharedPreferences
|
||||
) : StatusLinkClickHandler(context, manager, preferences) {
|
||||
|
||||
override fun onLinkClick(link: String, orig: String?, accountKey: UserKey?,
|
||||
@ -1397,7 +1396,7 @@ class StatusFragment : BaseSupportFragment(), LoaderCallbacks<SingleResponse<Par
|
||||
private fun expandOrOpenMedia(current: ParcelableMedia) {
|
||||
if (adapter.isDetailMediaExpanded) {
|
||||
IntentUtils.openMedia(adapter.context, adapter.status, current, null,
|
||||
preferences.getBoolean(SharedPreferenceConstants.KEY_NEW_DOCUMENT_API))
|
||||
preferences[newDocumentApiKey])
|
||||
return
|
||||
}
|
||||
adapter.isDetailMediaExpanded = true
|
||||
|
@ -21,16 +21,42 @@ package org.mariotaku.twidere.fragment.iface
|
||||
|
||||
import android.graphics.Rect
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.Fragment
|
||||
import org.mariotaku.twidere.constant.IntentConstants
|
||||
import java.util.*
|
||||
|
||||
interface IBaseFragment {
|
||||
val extraConfiguration: Bundle?
|
||||
get() = null
|
||||
|
||||
val tabPosition: Int
|
||||
get() = (this as Fragment).arguments?.getInt(IntentConstants.EXTRA_TAB_POSITION, -1) ?: -1
|
||||
|
||||
val tabId: Long
|
||||
get() = (this as Fragment).arguments?.getLong(IntentConstants.EXTRA_TAB_ID, -1L) ?: -1L
|
||||
|
||||
fun requestFitSystemWindows()
|
||||
fun requestFitSystemWindows() {
|
||||
val fragment = this as Fragment
|
||||
val activity = fragment.activity
|
||||
val parentFragment = fragment.parentFragment
|
||||
val callback: IBaseFragment.SystemWindowsInsetsCallback
|
||||
if (parentFragment is IBaseFragment.SystemWindowsInsetsCallback) {
|
||||
callback = parentFragment
|
||||
} else if (activity is IBaseFragment.SystemWindowsInsetsCallback) {
|
||||
callback = activity
|
||||
} else {
|
||||
return
|
||||
}
|
||||
val insets = Rect()
|
||||
if (callback.getSystemWindowsInsets(insets)) {
|
||||
fitSystemWindows(insets)
|
||||
}
|
||||
}
|
||||
|
||||
fun fitSystemWindows(insets: Rect) {
|
||||
val fragment = this as Fragment
|
||||
fragment.view?.setPadding(insets.left, insets.top, insets.right, insets.bottom)
|
||||
}
|
||||
|
||||
interface SystemWindowsInsetsCallback {
|
||||
fun getSystemWindowsInsets(insets: Rect): Boolean
|
||||
|
@ -42,6 +42,7 @@ import org.mariotaku.twidere.model.ParcelableStatus
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.model.util.AccountUtils
|
||||
import org.mariotaku.twidere.model.util.ParcelableStatusUtils
|
||||
import org.mariotaku.twidere.task.twitter.GetStatusesTask
|
||||
import org.mariotaku.twidere.util.JsonSerializer
|
||||
import org.mariotaku.twidere.util.SharedPreferencesWrapper
|
||||
import org.mariotaku.twidere.util.TwidereArrayUtils
|
||||
@ -147,18 +148,26 @@ abstract class MicroBlogAPIStatusesLoader(
|
||||
val noRowsDeleted = rowsDeleted == 0
|
||||
val insertGap = minIdx != -1 && (noRowsDeleted || deletedOldGap) && !noItemsBefore
|
||||
&& statuses.size >= loadItemLimit && !loadingMore
|
||||
for (i in 0 until statuses.size) {
|
||||
val status = statuses[i]
|
||||
val item = ParcelableStatusUtils.fromStatus(status, accountKey, insertGap && isGapEnabled && minIdx == i)
|
||||
ParcelableStatusUtils.updateExtraInformation(item, details, userColorNameManager)
|
||||
data.add(item)
|
||||
|
||||
if (statuses.isNotEmpty()) {
|
||||
val firstSortId = statuses.first().sortId
|
||||
val lastSortId = statuses.last().sortId
|
||||
// Get id diff of first and last item
|
||||
val sortDiff = firstSortId - lastSortId
|
||||
for (i in 0 until statuses.size) {
|
||||
val status = statuses[i]
|
||||
val item = ParcelableStatusUtils.fromStatus(status, accountKey, insertGap && isGapEnabled && minIdx == i)
|
||||
item.position_key = GetStatusesTask.getPositionKey(item.timestamp, item.sort_id, lastSortId,
|
||||
sortDiff, i, statuses.size)
|
||||
ParcelableStatusUtils.updateExtraInformation(item, details, userColorNameManager)
|
||||
data.add(item)
|
||||
}
|
||||
}
|
||||
|
||||
val db = TwidereApplication.getInstance(context).sqLiteDatabase
|
||||
val array = data.toTypedArray()
|
||||
var i = 0
|
||||
val size = array.size
|
||||
while (i < size) {
|
||||
for (i in (0 until size)) {
|
||||
val status = array[i]
|
||||
val filtered = shouldFilterStatus(db, status)
|
||||
if (filtered) {
|
||||
@ -168,12 +177,12 @@ abstract class MicroBlogAPIStatusesLoader(
|
||||
status.is_filtered = true
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
if (comparator != null) {
|
||||
Collections.sort(data, comparator)
|
||||
data.sortWith(comparator!!)
|
||||
} else {
|
||||
Collections.sort(data)
|
||||
data.sort()
|
||||
}
|
||||
saveCachedData(data)
|
||||
return ListResponse.getListInstance(CopyOnWriteArrayList(data))
|
||||
|
@ -6,6 +6,8 @@ import android.text.style.URLSpan
|
||||
import org.mariotaku.microblog.library.twitter.model.Status
|
||||
import org.mariotaku.twidere.TwidereConstants.USER_TYPE_FANFOU_COM
|
||||
import org.mariotaku.twidere.model.*
|
||||
import org.mariotaku.twidere.model.ParcelableStatus.FilterFlags
|
||||
import org.mariotaku.twidere.task.twitter.GetStatusesTask
|
||||
import org.mariotaku.twidere.util.HtmlSpanBuilder
|
||||
import org.mariotaku.twidere.util.InternalTwitterContentUtils
|
||||
import org.mariotaku.twidere.util.TwitterContentUtils
|
||||
@ -58,6 +60,13 @@ object ParcelableStatusUtils {
|
||||
result.retweeted_by_user_profile_image = TwitterContentUtils.getProfileImageUrl(retweetUser)
|
||||
|
||||
result.extras.retweeted_external_url = retweetedStatus.inferExternalUrl()
|
||||
|
||||
if (retweetUser.isBlocking) {
|
||||
result.filter_flags = result.filter_flags or FilterFlags.BLOCKING_USER
|
||||
}
|
||||
if (retweetUser.isBlockedBy) {
|
||||
result.filter_flags = result.filter_flags or FilterFlags.BLOCKED_BY_USER
|
||||
}
|
||||
} else {
|
||||
status = orig
|
||||
}
|
||||
@ -96,6 +105,8 @@ object ParcelableStatusUtils {
|
||||
result.quoted_user_profile_image = TwitterContentUtils.getProfileImageUrl(quotedUser)
|
||||
result.quoted_user_is_protected = quotedUser.isProtected
|
||||
result.quoted_user_is_verified = quotedUser.isVerified
|
||||
} else {
|
||||
result.filter_flags = result.filter_flags or FilterFlags.QUOTE_NOT_AVAILABLE
|
||||
}
|
||||
|
||||
result.reply_count = status.replyCount
|
||||
@ -151,6 +162,7 @@ object ParcelableStatusUtils {
|
||||
result.place_full_name = getPlaceFullName(status)
|
||||
result.card_name = if (result.card != null) result.card!!.name else null
|
||||
result.lang = status.lang
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,61 @@
|
||||
package org.mariotaku.twidere.util
|
||||
|
||||
import android.widget.AbsListView
|
||||
|
||||
import org.mariotaku.twidere.util.support.ViewSupport
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/3/1.
|
||||
*/
|
||||
class ListViewScrollHandler(
|
||||
contentListSupport: ContentScrollHandler.ContentListSupport,
|
||||
viewCallback: ContentScrollHandler.ViewCallback?
|
||||
) : ContentScrollHandler(contentListSupport, viewCallback), AbsListView.OnScrollListener,
|
||||
ListScrollDistanceCalculator.ScrollDistanceListener {
|
||||
private val calculator: ListScrollDistanceCalculator
|
||||
var onScrollListener: AbsListView.OnScrollListener? = null
|
||||
private var dy: Int = 0
|
||||
private var oldState = AbsListView.OnScrollListener.SCROLL_STATE_IDLE
|
||||
|
||||
init {
|
||||
calculator = ListScrollDistanceCalculator()
|
||||
}
|
||||
|
||||
override fun onScrollStateChanged(view: AbsListView, scrollState: Int) {
|
||||
calculator.onScrollStateChanged(view, scrollState)
|
||||
calculator.setScrollDistanceListener(this)
|
||||
handleScrollStateChanged(scrollState, AbsListView.OnScrollListener.SCROLL_STATE_IDLE)
|
||||
if (onScrollListener != null) {
|
||||
onScrollListener!!.onScrollStateChanged(view, scrollState)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onScroll(view: AbsListView, firstVisibleItem: Int, visibleItemCount: Int, totalItemCount: Int) {
|
||||
calculator.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount)
|
||||
val scrollState = scrollState
|
||||
handleScroll(dy, scrollState, oldState, AbsListView.OnScrollListener.SCROLL_STATE_IDLE)
|
||||
if (onScrollListener != null) {
|
||||
onScrollListener!!.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount)
|
||||
}
|
||||
}
|
||||
|
||||
val totalScrollDistance: Int
|
||||
get() = calculator.totalScrollDistance
|
||||
|
||||
override fun onScrollDistanceChanged(delta: Int, total: Int) {
|
||||
dy = -delta
|
||||
val scrollState = scrollState
|
||||
handleScroll(dy, scrollState, oldState, AbsListView.OnScrollListener.SCROLL_STATE_IDLE)
|
||||
oldState = scrollState
|
||||
}
|
||||
|
||||
class ListViewCallback(private val listView: AbsListView) : ContentScrollHandler.ViewCallback {
|
||||
|
||||
override val computingLayout: Boolean
|
||||
get() = ViewSupport.isInLayout(listView)
|
||||
|
||||
override fun post(runnable: Runnable) {
|
||||
listView.post(runnable)
|
||||
}
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ package org.mariotaku.twidere.util
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import android.os.BadParcelableException
|
||||
import android.support.customtabs.CustomTabsIntent
|
||||
@ -30,11 +31,12 @@ import edu.tsinghua.hotmobi.model.LinkEvent
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import org.mariotaku.chameleon.Chameleon
|
||||
import org.mariotaku.chameleon.ChameleonUtils
|
||||
import org.mariotaku.kpreferences.get
|
||||
import org.mariotaku.twidere.activity.WebLinkHandlerActivity
|
||||
import org.mariotaku.twidere.annotation.Referral
|
||||
import org.mariotaku.twidere.app.TwidereApplication
|
||||
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_ACCOUNT_KEY
|
||||
import org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_NEW_DOCUMENT_API
|
||||
import org.mariotaku.twidere.constant.newDocumentApiKey
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.model.util.ParcelableMediaUtils
|
||||
import org.mariotaku.twidere.util.TwidereLinkify.OnLinkClickListener
|
||||
@ -43,7 +45,7 @@ import org.mariotaku.twidere.util.media.preview.PreviewMediaExtractor
|
||||
open class OnLinkClickHandler(
|
||||
protected val context: Context,
|
||||
protected val manager: MultiSelectManager?,
|
||||
protected val preferences: SharedPreferencesWrapper
|
||||
protected val preferences: SharedPreferences
|
||||
) : OnLinkClickListener {
|
||||
|
||||
override fun onLinkClick(link: String, orig: String?, accountKey: UserKey?,
|
||||
@ -60,8 +62,7 @@ open class OnLinkClickHandler(
|
||||
when (type) {
|
||||
TwidereLinkify.LINK_TYPE_MENTION -> {
|
||||
IntentUtils.openUserProfile(context, accountKey, null, link, null,
|
||||
preferences.getBoolean(KEY_NEW_DOCUMENT_API),
|
||||
Referral.USER_MENTION)
|
||||
preferences[newDocumentApiKey], Referral.USER_MENTION)
|
||||
return true
|
||||
}
|
||||
TwidereLinkify.LINK_TYPE_HASHTAG -> {
|
||||
@ -101,7 +102,7 @@ open class OnLinkClickHandler(
|
||||
}
|
||||
val screenName = orig.substring(1, length)
|
||||
IntentUtils.openUserProfile(context, accountKey, UserKey.valueOf(id),
|
||||
screenName, null, preferences.getBoolean(KEY_NEW_DOCUMENT_API),
|
||||
screenName, null, preferences[newDocumentApiKey],
|
||||
Referral.USER_MENTION)
|
||||
return true
|
||||
}
|
||||
@ -137,7 +138,7 @@ open class OnLinkClickHandler(
|
||||
}
|
||||
TwidereLinkify.LINK_TYPE_USER_ID -> {
|
||||
IntentUtils.openUserProfile(context, accountKey, UserKey.valueOf(link), null, null,
|
||||
preferences.getBoolean(KEY_NEW_DOCUMENT_API),
|
||||
preferences[newDocumentApiKey],
|
||||
Referral.USER_MENTION)
|
||||
return true
|
||||
}
|
||||
@ -155,7 +156,7 @@ open class OnLinkClickHandler(
|
||||
protected open fun openMedia(accountKey: UserKey, extraId: Long, sensitive: Boolean, link: String, start: Int, end: Int) {
|
||||
val media = arrayOf(ParcelableMediaUtils.image(link))
|
||||
IntentUtils.openMedia(context, accountKey, sensitive, null, media, null,
|
||||
preferences.getBoolean(KEY_NEW_DOCUMENT_API))
|
||||
preferences[newDocumentApiKey])
|
||||
}
|
||||
|
||||
protected open fun openLink(link: String) {
|
||||
|
@ -21,9 +21,10 @@ package org.mariotaku.twidere.util
|
||||
|
||||
import android.content.Context
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import org.mariotaku.kpreferences.get
|
||||
import org.mariotaku.twidere.Constants
|
||||
import org.mariotaku.twidere.adapter.iface.IStatusesAdapter
|
||||
import org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_NEW_DOCUMENT_API
|
||||
import org.mariotaku.twidere.constant.newDocumentApiKey
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.model.util.ParcelableMediaUtils
|
||||
|
||||
@ -43,7 +44,7 @@ class StatusAdapterLinkClickHandler<D>(context: Context, preferences: SharedPref
|
||||
if (current != null && current.open_browser) {
|
||||
openLink(link)
|
||||
} else {
|
||||
val newDocument = preferences.getBoolean(KEY_NEW_DOCUMENT_API)
|
||||
val newDocument = preferences[newDocumentApiKey]
|
||||
IntentUtils.openMedia(context, status, current, null, newDocument)
|
||||
}
|
||||
}
|
||||
|
@ -20,8 +20,10 @@
|
||||
package org.mariotaku.twidere.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import org.mariotaku.kpreferences.get
|
||||
import org.mariotaku.twidere.Constants
|
||||
import org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_NEW_DOCUMENT_API
|
||||
import org.mariotaku.twidere.constant.newDocumentApiKey
|
||||
import org.mariotaku.twidere.model.ParcelableMedia
|
||||
import org.mariotaku.twidere.model.ParcelableStatus
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
@ -32,7 +34,7 @@ import org.mariotaku.twidere.model.UserKey
|
||||
open class StatusLinkClickHandler(
|
||||
context: Context,
|
||||
manager: MultiSelectManager,
|
||||
preferences: SharedPreferencesWrapper
|
||||
preferences: SharedPreferences
|
||||
) : OnLinkClickHandler(context, manager, preferences), Constants {
|
||||
|
||||
var status: ParcelableStatus? = null
|
||||
@ -44,8 +46,7 @@ open class StatusLinkClickHandler(
|
||||
if (current == null || current.open_browser) {
|
||||
openLink(link)
|
||||
} else {
|
||||
IntentUtils.openMedia(context, status, current, null,
|
||||
preferences.getBoolean(KEY_NEW_DOCUMENT_API))
|
||||
IntentUtils.openMedia(context, status, current, null, preferences[newDocumentApiKey])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -827,4 +827,5 @@
|
||||
<string name="action_block">Block</string>
|
||||
<string name="status_format_time_source"><xliff:g example="0:00, Jan 1 2017" id="time">%1$s</xliff:g> · <xliff:g example="Twidere for Android" id="source">%2$s</xliff:g></string>
|
||||
<string name="status_format_source"><xliff:g example="Twidere for Android" id="source">%s</xliff:g></string>
|
||||
<string name="preference_filter_unavailable_quote_statuses">Filter unavailable quotes</string>
|
||||
</resources>
|
7
twidere/src/main/res/xml/preferences_filters.xml
Normal file
7
twidere/src/main/res/xml/preferences_filters.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="filter_unavailable_quote_statuses"
|
||||
android:title="@string/preference_filter_unavailable_quote_statuses"/>
|
||||
</PreferenceScreen>
|
Loading…
x
Reference in New Issue
Block a user