Twidere-App-Android-Twitter.../twidere/src/main/java/org/mariotaku/twidere/fragment/CursorActivitiesFragment.kt

354 lines
13 KiB
Kotlin

/*
* 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
import android.content.Context
import android.database.ContentObserver
import android.database.Cursor
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.support.v4.content.Loader
import com.squareup.otto.Subscribe
import org.mariotaku.library.objectcursor.ObjectCursor
import org.mariotaku.sqliteqb.library.ArgsArray
import org.mariotaku.sqliteqb.library.Columns.Column
import org.mariotaku.sqliteqb.library.Expression
import org.mariotaku.twidere.R
import org.mariotaku.twidere.activity.HomeActivity
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter.IndicatorPosition
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_FROM_USER
import org.mariotaku.twidere.loader.ExtendedObjectCursorLoader
import org.mariotaku.twidere.model.*
import org.mariotaku.twidere.model.message.*
import org.mariotaku.twidere.provider.TwidereDataStore.*
import org.mariotaku.twidere.util.DataStoreUtils
import org.mariotaku.twidere.util.DataStoreUtils.getTableNameByUri
import org.mariotaku.twidere.util.ErrorInfoStore
import org.mariotaku.twidere.util.TwidereArrayUtils
import org.mariotaku.twidere.util.Utils
/**
* Displays statuses from database
* Created by mariotaku on 14/12/3.
*/
abstract class CursorActivitiesFragment : AbsActivitiesFragment() {
override fun onLoadingFinished() {
val accountKeys = accountKeys
val adapter = adapter
if (adapter!!.itemCount > 0) {
showContent()
} else if (accountKeys.size > 0) {
val errorInfo = ErrorInfoStore.getErrorInfo(context,
errorInfoStore.get(errorInfoKey, accountKeys[0]))
if (errorInfo != null) {
showEmpty(errorInfo.icon, errorInfo.message)
} else {
showEmpty(R.drawable.ic_info_refresh, getString(R.string.swipe_down_to_refresh))
}
} else {
showError(R.drawable.ic_info_accounts, getString(R.string.no_account_selected))
}
}
protected abstract val errorInfoKey: String
private var mContentObserver: ContentObserver? = null
abstract val contentUri: Uri
override fun onCreateActivitiesLoader(context: Context,
args: Bundle,
fromUser: Boolean): Loader<List<ParcelableActivity>> {
val uri = contentUri
val table = getTableNameByUri(uri)
val sortOrder = sortOrder
val accountKeys = accountKeys
val accountWhere = Expression.`in`(Column(Activities.ACCOUNT_KEY),
ArgsArray(accountKeys.size))
val filterWhere = getFiltersWhere(table)
val where: Expression
if (filterWhere != null) {
where = Expression.and(accountWhere, filterWhere)
} else {
where = accountWhere
}
val accountSelectionArgs = TwidereArrayUtils.toStringArray(accountKeys, 0,
accountKeys.size)
val expression = processWhere(where, accountSelectionArgs)
val selection = expression.sql
val adapter = adapter
adapter!!.showAccountsColor = accountKeys.size > 1
val projection = Activities.COLUMNS
return CursorActivitiesLoader(context, uri, projection, selection,
expression.parameters, sortOrder, fromUser)
}
override fun createMessageBusCallback(): Any {
return CursorActivitiesBusCallback()
}
override val accountKeys: Array<UserKey>
get() {
val context = context!!
val args = arguments
val accountKeys = Utils.getAccountKeys(context, args)
if (accountKeys != null) {
return accountKeys
}
if (context is HomeActivity) {
return context.activatedAccountKeys
}
return DataStoreUtils.getActivatedAccountKeys(context)
}
override fun onStart() {
super.onStart()
val cr = contentResolver
mContentObserver = object : ContentObserver(Handler()) {
override fun onChange(selfChange: Boolean) {
reloadActivities()
}
}
cr!!.registerContentObserver(Accounts.CONTENT_URI, true, mContentObserver!!)
cr.registerContentObserver(Filters.CONTENT_URI, true, mContentObserver!!)
updateRefreshState()
reloadActivities()
}
protected fun reloadActivities() {
if (activity == null || isDetached) return
val args = Bundle()
val fragmentArgs = arguments
if (fragmentArgs != null) {
args.putAll(fragmentArgs)
args.putBoolean(EXTRA_FROM_USER, true)
}
loaderManager.restartLoader(0, args, this)
}
override fun onStop() {
val cr = contentResolver
cr!!.unregisterContentObserver(mContentObserver!!)
super.onStop()
}
override fun hasMoreData(data: List<ParcelableActivity>?): Boolean {
return data?.size != 0
}
override fun onLoaderReset(loader: Loader<List<ParcelableActivity>>) {
adapter!!.setData(null)
}
override fun onLoadMoreContents(@IndicatorPosition position: Long) {
// Only supports load from end, skip START flag
if (position and ILoadMoreSupportAdapter.START !== 0L || refreshing) return
super.onLoadMoreContents(position.toLong())
if (position == 0L) return
getActivities(object : SimpleRefreshTaskParam() {
override fun getAccountKeysWorker(): Array<UserKey> {
return this@CursorActivitiesFragment.accountKeys
}
override fun getMaxIds(): Array<String>? {
return getOldestActivityIds(accountKeys)
}
override fun getMaxSortIds(): LongArray? {
val context = context ?: return null
return DataStoreUtils.getOldestActivityMaxSortPositions(context,
contentUri, accountKeys)
}
override fun hasMaxIds(): Boolean {
return true
}
override fun shouldAbort(): Boolean {
return context == null
}
})
}
override fun triggerRefresh(): Boolean {
super.triggerRefresh()
getActivities(object : SimpleRefreshTaskParam() {
override fun getAccountKeysWorker(): Array<UserKey> {
return this@CursorActivitiesFragment.accountKeys
}
override fun getSinceIds(): Array<String>? {
return getNewestActivityIds(accountKeys)
}
override fun getSinceSortIds(): LongArray? {
val context = context ?: return null
return DataStoreUtils.getNewestActivityMaxSortPositions(context,
contentUri, accountKeys)
}
override fun hasSinceIds(): Boolean {
return true
}
override fun shouldAbort(): Boolean {
return context == null
}
})
return true
}
protected fun getFiltersWhere(table: String): Expression? {
if (!isFilterEnabled) return null
return DataStoreUtils.buildActivityFilterWhereClause(table, null)
}
protected fun getNewestActivityIds(accountKeys: Array<UserKey>): Array<String>? {
val context = context ?: return null
return DataStoreUtils.getNewestActivityMaxPositions(context, contentUri, accountKeys)
}
protected abstract val notificationType: Int
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
val context = context
if (context != null && isVisibleToUser) {
val accountKeys = accountKeys
for (accountKey in accountKeys) {
twitterWrapper.clearNotificationAsync(notificationType, accountKey)
}
}
}
protected fun getOldestActivityIds(accountKeys: Array<UserKey>): Array<String>? {
val context = context ?: return null
return DataStoreUtils.getOldestActivityMaxPositions(context, contentUri, accountKeys)
}
protected abstract val isFilterEnabled: Boolean
protected open fun processWhere(where: Expression, whereArgs: Array<String>): ParameterizedExpression {
return ParameterizedExpression(where, whereArgs)
}
protected abstract fun updateRefreshState()
private val sortOrder: String
get() = Activities.DEFAULT_SORT_ORDER
private fun updateFavoritedStatus(status: ParcelableStatus) {
activity ?: return
replaceStatusStates(status)
}
fun replaceStatusStates(result: ParcelableStatus?) {
if (result == null) return
val lm = layoutManager
val adapter = adapter
val rangeStart = Math.max(adapter!!.activityStartIndex, lm!!.findFirstVisibleItemPosition())
val rangeEnd = Math.min(lm.findLastVisibleItemPosition(), adapter.activityStartIndex + adapter.activityCount - 1)
var i = rangeStart
val j = rangeEnd + 1
while (i < j) {
val activity = adapter.getActivity(i)
if (result.account_key == activity!!.account_key && result.id == activity.status_id) {
if (result.id != activity.status_id) {
i++
continue
}
val statusesMatrix = arrayOf(activity.target_statuses, activity.target_object_statuses)
for (statusesArray in statusesMatrix) {
if (statusesArray == null) continue
for (status in statusesArray) {
if (result.id == status.id || result.id == status.retweet_id
|| result.id == status.my_retweet_id) {
status.is_favorite = result.is_favorite
status.reply_count = result.reply_count
status.retweet_count = result.retweet_count
status.favorite_count = result.favorite_count
}
}
}
}
i++
}
adapter.notifyItemRangeChanged(rangeStart, rangeEnd)
}
protected inner class CursorActivitiesBusCallback {
@Subscribe
fun notifyGetStatusesTaskChanged(event: GetActivitiesTaskEvent) {
if (event.uri != contentUri) return
refreshing = event.running
if (!event.running) {
setLoadMoreIndicatorPosition(ILoadMoreSupportAdapter.NONE)
refreshEnabled = true
onLoadingFinished()
}
}
@Subscribe
fun notifyFavoriteTask(event: FavoriteTaskEvent) {
if (event.isSucceeded) {
updateFavoritedStatus(event.status!!)
}
}
@Subscribe
fun notifyStatusDestroyed(event: StatusDestroyedEvent) {
}
@Subscribe
fun notifyStatusListChanged(event: StatusListChangedEvent) {
adapter!!.notifyDataSetChanged()
}
@Subscribe
fun notifyStatusRetweeted(event: StatusRetweetedEvent) {
}
@Subscribe
fun notifyAccountChanged(event: AccountChangedEvent) {
}
}
class CursorActivitiesLoader(context: Context, uri: Uri, projection: Array<String>,
selection: String, selectionArgs: Array<String>,
sortOrder: String, fromUser: Boolean) : ExtendedObjectCursorLoader<ParcelableActivity>(context, ParcelableActivityCursorIndices::class.java, uri, projection, selection, selectionArgs, sortOrder, fromUser) {
override fun createObjectCursor(cursor: Cursor, indices: ObjectCursor.CursorIndices<ParcelableActivity>): ObjectCursor<ParcelableActivity> {
val filteredUserIds = DataStoreUtils.getFilteredUserIds(context)
return ActivityCursor(cursor, indices, filteredUserIds)
}
class ActivityCursor(cursor: Cursor, indies: ObjectCursor.CursorIndices<ParcelableActivity>,
val filteredUserIds: Array<UserKey>) : ObjectCursor<ParcelableActivity>(cursor, indies)
}
}