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

289 lines
10 KiB
Kotlin

/*
* 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.fragment
import android.content.Context
import android.os.Bundle
import android.support.v4.app.hasRunningLoadersSafe
import android.support.v4.content.Loader
import android.text.TextUtils
import com.bumptech.glide.Glide
import com.squareup.otto.Subscribe
import org.mariotaku.twidere.R
import org.mariotaku.twidere.adapter.ListParcelableStatusesAdapter
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter
import org.mariotaku.twidere.constant.IntentConstants.*
import org.mariotaku.twidere.loader.AbsRequestStatusesLoader
import org.mariotaku.twidere.model.BaseRefreshTaskParam
import org.mariotaku.twidere.model.ParcelableStatus
import org.mariotaku.twidere.model.RefreshTaskParam
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.event.FavoriteTaskEvent
import org.mariotaku.twidere.model.event.StatusDestroyedEvent
import org.mariotaku.twidere.model.event.StatusListChangedEvent
import org.mariotaku.twidere.model.event.StatusRetweetedEvent
import org.mariotaku.twidere.util.Utils
import java.util.*
/**
* Created by mariotaku on 14/12/3.
*/
abstract class ParcelableStatusesFragment : AbsStatusesFragment() {
override var refreshing: Boolean
get() {
if (context == null || isDetached) return false
return loaderManager.hasRunningLoadersSafe()
}
set(value) {
super.refreshing = value
}
protected open val savedStatusesFileArgs: Array<String>?
get() = null
private var lastId: String? = null
private var page = 1
private var pageDelta: Int = 0
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
if (savedInstanceState != null) {
page = savedInstanceState.getInt(EXTRA_PAGE)
}
}
override fun onStart() {
super.onStart()
bus.register(this)
}
override fun onStop() {
bus.unregister(this)
super.onStop()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt(EXTRA_PAGE, page)
}
override fun getStatuses(param: RefreshTaskParam): Boolean {
if (!loaderInitialized) return false
val args = Bundle(arguments)
val maxIds = param.maxIds
if (maxIds != null) {
args.putString(EXTRA_MAX_ID, maxIds[0])
args.putBoolean(EXTRA_MAKE_GAP, false)
}
val sinceIds = param.sinceIds
if (sinceIds != null) {
args.putString(EXTRA_SINCE_ID, sinceIds[0])
}
args.putBoolean(EXTRA_LOADING_MORE, param.isLoadingMore)
args.putBoolean(EXTRA_FROM_USER, true)
if (param is StatusesRefreshTaskParam) {
if (param.page > 0) {
args.putInt(EXTRA_PAGE, param.page)
}
}
loaderManager.restartLoader(loaderId, args, this)
return true
}
override fun hasMoreData(data: List<ParcelableStatus>?): Boolean {
if (data == null || data.isEmpty()) return false
val tmpLastId = lastId
lastId = data[data.size - 1].id
return !TextUtils.equals(lastId, tmpLastId)
}
override val accountKeys: Array<UserKey>
get() = Utils.getAccountKeys(context, arguments) ?: emptyArray()
override fun createMessageBusCallback(): Any {
return ParcelableStatusesBusCallback()
}
override fun onCreateAdapter(context: Context): ListParcelableStatusesAdapter {
return ListParcelableStatusesAdapter(context, Glide.with(this))
}
override fun onStatusesLoaded(loader: Loader<List<ParcelableStatus>?>, data: List<ParcelableStatus>?) {
refreshEnabled = true
refreshing = false
setLoadMoreIndicatorPosition(ILoadMoreSupportAdapter.NONE)
if (adapter.itemCount > 0) {
showContent()
} else if (loader is AbsRequestStatusesLoader) {
val e = loader.exception
if (e != null) {
showError(R.drawable.ic_info_error_generic, Utils.getErrorMessage(context, e) ?:
context.getString(R.string.error_unknown_error))
} else {
showEmpty(R.drawable.ic_info_refresh, getString(R.string.swipe_down_to_refresh))
}
} else {
showEmpty(R.drawable.ic_info_refresh, getString(R.string.swipe_down_to_refresh))
}
}
override fun onLoadMoreContents(position: Long) {
// Only supports load from end, skip START flag
if (position and ILoadMoreSupportAdapter.START != 0L || refreshing) return
super.onLoadMoreContents(position)
if (position == 0L) return
// Get last raw status
val startIdx = adapter.statusStartIndex
if (startIdx < 0) return
val statusCount = adapter.getStatusCount(true)
if (statusCount <= 0) return
val status = adapter.getStatus(startIdx + statusCount - 1, true)
val accountKeys = arrayOf(status.account_key)
val maxIds = arrayOf<String?>(status.id)
val param = StatusesRefreshTaskParam(accountKeys, maxIds, null, page + pageDelta)
param.isLoadingMore = true
getStatuses(param)
}
override fun triggerRefresh(): Boolean {
super.triggerRefresh()
val accountKeys = accountKeys
val statusStartIndex = adapter.statusStartIndex
if (statusStartIndex >= 0 && adapter.getStatusCount(true) > 0) {
val firstStatus = adapter.getStatus(statusStartIndex, true)
val sinceIds = Array(accountKeys.size) {
return@Array if (firstStatus.account_key == accountKeys[it]) firstStatus.id else null
}
getStatuses(BaseRefreshTaskParam(accountKeys, null, sinceIds))
} else {
getStatuses(BaseRefreshTaskParam(accountKeys, null, null))
}
return true
}
override fun onHasMoreDataChanged(hasMoreData: Boolean) {
pageDelta = if (hasMoreData) 1 else 0
}
fun removeStatus(statusId: String) {
val list = adapterData ?: return
val dataToRemove = HashSet<ParcelableStatus>()
for (i in 0 until list.size) {
val status = list[i]
if (status.id == statusId || status.retweet_id == statusId) {
dataToRemove.add(status)
} else if (status.my_retweet_id == statusId) {
status.my_retweet_id = null
status.retweet_count = status.retweet_count - 1
}
}
if (list is MutableList) {
list.removeAll(dataToRemove)
}
adapterData = list
}
fun replaceStatusStates(status: ParcelableStatus?) {
if (status == null) return
val lm = layoutManager
val rangeStart = Math.max(adapter.statusStartIndex, lm.findFirstVisibleItemPosition())
val rangeEnd = Math.min(lm.findLastVisibleItemPosition(), adapter.statusStartIndex + adapter.getStatusCount(false) - 1)
for (i in rangeStart..rangeEnd) {
val item = adapter.getStatus(i, false)
if (status == item) {
item.favorite_count = status.favorite_count
item.retweet_count = status.retweet_count
item.reply_count = status.reply_count
item.is_favorite = status.is_favorite
}
}
adapter.notifyItemRangeChanged(rangeStart, rangeEnd)
}
private fun updateFavoritedStatus(status: ParcelableStatus) {
replaceStatusStates(status)
}
private fun updateRetweetedStatuses(status: ParcelableStatus?) {
val data = adapterData
if (status == null || status.retweet_id == null || data == null) return
data.forEach { orig ->
if (orig.account_key == status.account_key && TextUtils.equals(orig.id, status.retweet_id)) {
orig.my_retweet_id = status.my_retweet_id
orig.retweet_count = status.retweet_count
}
}
adapterData = data
}
protected open fun notifyFavoriteTask(event: FavoriteTaskEvent) {
if (event.isSucceeded) {
updateFavoritedStatus(event.status!!)
}
}
protected open fun notifyStatusDestroyed(event: StatusDestroyedEvent) {
removeStatus(event.status.id)
}
protected open fun notifyStatusListChanged(event: StatusListChangedEvent) {
adapter.notifyDataSetChanged()
}
protected open fun notifyStatusRetweeted(event: StatusRetweetedEvent) {
updateRetweetedStatuses(event.status)
}
protected inner class ParcelableStatusesBusCallback {
@Subscribe
fun onFavoriteTaskEvent(event: FavoriteTaskEvent) {
notifyFavoriteTask(event)
}
@Subscribe
fun onStatusDestroyedEvent(event: StatusDestroyedEvent) {
notifyStatusDestroyed(event)
}
@Subscribe
fun onStatusListChangedEvent(event: StatusListChangedEvent) {
notifyStatusListChanged(event)
}
@Subscribe
fun onStatusRetweetedEvent(event: StatusRetweetedEvent) {
notifyStatusRetweeted(event)
}
}
protected class StatusesRefreshTaskParam(
accountKeys: Array<UserKey>,
maxIds: Array<String?>?,
sinceIds: Array<String?>?,
var page: Int = -1
) : BaseRefreshTaskParam(accountKeys, maxIds, sinceIds)
}