Twidere-App-Android-Twitter.../twidere/src/main/kotlin/org/mariotaku/twidere/fragment/AbsStatusesFragment.kt

755 lines
32 KiB
Kotlin
Raw Normal View History

2016-06-29 15:47:52 +02:00
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.fragment
2016-12-18 06:21:24 +01:00
import android.accounts.AccountManager
import android.app.Activity
import android.content.ContentValues
2016-06-29 15:47:52 +02:00
import android.content.Context
import android.content.Intent
import android.graphics.Rect
2020-06-02 11:15:34 +02:00
import android.net.Uri
2016-06-29 15:47:52 +02:00
import android.os.Bundle
2020-01-26 08:35:15 +01:00
import androidx.annotation.CallSuper
import androidx.fragment.app.Fragment
import androidx.loader.app.LoaderManager.LoaderCallbacks
import androidx.loader.content.Loader
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.OnScrollListener
2016-06-29 15:47:52 +02:00
import android.view.*
import com.squareup.otto.Subscribe
import kotlinx.android.synthetic.main.fragment_content_recyclerview.*
2016-12-16 01:38:37 +01:00
import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.*
import org.mariotaku.sqliteqb.library.Expression
2016-06-29 15:47:52 +02:00
import org.mariotaku.twidere.R
import org.mariotaku.twidere.activity.AccountSelectorActivity
import org.mariotaku.twidere.activity.ComposeActivity
2016-06-29 15:47:52 +02:00
import org.mariotaku.twidere.adapter.ParcelableStatusesAdapter
import org.mariotaku.twidere.adapter.decorator.ExtendedDividerItemDecoration
2016-06-29 15:47:52 +02:00
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter
import org.mariotaku.twidere.annotation.ReadPositionTag
2017-04-12 10:17:20 +02:00
import org.mariotaku.twidere.constant.*
2016-08-19 16:25:27 +02:00
import org.mariotaku.twidere.constant.IntentConstants.*
import org.mariotaku.twidere.constant.KeyboardShortcutConstants.*
2016-12-29 06:50:18 +01:00
import org.mariotaku.twidere.extension.model.getAccountType
import org.mariotaku.twidere.fragment.status.FavoriteConfirmDialogFragment
import org.mariotaku.twidere.fragment.status.RetweetQuoteDialogFragment
2016-06-29 15:47:52 +02:00
import org.mariotaku.twidere.graphic.like.LikeAnimationDrawable
import org.mariotaku.twidere.loader.iface.IExtendedLoader
import org.mariotaku.twidere.model.*
2016-12-18 06:21:24 +01:00
import org.mariotaku.twidere.model.analyzer.Share
2017-02-09 09:00:12 +01:00
import org.mariotaku.twidere.model.event.StatusListChangedEvent
2017-04-21 18:32:03 +02:00
import org.mariotaku.twidere.model.pagination.SinceMaxPagination
import org.mariotaku.twidere.model.timeline.TimelineFilter
2016-12-18 06:21:24 +01:00
import org.mariotaku.twidere.model.util.AccountUtils
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
2016-06-29 15:47:52 +02:00
import org.mariotaku.twidere.util.*
import org.mariotaku.twidere.util.KeyboardShortcutsHandler.KeyboardShortcutCallback
2017-03-07 15:13:59 +01:00
import org.mariotaku.twidere.util.glide.PauseRecyclerViewOnScrollListener
import org.mariotaku.twidere.util.sync.SyncTaskRunner
2016-06-29 15:47:52 +02:00
import org.mariotaku.twidere.view.ExtendedRecyclerView
import org.mariotaku.twidere.view.holder.GapViewHolder
import org.mariotaku.twidere.view.holder.StatusViewHolder
import org.mariotaku.twidere.view.holder.iface.IStatusViewHolder
/**
* Created by mariotaku on 14/11/5.
*/
abstract class AbsStatusesFragment : AbsContentListRecyclerViewFragment<ParcelableStatusesAdapter>(),
2016-08-19 16:25:27 +02:00
LoaderCallbacks<List<ParcelableStatus>?>, IStatusViewHolder.StatusClickListener,
KeyboardShortcutCallback {
2016-06-29 15:47:52 +02:00
2016-12-16 01:38:37 +01:00
private lateinit var statusesBusCallback: Any
private lateinit var navigationHelper: RecyclerViewNavigationHelper
private var pauseOnScrollListener: OnScrollListener? = null
var loaderInitialized: Boolean = false
private set
2016-06-29 15:47:52 +02:00
private val onScrollListener = object : OnScrollListener() {
2020-01-26 08:35:15 +01:00
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
2016-06-29 15:47:52 +02:00
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
val layoutManager = layoutManager
2016-09-01 04:43:59 +02:00
saveReadPosition(layoutManager.findFirstVisibleItemPosition())
2016-06-29 15:47:52 +02:00
}
}
}
2016-08-19 16:25:27 +02:00
protected abstract val accountKeys: Array<UserKey>
protected var adapterData: List<ParcelableStatus>?
get() = adapter.getData()
2016-08-19 16:25:27 +02:00
set(data) {
adapter.setData(data)
2016-08-19 16:25:27 +02:00
}
@ReadPositionTag
2016-08-19 16:25:27 +02:00
protected open val readPositionTag: String?
get() = null
2017-04-16 10:47:02 +02:00
protected open val timelineSyncTag: String?
get() = null
2016-08-19 16:25:27 +02:00
protected open val readPositionTagWithArguments: String?
get() = readPositionTag
protected open val useSortIdAsReadPosition: Boolean = true
/**
* Used for 'restore position' feature
*/
protected open val currentReadPositionTag: String?
get() {
val positionTag = readPositionTagWithArguments ?: readPositionTag ?: return null
return if (tabId < 0) null else "${positionTag}_${tabId}_current"
}
2016-08-19 16:25:27 +02:00
override val extraContentPadding: Rect
get() {
val paddingVertical = resources.getDimensionPixelSize(R.dimen.element_spacing_small)
return Rect(0, paddingVertical, 0, paddingVertical)
}
val shouldInitLoader: Boolean
get() = (parentFragment as? StatusesFragmentDelegate)?.shouldInitLoader ?: true
2016-06-29 15:47:52 +02:00
protected open val enableTimelineFilter: Boolean = false
protected open val timelineFilter: TimelineFilter? = null
2017-04-13 09:53:22 +02:00
protected open val loaderId: Int
get() = tabId.toInt().coerceIn(0..Int.MAX_VALUE)
2016-08-19 16:25:27 +02:00
// Fragment life cycles
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
2016-12-16 01:38:37 +01:00
statusesBusCallback = createMessageBusCallback()
scrollListener.reversed = preferences[readFromBottomKey]
2016-08-19 16:25:27 +02:00
adapter.statusClickListener = this
registerForContextMenu(recyclerView)
navigationHelper = RecyclerViewNavigationHelper(recyclerView, layoutManager, adapter, this)
pauseOnScrollListener = PauseRecyclerViewOnScrollListener(
pauseOnScroll = false, pauseOnFling = false,
requestManager = requestManager
)
2016-08-19 16:25:27 +02:00
if (shouldInitLoader) {
initLoaderIfNeeded()
}
showProgress()
}
override fun onStart() {
super.onStart()
recyclerView.addOnScrollListener(onScrollListener)
2020-01-26 08:35:15 +01:00
pauseOnScrollListener?.let { recyclerView.addOnScrollListener(it) }
2016-08-19 16:25:27 +02:00
bus.register(statusesBusCallback)
}
override fun onStop() {
bus.unregister(statusesBusCallback)
2020-01-26 08:35:15 +01:00
pauseOnScrollListener?.let { recyclerView.removeOnScrollListener(it) }
2016-08-19 16:25:27 +02:00
recyclerView.removeOnScrollListener(onScrollListener)
if (userVisibleHint) {
saveReadPosition()
}
super.onStop()
}
override fun onDestroy() {
adapter.statusClickListener = null
2016-08-19 16:25:27 +02:00
super.onDestroy()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
REQUEST_FAVORITE_SELECT_ACCOUNT, REQUEST_RETWEET_SELECT_ACCOUNT -> {
handleActionActivityResult(this, requestCode, resultCode, data)
}
}
}
2016-06-29 15:47:52 +02:00
abstract fun getStatuses(param: RefreshTaskParam): Boolean
override fun handleKeyboardShortcutSingle(handler: KeyboardShortcutsHandler, keyCode: Int, event: KeyEvent, metaState: Int): Boolean {
2016-08-19 16:25:27 +02:00
var action = handler.getKeyAction(CONTEXT_TAG_NAVIGATION, keyCode, event, metaState)
if (ACTION_NAVIGATION_REFRESH == action) {
2016-06-29 15:47:52 +02:00
triggerRefresh()
return true
}
val focusedChild = RecyclerViewUtils.findRecyclerViewChild(recyclerView,
layoutManager.focusedChild)
var position = -1
if (focusedChild != null && focusedChild.parent === recyclerView) {
position = recyclerView.getChildLayoutPosition(focusedChild)
}
if (position != -1) {
val status = adapter.getStatus(position)
2016-06-29 15:47:52 +02:00
if (keyCode == KeyEvent.KEYCODE_ENTER) {
2020-01-26 08:35:15 +01:00
activity?.let {
IntentUtils.openStatus(it, status, null)
}
2016-06-29 15:47:52 +02:00
return true
}
if (action == null) {
2016-08-19 16:25:27 +02:00
action = handler.getKeyAction(CONTEXT_TAG_STATUS, keyCode, event, metaState)
2016-06-29 15:47:52 +02:00
}
if (action == null) return false
2017-04-12 10:17:20 +02:00
return handleKeyboardShortcutAction(this, action, status, position)
2016-06-29 15:47:52 +02:00
}
2016-12-16 01:38:37 +01:00
return navigationHelper.handleKeyboardShortcutSingle(handler, keyCode, event, metaState)
2016-06-29 15:47:52 +02:00
}
override fun isKeyboardShortcutHandled(handler: KeyboardShortcutsHandler, keyCode: Int, event: KeyEvent, metaState: Int): Boolean {
2016-08-19 16:25:27 +02:00
var action = handler.getKeyAction(CONTEXT_TAG_NAVIGATION, keyCode, event, metaState)
if (ACTION_NAVIGATION_REFRESH == action) {
2016-06-29 15:47:52 +02:00
return true
}
if (action == null) {
2016-08-19 16:25:27 +02:00
action = handler.getKeyAction(CONTEXT_TAG_STATUS, keyCode, event, metaState)
2016-06-29 15:47:52 +02:00
}
if (action == null) return false
when (action) {
2016-08-19 16:25:27 +02:00
ACTION_STATUS_REPLY, ACTION_STATUS_RETWEET, ACTION_STATUS_FAVORITE -> return true
2016-06-29 15:47:52 +02:00
}
2016-12-16 01:38:37 +01:00
return navigationHelper.isKeyboardShortcutHandled(handler, keyCode, event, metaState)
2016-06-29 15:47:52 +02:00
}
override fun handleKeyboardShortcutRepeat(handler: KeyboardShortcutsHandler, keyCode: Int, repeatCount: Int,
2017-02-13 17:44:56 +01:00
event: KeyEvent, metaState: Int): Boolean {
2016-12-16 01:38:37 +01:00
return navigationHelper.handleKeyboardShortcutRepeat(handler, keyCode, repeatCount, event, metaState)
2016-06-29 15:47:52 +02:00
}
2020-01-26 08:35:15 +01:00
override fun onCreateLoader(id: Int, args: Bundle?): Loader<List<ParcelableStatus>?> {
val fromUser = args?.getBoolean(EXTRA_FROM_USER)
args?.remove(EXTRA_FROM_USER)
return onCreateStatusesLoader(requireActivity(), args!!, fromUser!!)
2016-06-29 15:47:52 +02:00
}
2016-08-23 03:46:14 +02:00
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
if (userVisibleHint && !isVisibleToUser && host != null) {
saveReadPosition()
}
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)
*/
2016-06-29 15:47:52 +02:00
override fun onLoadFinished(loader: Loader<List<ParcelableStatus>?>, data: List<ParcelableStatus>?) {
val rememberPosition = preferences[rememberPositionKey]
2017-04-23 18:40:32 +02:00
val currentReadPositionTag = currentReadPositionTag
val readFromBottom = preferences[readFromBottomKey]
val firstLoad = adapterData.isNullOrEmpty()
var lastReadId: Long = -1
var lastReadViewTop = 0
var loadMore = false
var wasAtTop = false
// 1. Save current read position if not first load
if (!firstLoad) {
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
val lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition()
wasAtTop = firstVisibleItemPosition == 0
// Get display range of statuses
val statusRange = rangeOfSize(adapter.statusStartIndex, adapter.getStatusCount(raw = false))
2017-02-02 15:20:14 +01:00
val lastReadPosition = if (loadMore || readFromBottom) {
lastVisibleItemPosition
} else {
firstVisibleItemPosition
2017-01-31 05:04:36 +01:00
}.coerceInOr(statusRange, -1)
2020-06-09 02:21:48 +02:00
lastReadId = when {
lastReadPosition < 0 -> {
-1
}
useSortIdAsReadPosition -> {
adapter.getStatusSortId(lastReadPosition, false)
}
else -> {
adapter.getStatusPositionKey(lastReadPosition)
}
2016-06-29 15:47:52 +02:00
}
lastReadViewTop = layoutManager.findViewByPosition(lastReadPosition)?.top ?: 0
2020-06-08 23:07:20 +02:00
loadMore = statusRange.last in 0..lastVisibleItemPosition
2017-04-23 18:40:32 +02:00
} else if (rememberPosition) {
val syncManager = timelineSyncManager
val positionTag = this.readPositionTag
val syncTag = this.timelineSyncTag
val currentTag = this.currentReadPositionTag
if (syncManager != null && positionTag != null && syncTag != null &&
syncPreferences.isSyncEnabled(SyncTaskRunner.SYNC_TYPE_TIMELINE_POSITIONS)) {
2017-04-23 18:40:32 +02:00
lastReadId = syncManager.peekPosition(positionTag, syncTag)
}
if (lastReadId <= 0 && currentTag != null) {
lastReadId = readStateManager.getPosition(currentTag)
}
lastReadViewTop = 0
2016-06-29 15:47:52 +02:00
}
// 2. Change adapter data
adapterData = data
adapter.timelineFilter = timelineFilter
2016-07-03 08:42:28 +02:00
refreshEnabled = true
var restorePosition = -1
2016-08-13 16:04:31 +02:00
if (loader !is IExtendedLoader || loader.fromUser) {
if (hasMoreData(loader, data)) {
2016-06-29 15:47:52 +02:00
adapter.loadMoreSupportedPosition = ILoadMoreSupportAdapter.END
onHasMoreDataChanged(true)
} else {
adapter.loadMoreSupportedPosition = ILoadMoreSupportAdapter.NONE
onHasMoreDataChanged(false)
}
restorePosition = if (useSortIdAsReadPosition) {
adapter.findPositionBySortId(lastReadId)
} else {
adapter.findPositionByPositionKey(lastReadId)
2016-06-29 15:47:52 +02:00
}
} else {
onHasMoreDataChanged(false)
}
2017-02-02 15:20:14 +01:00
if (loadMore) {
restorePosition += 1
restorePosition.coerceInOr(0 until layoutManager.itemCount, -1)
}
if (restorePosition != -1 && adapter.isStatus(restorePosition) && (loadMore || !wasAtTop
|| readFromBottom || (rememberPosition && firstLoad))) {
if (layoutManager.height == 0) {
// RecyclerView has not currently laid out, ignore padding.
layoutManager.scrollToPositionWithOffset(restorePosition, lastReadViewTop)
} else {
layoutManager.scrollToPositionWithOffset(restorePosition, lastReadViewTop - layoutManager.paddingTop)
}
}
2016-06-29 15:47:52 +02:00
if (loader is IExtendedLoader) {
2016-08-13 16:04:31 +02:00
loader.fromUser = false
2016-06-29 15:47:52 +02:00
}
onStatusesLoaded(loader, data)
}
override fun onLoaderReset(loader: Loader<List<ParcelableStatus>?>) {
if (loader is IExtendedLoader) {
2016-08-13 16:04:31 +02:00
loader.fromUser = false
2016-06-29 15:47:52 +02:00
}
}
2016-08-19 16:25:27 +02:00
2016-06-29 15:47:52 +02:00
override fun onGapClick(holder: GapViewHolder, position: Int) {
val status = adapter.getStatus(position)
2017-02-20 16:13:48 +01:00
DebugLog.v(msg = "Load activity gap $status")
2016-12-06 04:08:56 +01:00
adapter.addGapLoadingId(ObjectId(status.account_key, status.id))
2017-04-12 14:58:08 +02:00
val accountKeys = arrayOf(status.account_key)
2017-04-21 18:32:03 +02:00
val pagination = arrayOf(SinceMaxPagination.maxId(status.id, status.sort_id))
getStatuses(BaseRefreshTaskParam(accountKeys, pagination))
2016-06-29 15:47:52 +02:00
}
2017-02-28 03:37:03 +01:00
override fun onMediaClick(holder: IStatusViewHolder, view: View, current: ParcelableMedia,
statusPosition: Int) {
val status = adapter.getStatus(statusPosition)
2020-01-26 08:35:15 +01:00
activity?.let {
IntentUtils.openMedia(it, status, current, preferences[newDocumentApiKey],
preferences[displaySensitiveContentsKey])
}
2017-02-28 03:37:03 +01:00
}
override fun onQuotedMediaClick(holder: IStatusViewHolder, view: View, current: ParcelableMedia,
statusPosition: Int) {
val status = adapter.getStatus(statusPosition)
2017-02-28 03:37:03 +01:00
val quotedMedia = status.quoted_media ?: return
2020-01-26 08:35:15 +01:00
activity?.let {
IntentUtils.openMedia(it, status.account_key, status.is_possibly_sensitive, status,
current, quotedMedia, preferences[newDocumentApiKey], preferences[displaySensitiveContentsKey])
}
2016-06-29 15:47:52 +02:00
}
override fun onItemActionClick(holder: RecyclerView.ViewHolder, id: Int, position: Int) {
2017-09-15 11:58:29 +02:00
val status = getFullStatus(position) ?: return
2017-04-12 10:17:20 +02:00
handleActionClick(this@AbsStatusesFragment, id, status, holder as StatusViewHolder)
}
override fun onItemActionLongClick(holder: RecyclerView.ViewHolder, id: Int, position: Int): Boolean {
2017-09-15 11:58:29 +02:00
val status = getFullStatus(position) ?: return false
return handleActionLongClick(this, status, adapter.getItemId(position), id)
2016-06-29 15:47:52 +02:00
}
2017-04-21 11:23:55 +02:00
override fun onCreateItemDecoration(context: Context, recyclerView: RecyclerView, layoutManager: LinearLayoutManager): RecyclerView.ItemDecoration? {
val itemDecoration = ExtendedDividerItemDecoration(context, (recyclerView.layoutManager as LinearLayoutManager).orientation)
2016-06-29 15:47:52 +02:00
val res = context.resources
if (adapter.profileImageEnabled) {
2016-06-29 15:47:52 +02:00
val decorPaddingLeft = res.getDimensionPixelSize(R.dimen.element_spacing_normal) * 2 + res.getDimensionPixelSize(R.dimen.icon_size_status_profile_image)
itemDecoration.setPadding { position, rect ->
val itemViewType = adapter.getItemViewType(position)
var nextItemIsStatus = false
if (position < adapter.itemCount - 1) {
2017-02-28 07:35:31 +01:00
nextItemIsStatus = adapter.getItemViewType(position + 1) == ParcelableStatusesAdapter.VIEW_TYPE_STATUS
2016-06-29 15:47:52 +02:00
}
2017-02-28 07:35:31 +01:00
if (nextItemIsStatus && itemViewType == ParcelableStatusesAdapter.VIEW_TYPE_STATUS) {
2016-06-29 15:47:52 +02:00
rect.left = decorPaddingLeft
} else {
rect.left = 0
}
true
}
}
itemDecoration.setDecorationEndOffset(1)
return itemDecoration
}
protected fun saveReadPosition() {
2016-12-16 01:38:37 +01:00
saveReadPosition(layoutManager.findFirstVisibleItemPosition())
2016-06-29 15:47:52 +02:00
}
protected open fun onHasMoreDataChanged(hasMoreData: Boolean) {
}
override fun onStatusClick(holder: IStatusViewHolder, position: Int) {
2017-09-15 09:32:20 +02:00
val status = getFullStatus(position) ?: return
2020-01-26 08:35:15 +01:00
activity?.let {
IntentUtils.openStatus(it, status, null)
}
2017-01-17 18:59:44 +01:00
}
override fun onQuotedStatusClick(holder: IStatusViewHolder, position: Int) {
val status = adapter.getStatus(position)
2017-01-25 05:15:54 +01:00
val quotedId = status.quoted_id ?: return
2020-01-26 08:35:15 +01:00
activity?.let {
IntentUtils.openStatus(it, status.account_key, quotedId)
}
2016-06-29 15:47:52 +02:00
}
override fun onStatusLongClick(holder: IStatusViewHolder, position: Int): Boolean {
//TODO handle long click event
return true
}
override fun onItemMenuClick(holder: RecyclerView.ViewHolder, menuView: View, position: Int) {
if (activity == null) return
val view = layoutManager.findViewByPosition(position) ?: return
2016-06-29 15:47:52 +02:00
recyclerView.showContextMenuForChild(view)
}
override fun onUserProfileClick(holder: IStatusViewHolder, position: Int) {
val status = adapter.getStatus(position)
2017-01-20 15:08:42 +01:00
val intent = IntentUtils.userProfile(status.account_key, status.user_key,
2017-08-28 06:31:19 +02:00
status.user_screen_name, status.extras?.user_statusnet_profile_url)
2017-01-20 15:08:42 +01:00
IntentUtils.applyNewDocument(intent, preferences[newDocumentApiKey])
2016-06-29 15:47:52 +02:00
startActivity(intent)
}
2020-06-02 11:15:34 +02:00
override fun onLinkClick(holder: IStatusViewHolder, position: Int) {
val status = adapter.getStatus(position)
val url = status.extras?.entities_url?.firstOrNull()
OnLinkClickHandler.openLink(requireContext(), preferences, Uri.parse(url))
}
2016-06-29 15:47:52 +02:00
override fun scrollToStart(): Boolean {
val result = super.scrollToStart()
if (result) {
saveReadPosition(0)
}
return result
}
2016-08-19 16:25:27 +02:00
fun initLoaderIfNeeded() {
if (isDetached || host == null || loaderInitialized) return
2016-06-29 15:47:52 +02:00
val loaderArgs = Bundle(arguments)
2016-08-19 16:25:27 +02:00
loaderArgs.putBoolean(EXTRA_FROM_USER, true)
2017-04-13 09:53:22 +02:00
loaderManager.initLoader(loaderId, loaderArgs, this)
2016-08-19 16:25:27 +02:00
loaderInitialized = true
2016-06-29 15:47:52 +02:00
}
protected open fun createMessageBusCallback(): Any {
return StatusesBusCallback()
}
2017-04-27 17:56:54 +02:00
@CallSuper
protected open fun saveReadPosition(position: Int) {
if (host == null) return
if (position == RecyclerView.NO_POSITION || adapter.getStatusCount(false) <= 0) return
val status = adapter.getStatus(position.coerceIn(rangeOfSize(adapter.statusStartIndex,
adapter.getStatusCount(false))))
val readPosition = if (useSortIdAsReadPosition) {
status.sort_id
} else {
status.position_key
}
2017-04-13 18:57:14 +02:00
val positionTag = readPositionTag ?: ReadPositionTag.CUSTOM_TIMELINE
readPositionTagWithArguments?.let {
2017-04-13 18:57:14 +02:00
accountKeys.forEach { accountKey ->
val tag = Utils.getReadPositionTagWithAccount(it, accountKey)
readStateManager.setPosition(tag, readPosition)
}
}
2017-04-16 10:47:02 +02:00
timelineSyncTag?.let { syncTag ->
timelineSyncManager?.setPosition(positionTag, syncTag, status.position_key)
}
currentReadPositionTag?.let {
readStateManager.setPosition(it, readPosition, true)
2016-06-29 15:47:52 +02:00
}
}
2017-09-15 09:32:20 +02:00
protected open fun getFullStatus(position: Int): ParcelableStatus? {
return adapter.getStatus(position)
}
protected abstract fun hasMoreData(loader: Loader<List<ParcelableStatus>?>,
2020-01-26 08:35:15 +01:00
data: List<ParcelableStatus>?): Boolean
2016-08-19 16:25:27 +02:00
protected abstract fun onCreateStatusesLoader(context: Context, args: Bundle,
2017-02-13 17:44:56 +01:00
fromUser: Boolean): Loader<List<ParcelableStatus>?>
2016-08-19 16:25:27 +02:00
protected abstract fun onStatusesLoaded(loader: Loader<List<ParcelableStatus>?>, data: List<ParcelableStatus>?)
// Context Menu functions
2016-06-29 15:47:52 +02:00
override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
if (!userVisibleHint || menuInfo == null) return
val inflater = MenuInflater(context)
val contextMenuInfo = menuInfo as ExtendedRecyclerView.ContextMenuInfo?
val status = adapter.getStatus(contextMenuInfo!!.position)
2016-06-29 15:47:52 +02:00
inflater.inflate(R.menu.action_status, menu)
2020-01-26 08:35:15 +01:00
context?.let {
MenuUtils.setupForStatus(it, menu, preferences, twitterWrapper, userColorNameManager,
2017-04-12 10:17:20 +02:00
status)
2020-01-26 08:35:15 +01:00
}
2016-06-29 15:47:52 +02:00
}
2016-12-08 15:56:21 +01:00
override fun onContextItemSelected(item: MenuItem): Boolean {
2016-06-29 15:47:52 +02:00
if (!userVisibleHint) return false
2016-12-08 15:56:21 +01:00
val contextMenuInfo = item.menuInfo as ExtendedRecyclerView.ContextMenuInfo
val status = adapter.getStatus(contextMenuInfo.position)
when (item.itemId) {
R.id.share -> {
2020-01-26 08:35:15 +01:00
val shareIntent = activity?.let { Utils.createStatusShareIntent(it, status) }
val chooser = Intent.createChooser(shareIntent, getString(R.string.share_status))
startActivity(chooser)
val am = AccountManager.get(context)
val accountType = AccountUtils.findByAccountKey(am, status.account_key)?.getAccountType(am)
Analyzer.log(Share.status(accountType, status))
return true
}
R.id.make_gap -> {
if (this !is CursorStatusesFragment) return true
2020-01-26 08:35:15 +01:00
val resolver = context?.contentResolver
val values = ContentValues()
values.put(Statuses.IS_GAP, 1)
2017-04-10 11:35:35 +02:00
val where = Expression.equals(Statuses._ID, status._id).sql
2020-01-26 08:35:15 +01:00
resolver?.update(contentUri, values, where, null)
return true
}
else -> return MenuUtils.handleStatusClick(requireActivity(), this, requireFragmentManager(),
2017-04-12 10:17:20 +02:00
preferences, userColorNameManager, twitterWrapper, status, item)
2016-06-29 15:47:52 +02:00
}
}
class DefaultOnLikedListener(
private val twitter: AsyncTwitterWrapper,
private val status: ParcelableStatus,
private val accountKey: UserKey? = null
2016-06-29 15:47:52 +02:00
) : LikeAnimationDrawable.OnLikedListener {
override fun onLiked(): Boolean {
if (status.is_favorite) return false
twitter.createFavoriteAsync(accountKey ?: status.account_key, status)
2016-06-29 15:47:52 +02:00
return true
}
}
protected inner class StatusesBusCallback {
@Subscribe
fun notifyStatusListChanged(event: StatusListChangedEvent) {
adapter.notifyDataSetChanged()
2016-06-29 15:47:52 +02:00
}
}
2016-08-19 16:25:27 +02:00
interface StatusesFragmentDelegate {
val shouldInitLoader: Boolean
}
2016-06-29 15:47:52 +02:00
companion object {
const val REQUEST_FAVORITE_SELECT_ACCOUNT = 101
const val REQUEST_RETWEET_SELECT_ACCOUNT = 102
2016-06-29 15:47:52 +02:00
2017-04-12 10:17:20 +02:00
fun handleActionClick(fragment: BaseFragment, id: Int, status: ParcelableStatus,
holder: StatusViewHolder) {
2016-06-29 15:47:52 +02:00
when (id) {
R.id.reply -> {
2016-08-19 16:25:27 +02:00
val intent = Intent(INTENT_ACTION_REPLY)
2020-01-26 08:35:15 +01:00
intent.`package` = fragment.context?.packageName
2016-08-19 16:25:27 +02:00
intent.putExtra(EXTRA_STATUS, status)
2017-04-12 10:17:20 +02:00
fragment.startActivity(intent)
2016-06-29 15:47:52 +02:00
}
R.id.retweet -> {
2017-04-12 10:17:20 +02:00
fragment.executeAfterFragmentResumed { fragment ->
RetweetQuoteDialogFragment.show(fragment.childFragmentManager,
status.account_key, status.id, status)
2017-02-13 17:44:56 +01:00
}
2016-06-29 15:47:52 +02:00
}
R.id.favorite -> {
2020-06-09 02:21:48 +02:00
when {
fragment.preferences[favoriteConfirmationKey] -> {
fragment.executeAfterFragmentResumed {
FavoriteConfirmDialogFragment.show(it.childFragmentManager,
2017-04-12 10:17:20 +02:00
status.account_key, status.id, status)
2020-06-09 02:21:48 +02:00
}
}
status.is_favorite -> {
fragment.twitterWrapper.destroyFavoriteAsync(status.account_key, status.id)
}
else -> {
holder.playLikeAnimation(DefaultOnLikedListener(fragment.twitterWrapper, status))
2017-04-12 10:17:20 +02:00
}
2016-06-29 15:47:52 +02:00
}
}
}
}
fun handleActionLongClick(fragment: Fragment, status: ParcelableStatus, itemId: Long, id: Int): Boolean {
when (id) {
R.id.favorite -> {
2020-01-26 08:35:15 +01:00
val intent = fragment.context?.let { selectAccountIntent(it, status, itemId) }
fragment.startActivityForResult(intent, REQUEST_FAVORITE_SELECT_ACCOUNT)
return true
}
R.id.retweet -> {
2020-01-26 08:35:15 +01:00
val intent = fragment.context?.let { selectAccountIntent(it, status, itemId, false) }
fragment.startActivityForResult(intent, REQUEST_RETWEET_SELECT_ACCOUNT)
return true
}
}
return false
}
fun handleActionActivityResult(fragment: BaseFragment, requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
2020-06-08 23:01:17 +02:00
REQUEST_FAVORITE_SELECT_ACCOUNT -> {
if (resultCode != Activity.RESULT_OK || data == null) return
2019-10-25 10:50:10 +02:00
val accountKey = data.getParcelableExtra<UserKey>(EXTRA_ACCOUNT_KEY)!!
val extras = data.getBundleExtra(EXTRA_EXTRAS)!!
val status = extras.getParcelable<ParcelableStatus>(EXTRA_STATUS)!!
2017-04-12 10:17:20 +02:00
if (fragment.preferences[favoriteConfirmationKey]) {
fragment.executeAfterFragmentResumed {
FavoriteConfirmDialogFragment.show(it.childFragmentManager,
accountKey, status.id, status)
}
} else {
fragment.twitterWrapper.createFavoriteAsync(accountKey, status)
}
}
2020-06-08 23:01:17 +02:00
REQUEST_RETWEET_SELECT_ACCOUNT -> {
if (resultCode != Activity.RESULT_OK || data == null) return
2019-10-25 10:50:10 +02:00
val accountKey = data.getParcelableExtra<UserKey>(EXTRA_ACCOUNT_KEY)!!
val extras = data.getBundleExtra(EXTRA_EXTRAS)!!
val status = extras.getParcelable<ParcelableStatus>(EXTRA_STATUS)!!
if (status.account_key.host != accountKey.host) {
val composeIntent = Intent(fragment.context, ComposeActivity::class.java)
2017-09-15 11:58:29 +02:00
composeIntent.putExtra(Intent.EXTRA_TEXT, "${status.text_plain} ${LinkCreator.getStatusWebLink(status)}")
composeIntent.putExtra(EXTRA_ACCOUNT_KEY, accountKey)
composeIntent.putExtra(EXTRA_SELECTION, 0)
fragment.startActivity(composeIntent)
} else fragment.executeAfterFragmentResumed {
2017-04-12 10:17:20 +02:00
RetweetQuoteDialogFragment.show(it.childFragmentManager, accountKey,
status.id, status)
2017-04-03 07:55:43 +02:00
}
}
}
}
fun selectAccountIntent(context: Context, status: ParcelableStatus, itemId: Long,
sameHostOnly: Boolean = true): Intent {
val intent = Intent(context, AccountSelectorActivity::class.java)
intent.putExtra(EXTRA_SELECT_ONLY_ITEM_AUTOMATICALLY, true)
if (sameHostOnly) {
intent.putExtra(EXTRA_ACCOUNT_HOST, status.account_key.host)
}
intent.putExtra(EXTRA_SINGLE_SELECTION, true)
intent.putExtra(EXTRA_EXTRAS, Bundle {
this[EXTRA_STATUS] = status
this[EXTRA_ID] = itemId
})
return intent
}
2017-04-12 10:17:20 +02:00
fun handleKeyboardShortcutAction(fragment: BaseFragment, action: String,
status: ParcelableStatus, position: Int): Boolean {
when (action) {
ACTION_STATUS_REPLY -> {
val intent = Intent(INTENT_ACTION_REPLY)
intent.putExtra(EXTRA_STATUS, status)
fragment.startActivity(intent)
return true
}
ACTION_STATUS_RETWEET -> {
fragment.executeAfterFragmentResumed {
RetweetQuoteDialogFragment.show(it.childFragmentManager,
status.account_key, status.id, status)
}
return true
}
ACTION_STATUS_FAVORITE -> {
2020-06-09 02:21:48 +02:00
when {
fragment.preferences[favoriteConfirmationKey] -> {
fragment.executeAfterFragmentResumed {
FavoriteConfirmDialogFragment.show(it.childFragmentManager,
2017-04-12 10:17:20 +02:00
status.account_key, status.id, status)
2020-06-09 02:21:48 +02:00
}
}
status.is_favorite -> {
fragment.twitterWrapper.destroyFavoriteAsync(status.account_key, status.id)
}
else -> {
val holder = fragment.recyclerView.findViewHolderForLayoutPosition(position) as StatusViewHolder
holder.playLikeAnimation(DefaultOnLikedListener(fragment.twitterWrapper, status))
2017-04-12 10:17:20 +02:00
}
}
return true
}
}
return false
}
2016-06-29 15:47:52 +02:00
}
}