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

2216 lines
96 KiB
Kotlin
Raw Normal View History

2016-06-29 15:47:52 +02:00
/*
* 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
2016-12-04 04:58:03 +01:00
import android.accounts.AccountManager
2016-06-29 15:47:52 +02:00
import android.app.Activity
import android.app.Dialog
import android.content.*
2016-06-29 15:47:52 +02:00
import android.graphics.Color
import android.graphics.Rect
import android.nfc.NdefMessage
import android.nfc.NdefRecord
import android.nfc.NfcAdapter.CreateNdefMessageCallback
import android.os.AsyncTask
import android.os.Bundle
import android.support.annotation.UiThread
import android.support.v4.app.LoaderManager.LoaderCallbacks
2016-11-26 09:13:00 +01:00
import android.support.v4.app.hasRunningLoadersSafe
2016-06-29 15:47:52 +02:00
import android.support.v4.content.ContextCompat
2017-02-07 16:36:29 +01:00
import android.support.v4.content.FixedAsyncTaskLoader
2016-06-29 15:47:52 +02:00
import android.support.v4.content.Loader
import android.support.v4.view.MenuItemCompat
import android.support.v4.view.ViewCompat
import android.support.v7.app.AlertDialog
import android.support.v7.widget.ActionMenuView
import android.support.v7.widget.FixedLinearLayoutManager
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.RecyclerView.ViewHolder
import android.text.SpannableString
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.TextUtils
import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan
import android.text.style.ForegroundColorSpan
import android.text.style.URLSpan
import android.view.*
import android.view.View.OnClickListener
import android.widget.ImageView
import android.widget.Space
import android.widget.TextView
2017-03-01 15:12:25 +01:00
import com.bumptech.glide.Glide
2016-06-29 15:47:52 +02:00
import com.squareup.otto.Subscribe
import edu.tsinghua.hotmobi.HotMobiLogger
import edu.tsinghua.hotmobi.model.MediaEvent
import edu.tsinghua.hotmobi.model.TimelineType
2016-10-05 15:12:33 +02:00
import edu.tsinghua.hotmobi.model.TranslateEvent
2016-06-29 15:47:52 +02:00
import edu.tsinghua.hotmobi.model.TweetEvent
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.applyFontFamily
import org.mariotaku.ktextension.contains
import org.mariotaku.ktextension.findPositionByItemId
2017-03-05 09:08:09 +01:00
import org.mariotaku.library.objectcursor.ObjectCursor
2016-06-29 15:47:52 +02:00
import org.mariotaku.microblog.library.MicroBlogException
import org.mariotaku.microblog.library.twitter.model.Paging
import org.mariotaku.microblog.library.twitter.model.TranslationResult
import org.mariotaku.sqliteqb.library.Expression
import org.mariotaku.twidere.Constants.*
import org.mariotaku.twidere.R
import org.mariotaku.twidere.activity.ColorPickerDialogActivity
import org.mariotaku.twidere.adapter.BaseRecyclerViewAdapter
import org.mariotaku.twidere.adapter.ListParcelableStatusesAdapter
import org.mariotaku.twidere.adapter.LoadMoreSupportAdapter
import org.mariotaku.twidere.adapter.decorator.DividerItemDecoration
import org.mariotaku.twidere.adapter.iface.IGapSupportedAdapter
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter.IndicatorPosition
import org.mariotaku.twidere.adapter.iface.IStatusesAdapter
2016-12-03 06:48:40 +01:00
import org.mariotaku.twidere.annotation.AccountType
2016-06-29 15:47:52 +02:00
import org.mariotaku.twidere.annotation.Referral
2017-01-07 07:16:02 +01:00
import org.mariotaku.twidere.constant.*
2016-06-29 15:47:52 +02:00
import org.mariotaku.twidere.constant.KeyboardShortcutConstants.*
2017-02-05 14:42:20 +01:00
import org.mariotaku.twidere.extension.applyTheme
2017-03-02 02:08:54 +01:00
import org.mariotaku.twidere.extension.loadProfileImage
import org.mariotaku.twidere.extension.model.applyTo
2016-12-29 06:50:18 +01:00
import org.mariotaku.twidere.extension.model.getAccountType
2017-01-07 11:02:32 +01:00
import org.mariotaku.twidere.extension.model.media_type
2017-02-21 16:12:03 +01:00
import org.mariotaku.twidere.extension.view.calculateSpaceItemHeight
2017-02-13 17:44:56 +01:00
import org.mariotaku.twidere.fragment.AbsStatusesFragment.Companion.handleActionClick
2016-06-29 15:47:52 +02:00
import org.mariotaku.twidere.loader.ConversationLoader
import org.mariotaku.twidere.loader.ParcelableStatusLoader
import org.mariotaku.twidere.menu.FavoriteItemProvider
import org.mariotaku.twidere.model.*
2016-12-18 06:21:24 +01:00
import org.mariotaku.twidere.model.analyzer.Share
2017-01-07 11:02:32 +01:00
import org.mariotaku.twidere.model.analyzer.StatusView
2017-02-09 09:00:12 +01:00
import org.mariotaku.twidere.model.event.FavoriteTaskEvent
import org.mariotaku.twidere.model.event.StatusListChangedEvent
2016-06-29 15:47:52 +02:00
import org.mariotaku.twidere.model.util.*
import org.mariotaku.twidere.provider.TwidereDataStore.*
import org.mariotaku.twidere.util.*
import org.mariotaku.twidere.util.ContentScrollHandler.ContentListSupport
import org.mariotaku.twidere.util.KeyboardShortcutsHandler.KeyboardShortcutCallback
2016-12-15 13:27:55 +01:00
import org.mariotaku.twidere.util.RecyclerViewScrollHandler.RecyclerViewCallback
2017-01-25 05:00:13 +01:00
import org.mariotaku.twidere.util.twitter.card.TwitterCardViewFactory
2016-06-29 15:47:52 +02:00
import org.mariotaku.twidere.view.CardMediaContainer.OnMediaClickListener
import org.mariotaku.twidere.view.ExtendedRecyclerView
import org.mariotaku.twidere.view.holder.GapViewHolder
import org.mariotaku.twidere.view.holder.LoadIndicatorViewHolder
import org.mariotaku.twidere.view.holder.StatusViewHolder
import org.mariotaku.twidere.view.holder.iface.IStatusViewHolder
import org.mariotaku.twidere.view.holder.iface.IStatusViewHolder.StatusClickListener
import java.util.*
/**
* Displays status details
* Created by mariotaku on 14/12/5.
*/
2017-01-12 17:26:44 +01:00
class StatusFragment : BaseFragment(), LoaderCallbacks<SingleResponse<ParcelableStatus>>,
OnMediaClickListener, StatusClickListener, KeyboardShortcutCallback,
ContentListSupport<StatusFragment.StatusAdapter> {
2016-06-29 15:47:52 +02:00
private var mItemDecoration: DividerItemDecoration? = null
2016-12-08 15:56:21 +01:00
override lateinit var adapter: StatusAdapter
2016-06-29 15:47:52 +02:00
2016-12-15 13:27:55 +01:00
private lateinit var layoutManager: LinearLayoutManager
private lateinit var navigationHelper: RecyclerViewNavigationHelper
private lateinit var scrollListener: RecyclerViewScrollHandler<StatusFragment.StatusAdapter>
2016-06-29 15:47:52 +02:00
2016-12-15 13:27:55 +01:00
private var loadTranslationTask: LoadTranslationTask? = null
2016-06-29 15:47:52 +02:00
// Data fields
2016-07-05 15:19:51 +02:00
private var conversationLoaderInitialized: Boolean = false
2016-06-29 15:47:52 +02:00
private var mActivityLoaderInitialized: Boolean = false
private var hasMoreConversation = true
2016-12-15 13:27:55 +01:00
private var statusEvent: TweetEvent? = null
2016-06-29 15:47:52 +02:00
// Listeners
2016-07-05 15:19:51 +02:00
private val conversationsLoaderCallback = object : LoaderCallbacks<List<ParcelableStatus>> {
2016-06-29 15:47:52 +02:00
override fun onCreateLoader(id: Int, args: Bundle): Loader<List<ParcelableStatus>> {
2016-12-08 15:56:21 +01:00
val adapter = this@StatusFragment.adapter
2016-12-06 06:15:22 +01:00
adapter.isRepliesLoading = true
adapter.isConversationsLoading = true
adapter.updateItemDecoration()
val status: ParcelableStatus = args.getParcelable(EXTRA_STATUS)
2016-06-29 15:47:52 +02:00
val maxId = args.getString(EXTRA_MAX_ID)
val sinceId = args.getString(EXTRA_SINCE_ID)
val maxSortId = args.getLong(EXTRA_MAX_SORT_ID)
val sinceSortId = args.getLong(EXTRA_SINCE_SORT_ID)
val loadingMore = args.getBoolean(EXTRA_LOADING_MORE, false)
2016-12-06 06:15:22 +01:00
val loader = ConversationLoader(activity, status, sinceId, maxId, sinceSortId, maxSortId,
adapter.getData(), true, loadingMore)
2016-06-29 15:47:52 +02:00
// Setting comparator to null lets statuses sort ascending
2017-02-14 18:09:56 +01:00
loader.comparator = null
2016-06-29 15:47:52 +02:00
return loader
}
override fun onLoadFinished(loader: Loader<List<ParcelableStatus>>, data: List<ParcelableStatus>?) {
2016-12-08 15:56:21 +01:00
val adapter = this@StatusFragment.adapter
2016-12-06 06:15:22 +01:00
adapter.updateItemDecoration()
2016-06-29 15:47:52 +02:00
val conversationLoader = loader as ConversationLoader
var supportedPositions: Long = 0
if (data != null && !data.isEmpty()) {
if (conversationLoader.sinceSortId < data[data.size - 1].sort_id) {
supportedPositions = supportedPositions or ILoadMoreSupportAdapter.END
}
if (data[0].in_reply_to_status_id != null) {
supportedPositions = supportedPositions or ILoadMoreSupportAdapter.START
}
} else {
supportedPositions = supportedPositions or ILoadMoreSupportAdapter.END
val status = status
if (status != null && status.in_reply_to_status_id != null) {
supportedPositions = supportedPositions or ILoadMoreSupportAdapter.START
}
}
2016-12-06 06:15:22 +01:00
adapter.loadMoreSupportedPosition = supportedPositions
2016-06-29 15:47:52 +02:00
setConversation(data)
val canLoadAllReplies = loader.canLoadAllReplies()
if (canLoadAllReplies) {
2016-12-06 06:15:22 +01:00
adapter.setReplyError(null)
2016-06-29 15:47:52 +02:00
} else {
val error = SpannableStringBuilder.valueOf(
HtmlSpanBuilder.fromHtml(getString(R.string.cant_load_all_replies_message)))
2016-12-06 06:15:22 +01:00
val dialogSpan: ClickableSpan? = error.getSpans(0, error.length, URLSpan::class.java)
.firstOrNull { "#dialog" == it.url }
2016-06-29 15:47:52 +02:00
if (dialogSpan != null) {
val spanStart = error.getSpanStart(dialogSpan)
val spanEnd = error.getSpanEnd(dialogSpan)
error.removeSpan(dialogSpan)
error.setSpan(object : ClickableSpan() {
override fun onClick(widget: View) {
val activity = activity
if (activity == null || activity.isFinishing) return
2016-12-11 07:29:00 +01:00
MessageDialogFragment.show(activity.supportFragmentManager,
message = getString(R.string.cant_load_all_replies_explanation),
tag = "cant_load_all_replies_explanation")
2016-06-29 15:47:52 +02:00
}
}, spanStart, spanEnd, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
}
2016-12-06 06:15:22 +01:00
adapter.setReplyError(error)
2016-06-29 15:47:52 +02:00
}
2016-12-06 06:15:22 +01:00
adapter.isConversationsLoading = false
adapter.isRepliesLoading = false
2016-06-29 15:47:52 +02:00
}
override fun onLoaderReset(loader: Loader<List<ParcelableStatus>>) {
}
}
2017-01-07 11:02:32 +01:00
private val statusActivityLoaderCallback = object : LoaderCallbacks<StatusActivity?> {
2016-06-29 15:47:52 +02:00
override fun onCreateLoader(id: Int, args: Bundle): Loader<StatusActivity?> {
val accountKey = args.getParcelable<UserKey>(EXTRA_ACCOUNT_KEY)
val statusId = args.getString(EXTRA_STATUS_ID)
return StatusActivitySummaryLoader(activity, accountKey, statusId)
}
override fun onLoadFinished(loader: Loader<StatusActivity?>, data: StatusActivity?) {
2016-12-08 15:56:21 +01:00
adapter.updateItemDecoration()
adapter.statusActivity = data
2016-06-29 15:47:52 +02:00
}
override fun onLoaderReset(loader: Loader<StatusActivity?>) {
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
val activity = activity ?: return
when (requestCode) {
REQUEST_SET_COLOR -> {
2016-12-08 15:56:21 +01:00
val status = adapter.status ?: return
2016-06-29 15:47:52 +02:00
if (resultCode == Activity.RESULT_OK) {
if (data == null) return
val color = data.getIntExtra(EXTRA_COLOR, Color.TRANSPARENT)
userColorNameManager.setUserColor(status.user_key, color)
} else if (resultCode == ColorPickerDialogActivity.RESULT_CLEARED) {
userColorNameManager.clearUserColor(status.user_key)
}
val args = arguments
if (args.containsKey(EXTRA_STATUS)) {
args.putParcelable(EXTRA_STATUS, status)
}
loaderManager.restartLoader(LOADER_ID_DETAIL_STATUS, args, this)
}
REQUEST_SELECT_ACCOUNT -> {
2016-12-08 15:56:21 +01:00
val status = adapter.status ?: return
2016-06-29 15:47:52 +02:00
if (resultCode == Activity.RESULT_OK) {
if (data == null || !data.hasExtra(EXTRA_ID)) return
val accountKey = data.getParcelableExtra<UserKey>(EXTRA_ACCOUNT_KEY)
IntentUtils.openStatus(activity, accountKey, status.id)
}
}
AbsStatusesFragment.REQUEST_FAVORITE_SELECT_ACCOUNT,
AbsStatusesFragment.REQUEST_RETWEET_SELECT_ACCOUNT -> {
AbsStatusesFragment.handleActionActivityResult(this, requestCode, resultCode, data)
}
2016-06-29 15:47:52 +02:00
}
}
2016-12-15 13:27:55 +01:00
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_status, container, false)
2016-06-29 15:47:52 +02:00
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setHasOptionsMenu(true)
Utils.setNdefPushMessageCallback(activity, CreateNdefMessageCallback {
val status = status ?: return@CreateNdefMessageCallback null
NdefMessage(arrayOf(NdefRecord.createUri(LinkCreator.getStatusWebLink(status))))
})
adapter = StatusAdapter(this)
layoutManager = StatusListLinearLayoutManager(context, recyclerView)
2016-12-15 13:27:55 +01:00
mItemDecoration = StatusDividerItemDecoration(context, adapter, layoutManager.orientation)
2016-06-29 15:47:52 +02:00
recyclerView.addItemDecoration(mItemDecoration)
2016-12-15 13:27:55 +01:00
layoutManager.recycleChildrenOnDetach = true
2016-06-29 15:47:52 +02:00
recyclerView.layoutManager = layoutManager
recyclerView.clipToPadding = false
2016-12-08 15:56:21 +01:00
adapter.statusClickListener = this
2016-06-29 15:47:52 +02:00
recyclerView.adapter = adapter
2016-12-15 13:27:55 +01:00
registerForContextMenu(recyclerView)
2016-06-29 15:47:52 +02:00
2016-12-15 13:27:55 +01:00
scrollListener = RecyclerViewScrollHandler(this, RecyclerViewCallback(recyclerView))
scrollListener.touchSlop = ViewConfiguration.get(context).scaledTouchSlop
2016-06-29 15:47:52 +02:00
2016-12-15 13:27:55 +01:00
navigationHelper = RecyclerViewNavigationHelper(recyclerView, layoutManager,
2016-12-08 15:56:21 +01:00
adapter, null)
2016-06-29 15:47:52 +02:00
setState(STATE_LOADING)
loaderManager.initLoader(LOADER_ID_DETAIL_STATUS, arguments, this)
}
2017-02-28 03:37:03 +01:00
override fun onMediaClick(holder: IStatusViewHolder, view: View, current: ParcelableMedia, statusPosition: Int) {
2016-12-08 15:56:21 +01:00
val status = adapter.getStatus(statusPosition) ?: return
2017-02-28 03:37:03 +01:00
IntentUtils.openMedia(activity, status, current, preferences[newDocumentApiKey],
2017-01-20 15:08:42 +01:00
preferences[displaySensitiveContentsKey])
2016-06-29 15:47:52 +02:00
2017-02-28 03:37:03 +01:00
val event = MediaEvent.create(activity, status, current, TimelineType.DETAILS,
2016-12-08 15:56:21 +01:00
adapter.mediaPreviewEnabled)
2016-06-29 15:47:52 +02:00
HotMobiLogger.getInstance(activity).log(status.account_key, event)
}
override fun onGapClick(holder: GapViewHolder, position: Int) {
}
override fun onItemActionClick(holder: ViewHolder, id: Int, position: Int) {
val status = adapter.getStatus(position) ?: return
2017-02-13 17:44:56 +01:00
handleActionClick(holder as StatusViewHolder, status, id)
2016-06-29 15:47:52 +02:00
}
override fun onItemActionLongClick(holder: RecyclerView.ViewHolder, id: Int, position: Int): Boolean {
val status = adapter.getStatus(position) ?: return false
return AbsStatusesFragment.handleActionLongClick(this, status, adapter.getItemId(position), id)
}
2016-06-29 15:47:52 +02:00
override fun onStatusClick(holder: IStatusViewHolder, position: Int) {
2017-01-17 18:59:44 +01:00
val status = adapter.getStatus(position) ?: return
2017-01-20 15:08:42 +01:00
IntentUtils.openStatus(activity, status)
2017-01-17 18:59:44 +01:00
}
override fun onQuotedStatusClick(holder: IStatusViewHolder, position: Int) {
val status = adapter.getStatus(position) ?: return
val quotedId = status.quoted_id ?: return
IntentUtils.openStatus(activity, status.account_key, quotedId)
2016-06-29 15:47:52 +02:00
}
override fun onStatusLongClick(holder: IStatusViewHolder, position: Int): Boolean {
return false
}
override fun onItemMenuClick(holder: ViewHolder, menuView: View, position: Int) {
if (activity == null) return
2016-12-15 13:27:55 +01:00
val view = layoutManager.findViewByPosition(position) ?: return
2016-06-29 15:47:52 +02:00
recyclerView.showContextMenuForChild(view)
}
override fun onUserProfileClick(holder: IStatusViewHolder, position: Int) {
2016-12-15 13:27:55 +01:00
val status = adapter.getStatus(position)!!
IntentUtils.openUserProfile(activity, status.account_key, status.user_key,
status.user_screen_name, preferences[newDocumentApiKey], Referral.TIMELINE_STATUS,
2017-01-20 15:08:42 +01:00
null)
2016-06-29 15:47:52 +02:00
}
override fun onMediaClick(view: View, media: ParcelableMedia, accountKey: UserKey?, id: Long) {
val status = adapter.status ?: return
IntentUtils.openMediaDirectly(activity, accountKey, status, media,
preferences[newDocumentApiKey], null)
2016-06-29 15:47:52 +02:00
// BEGIN HotMobi
val event = MediaEvent.create(activity, status, media, TimelineType.OTHER,
2016-12-08 15:56:21 +01:00
adapter.mediaPreviewEnabled)
2016-06-29 15:47:52 +02:00
HotMobiLogger.getInstance(activity).log(status.account_key, event)
// END HotMobi
}
override fun handleKeyboardShortcutSingle(handler: KeyboardShortcutsHandler,
2017-02-12 03:39:47 +01:00
keyCode: Int, event: KeyEvent,
metaState: Int): Boolean {
2016-06-29 15:47:52 +02:00
if (!KeyboardShortcutsHandler.isValidForHotkey(keyCode, event)) return false
2016-12-15 13:27:55 +01:00
val focusedChild = RecyclerViewUtils.findRecyclerViewChild(recyclerView, layoutManager.focusedChild)
2016-06-29 15:47:52 +02:00
val position: Int
if (focusedChild != null && focusedChild.parent === recyclerView) {
2016-12-15 13:27:55 +01:00
position = recyclerView.getChildLayoutPosition(focusedChild)
2016-06-29 15:47:52 +02:00
} else {
return false
}
if (position == -1) return false
2016-12-08 15:56:21 +01:00
val status = adapter.getStatus(position) ?: return false
2016-06-29 15:47:52 +02:00
val action = handler.getKeyAction(CONTEXT_TAG_STATUS, keyCode, event, metaState) ?: return false
when (action) {
ACTION_STATUS_REPLY -> {
val intent = Intent(INTENT_ACTION_REPLY)
intent.putExtra(EXTRA_STATUS, status)
startActivity(intent)
return true
}
ACTION_STATUS_RETWEET -> {
RetweetQuoteDialogFragment.show(fragmentManager, status)
return true
}
ACTION_STATUS_FAVORITE -> {
val twitter = twitterWrapper
if (status.is_favorite) {
twitter.destroyFavoriteAsync(status.account_key, status.id)
} else {
2017-02-07 15:55:36 +01:00
twitter.createFavoriteAsync(status.account_key, status)
2016-06-29 15:47:52 +02:00
}
return true
}
}
return false
}
override fun isKeyboardShortcutHandled(handler: KeyboardShortcutsHandler, keyCode: Int, event: KeyEvent, metaState: Int): Boolean {
val action = handler.getKeyAction(CONTEXT_TAG_STATUS, keyCode, event, metaState) ?: return false
when (action) {
ACTION_STATUS_REPLY, ACTION_STATUS_RETWEET, ACTION_STATUS_FAVORITE -> return true
}
2016-12-15 13:27:55 +01:00
return navigationHelper.isKeyboardShortcutHandled(handler, keyCode, event, metaState)
2016-06-29 15:47:52 +02:00
}
override fun handleKeyboardShortcutRepeat(handler: KeyboardShortcutsHandler,
2017-02-12 03:39:47 +01:00
keyCode: Int, repeatCount: Int,
event: KeyEvent, metaState: Int): Boolean {
2016-12-15 13:27:55 +01:00
return navigationHelper.handleKeyboardShortcutRepeat(handler, keyCode,
2016-06-29 15:47:52 +02:00
repeatCount, event, metaState)
}
override fun onCreateLoader(id: Int, args: Bundle): Loader<SingleResponse<ParcelableStatus>> {
val fragmentArgs = arguments
val accountKey = fragmentArgs.getParcelable<UserKey>(EXTRA_ACCOUNT_KEY)
val statusId = fragmentArgs.getString(EXTRA_STATUS_ID)
return ParcelableStatusLoader(activity, false, fragmentArgs, accountKey, statusId)
}
2017-01-07 15:45:33 +01:00
2016-06-29 15:47:52 +02:00
override fun onLoadFinished(loader: Loader<SingleResponse<ParcelableStatus>>,
2017-02-12 03:39:47 +01:00
data: SingleResponse<ParcelableStatus>) {
2016-06-29 15:47:52 +02:00
val activity = activity ?: return
2016-07-05 15:19:51 +02:00
val status = data.data
if (status != null) {
2016-06-29 15:47:52 +02:00
val readPosition = saveReadPosition()
val dataExtra = data.extras
2016-12-18 03:04:02 +01:00
val details: AccountDetails? = dataExtra.getParcelable(EXTRA_ACCOUNT)
2016-12-08 15:56:21 +01:00
if (adapter.setStatus(status, details)) {
2016-06-29 15:47:52 +02:00
val args = arguments
if (args.containsKey(EXTRA_STATUS)) {
args.putParcelable(EXTRA_STATUS, status)
}
2016-12-08 15:56:21 +01:00
adapter.loadMoreSupportedPosition = ILoadMoreSupportAdapter.BOTH
adapter.setData(null)
2016-06-29 15:47:52 +02:00
loadConversation(status, null, null)
loadActivity(status)
2016-12-08 15:56:21 +01:00
val position = adapter.getFirstPositionOfItem(StatusAdapter.ITEM_IDX_STATUS)
2016-06-29 15:47:52 +02:00
if (position != RecyclerView.NO_POSITION) {
2016-12-15 13:27:55 +01:00
layoutManager.scrollToPositionWithOffset(position, 0)
2016-06-29 15:47:52 +02:00
}
val event = TweetEvent.create(activity, status, TimelineType.OTHER)
2016-10-05 15:12:33 +02:00
event.action = TweetEvent.Action.OPEN
2016-12-18 03:04:02 +01:00
if (details != null) {
event.isHasTranslateFeature = Utils.isOfficialCredentials(context, details)
} else {
event.isHasTranslateFeature = false
}
2016-12-15 13:27:55 +01:00
statusEvent = event
2017-01-07 12:16:17 +01:00
Analyzer.log(StatusView(details?.type, status.media_type).apply {
this.type = StatusView.getStatusType(status)
this.source = HtmlEscapeHelper.toPlainText(status.source)
})
2016-06-29 15:47:52 +02:00
} else if (readPosition != null) {
restoreReadPosition(readPosition)
}
setState(STATE_LOADED)
} else {
2016-12-08 15:56:21 +01:00
adapter.loadMoreSupportedPosition = ILoadMoreSupportAdapter.NONE
2016-06-29 15:47:52 +02:00
setState(STATE_ERROR)
2016-12-24 06:48:01 +01:00
val errorInfo = StatusCodeMessageUtils.getErrorInfo(context, data.exception!!)
errorText.text = errorInfo.message
errorIcon.setImageResource(errorInfo.icon)
2016-06-29 15:47:52 +02:00
}
2016-11-30 08:18:43 +01:00
activity.supportInvalidateOptionsMenu()
2016-06-29 15:47:52 +02:00
}
override fun onLoaderReset(loader: Loader<SingleResponse<ParcelableStatus>>) {
2016-12-15 13:27:55 +01:00
val event = statusEvent ?: return
2016-06-29 15:47:52 +02:00
event.markEnd()
val accountKey = UserKey(event.accountId, event.accountHost)
HotMobiLogger.getInstance(activity).log(accountKey, event)
}
2016-12-15 13:27:55 +01:00
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_status, menu)
2016-06-29 15:47:52 +02:00
}
2016-12-15 13:27:55 +01:00
override fun onPrepareOptionsMenu(menu: Menu) {
2016-12-08 15:56:21 +01:00
MenuUtils.setItemAvailability(menu, R.id.current_status, adapter.status != null)
2016-06-29 15:47:52 +02:00
super.onPrepareOptionsMenu(menu)
}
2016-12-15 13:27:55 +01:00
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
2016-06-29 15:47:52 +02:00
R.id.current_status -> {
2016-12-08 15:56:21 +01:00
if (adapter.status != null) {
val position = adapter.getFirstPositionOfItem(StatusAdapter.ITEM_IDX_STATUS)
2016-12-15 13:27:55 +01:00
recyclerView.smoothScrollToPosition(position)
2016-06-29 15:47:52 +02:00
}
return true
}
}
return super.onOptionsItemSelected(item)
}
private fun setConversation(data: List<ParcelableStatus>?) {
val readPosition = saveReadPosition()
2016-12-08 15:56:21 +01:00
val changed = adapter.setData(data)
2016-06-29 15:47:52 +02:00
hasMoreConversation = data != null && changed
restoreReadPosition(readPosition)
}
override val refreshing: Boolean
2016-11-26 09:13:00 +01:00
get() = loaderManager.hasRunningLoadersSafe()
2016-06-29 15:47:52 +02:00
override fun onLoadMoreContents(@IndicatorPosition position: Long) {
if (!hasMoreConversation) return
if (position and ILoadMoreSupportAdapter.START != 0L) {
2016-12-08 15:56:21 +01:00
val start = adapter.getIndexStart(StatusAdapter.ITEM_IDX_CONVERSATION)
val status = adapter.getStatus(start)
2016-06-29 15:47:52 +02:00
if (status == null || status.in_reply_to_status_id == null) return
loadConversation(status, null, status.id)
} else if (position and ILoadMoreSupportAdapter.END != 0L) {
2016-12-08 15:56:21 +01:00
val start = adapter.getIndexStart(StatusAdapter.ITEM_IDX_CONVERSATION)
val status = adapter.getStatus(start + adapter.statusCount - 1) ?: return
2016-06-29 15:47:52 +02:00
loadConversation(status, status.id, null)
}
2016-12-08 15:56:21 +01:00
adapter.loadMoreIndicatorPosition = position
2016-06-29 15:47:52 +02:00
}
override fun setControlVisible(visible: Boolean) {
// No-op
}
override val reachingEnd: Boolean
2017-02-02 15:08:04 +01:00
get() {
val lm = layoutManager
var itemPos = lm.findLastCompletelyVisibleItemPosition()
if (itemPos == RecyclerView.NO_POSITION) {
// No completely visible item, find visible item instead
itemPos = lm.findLastVisibleItemPosition()
}
return itemPos >= lm.itemCount - 1
}
2016-06-29 15:47:52 +02:00
override val reachingStart: Boolean
2017-02-02 15:08:04 +01:00
get() {
val lm = layoutManager
var itemPos = lm.findFirstCompletelyVisibleItemPosition()
if (itemPos == RecyclerView.NO_POSITION) {
// No completely visible item, find visible item instead
itemPos = lm.findFirstVisibleItemPosition()
}
return itemPos <= 1
}
2016-06-29 15:47:52 +02:00
private val status: ParcelableStatus?
2016-12-08 15:56:21 +01:00
get() = adapter.status
2016-06-29 15:47:52 +02:00
private fun loadConversation(status: ParcelableStatus?, sinceId: String?, maxId: String?) {
if (status == null || activity == null) return
val args = Bundle()
args.putParcelable(EXTRA_ACCOUNT_KEY, status.account_key)
args.putString(EXTRA_STATUS_ID, if (status.is_retweet) status.retweet_id else status.id)
args.putString(EXTRA_SINCE_ID, sinceId)
args.putString(EXTRA_MAX_ID, maxId)
args.putParcelable(EXTRA_STATUS, status)
2016-07-05 15:19:51 +02:00
if (conversationLoaderInitialized) {
loaderManager.restartLoader(LOADER_ID_STATUS_CONVERSATIONS, args, conversationsLoaderCallback)
2016-06-29 15:47:52 +02:00
return
}
2016-07-05 15:19:51 +02:00
loaderManager.initLoader(LOADER_ID_STATUS_CONVERSATIONS, args, conversationsLoaderCallback)
conversationLoaderInitialized = true
2016-06-29 15:47:52 +02:00
}
private fun loadActivity(status: ParcelableStatus?) {
if (status == null || host == null || isDetached) return
val args = Bundle()
args.putParcelable(EXTRA_ACCOUNT_KEY, status.account_key)
args.putString(EXTRA_STATUS_ID, if (status.is_retweet) status.retweet_id else status.id)
if (mActivityLoaderInitialized) {
2017-01-07 11:02:32 +01:00
loaderManager.restartLoader(LOADER_ID_STATUS_ACTIVITY, args, statusActivityLoaderCallback)
2016-06-29 15:47:52 +02:00
return
}
2017-01-07 11:02:32 +01:00
loaderManager.initLoader(LOADER_ID_STATUS_ACTIVITY, args, statusActivityLoaderCallback)
2016-06-29 15:47:52 +02:00
mActivityLoaderInitialized = true
}
private fun loadTranslation(status: ParcelableStatus?) {
if (status == null) return
2016-07-05 15:19:51 +02:00
if (AsyncTaskUtils.isTaskRunning(loadTranslationTask)) {
loadTranslationTask!!.cancel(true)
2016-06-29 15:47:52 +02:00
}
2016-07-05 15:19:51 +02:00
loadTranslationTask = LoadTranslationTask(this)
AsyncTaskUtils.executeTask<LoadTranslationTask, ParcelableStatus>(loadTranslationTask, status)
2016-06-29 15:47:52 +02:00
}
private fun displayTranslation(translation: TranslationResult) {
2016-12-08 15:56:21 +01:00
adapter.translationResult = translation
2016-10-05 15:12:33 +02:00
val status = this.status
2016-11-26 09:13:00 +01:00
val context = this.context ?: return
2016-10-05 15:12:33 +02:00
if (status != null) {
val event = TranslateEvent.create(context, status, translation.translatedLang)
2016-12-06 06:15:22 +01:00
HotMobiLogger.getInstance(context).log(status.account_key, event)
2016-10-05 15:12:33 +02:00
}
2016-06-29 15:47:52 +02:00
}
private fun saveReadPosition(): ReadPosition? {
2016-12-15 13:27:55 +01:00
val lm = layoutManager
2016-12-08 15:56:21 +01:00
val adapter = this.adapter
2016-11-26 09:13:00 +01:00
val position = lm.findFirstVisibleItemPosition()
2016-06-29 15:47:52 +02:00
if (position == RecyclerView.NO_POSITION) return null
2016-11-26 09:13:00 +01:00
val itemType = adapter.getItemType(position)
var itemId = adapter.getItemId(position)
2016-06-29 15:47:52 +02:00
val positionView: View?
if (itemType == StatusAdapter.ITEM_IDX_CONVERSATION_LOAD_MORE) {
// Should be next item
2016-11-26 09:13:00 +01:00
positionView = lm.findViewByPosition(position + 1)
itemId = adapter.getItemId(position + 1)
2016-06-29 15:47:52 +02:00
} else {
2016-11-26 09:13:00 +01:00
positionView = lm.findViewByPosition(position)
2016-06-29 15:47:52 +02:00
}
2016-11-26 09:13:00 +01:00
return ReadPosition(itemId, positionView?.top ?: 0)
2016-06-29 15:47:52 +02:00
}
private fun restoreReadPosition(position: ReadPosition?) {
2016-12-08 15:56:21 +01:00
val adapter = this.adapter
2016-06-29 15:47:52 +02:00
if (position == null) return
2016-11-26 09:13:00 +01:00
val adapterPosition = adapter.findPositionByItemId(position.statusId)
2016-06-29 15:47:52 +02:00
if (adapterPosition < 0) return
//TODO maintain read position
2016-12-15 13:27:55 +01:00
layoutManager.scrollToPositionWithOffset(adapterPosition, position.offsetTop)
2016-06-29 15:47:52 +02:00
}
private fun setState(state: Int) {
statusContent.visibility = if (state == STATE_LOADED) View.VISIBLE else View.GONE
progressContainer.visibility = if (state == STATE_LOADING) View.VISIBLE else View.GONE
errorContainer.visibility = if (state == STATE_ERROR) View.VISIBLE else View.GONE
}
private fun showConversationError(exception: Exception) {
}
override fun onStart() {
super.onStart()
bus.register(this)
2016-12-15 13:27:55 +01:00
recyclerView.addOnScrollListener(scrollListener)
recyclerView.setOnTouchListener(scrollListener.touchListener)
2016-06-29 15:47:52 +02:00
}
override fun onStop() {
recyclerView.setOnTouchListener(null)
2016-12-15 13:27:55 +01:00
recyclerView.removeOnScrollListener(scrollListener)
2016-06-29 15:47:52 +02:00
bus.unregister(this)
super.onStop()
}
override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
2016-12-15 13:27:55 +01:00
if (!userVisibleHint) return
val contextMenuInfo = menuInfo as? ExtendedRecyclerView.ContextMenuInfo ?: return
val status = adapter.getStatus(contextMenuInfo.position) ?: return
2016-06-29 15:47:52 +02:00
val inflater = MenuInflater(context)
inflater.inflate(R.menu.action_status, menu)
MenuUtils.setupForStatus(context, preferences, menu, status, twitterWrapper,
userColorNameManager)
2016-06-29 15:47:52 +02:00
}
2016-12-15 13:27:55 +01:00
override fun onContextItemSelected(item: MenuItem): Boolean {
2016-06-29 15:47:52 +02:00
if (!userVisibleHint) return false
2016-12-15 13:27:55 +01:00
val contextMenuInfo = item.menuInfo as? ExtendedRecyclerView.ContextMenuInfo ?: return false
2016-12-08 15:56:21 +01:00
val status = adapter.getStatus(contextMenuInfo.position) ?: return false
2016-06-29 15:47:52 +02:00
if (item.itemId == R.id.share) {
val shareIntent = Utils.createStatusShareIntent(activity, status)
val chooser = Intent.createChooser(shareIntent, getString(R.string.share_status))
2016-12-18 06:21:24 +01:00
2016-06-29 15:47:52 +02:00
startActivity(chooser)
2016-12-18 06:21:24 +01:00
val am = AccountManager.get(context)
val accountType = AccountUtils.findByAccountKey(am, status.account_key)?.getAccountType(am)
Analyzer.log(Share.status(accountType, status))
2016-06-29 15:47:52 +02:00
return true
}
return MenuUtils.handleStatusClick(activity, this, fragmentManager,
userColorNameManager, twitterWrapper, status, item)
}
@Subscribe
fun notifyStatusListChanged(event: StatusListChangedEvent) {
2016-12-08 15:56:21 +01:00
adapter.notifyDataSetChanged()
2016-06-29 15:47:52 +02:00
}
@Subscribe
fun notifyFavoriteTask(event: FavoriteTaskEvent) {
if (!event.isSucceeded) return
2016-12-08 15:56:21 +01:00
val status = adapter.findStatusById(event.accountKey, event.statusId)
2016-06-29 15:47:52 +02:00
if (status != null) {
when (event.action) {
FavoriteTaskEvent.Action.CREATE -> {
status.is_favorite = true
}
FavoriteTaskEvent.Action.DESTROY -> {
status.is_favorite = false
}
}
}
}
private fun onUserClick(user: ParcelableUser) {
2017-01-20 15:08:42 +01:00
IntentUtils.openUserProfile(context, user, true, Referral.TIMELINE_STATUS,
null)
2016-06-29 15:47:52 +02:00
}
class LoadSensitiveImageConfirmDialogFragment : BaseDialogFragment(), DialogInterface.OnClickListener {
override fun onClick(dialog: DialogInterface, which: Int) {
when (which) {
DialogInterface.BUTTON_POSITIVE -> {
val f = parentFragment
if (f is StatusFragment) {
val adapter = f.adapter
2016-12-08 15:56:21 +01:00
adapter.isDetailMediaExpanded = true
2016-06-29 15:47:52 +02:00
}
}
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val context = activity
val builder = AlertDialog.Builder(context)
builder.setTitle(android.R.string.dialog_alert_title)
builder.setMessage(R.string.sensitive_content_warning)
builder.setPositiveButton(android.R.string.ok, this)
builder.setNegativeButton(android.R.string.cancel, null)
2017-02-05 14:42:20 +01:00
val dialog = builder.create()
dialog.setOnShowListener {
it as AlertDialog
it.applyTheme()
}
return dialog
2016-06-29 15:47:52 +02:00
}
}
internal class LoadTranslationTask(val fragment: StatusFragment) : AsyncTask<ParcelableStatus, Any, SingleResponse<TranslationResult>>() {
2017-02-14 13:32:15 +01:00
private val context = fragment.activity
2016-06-29 15:47:52 +02:00
override fun doInBackground(vararg params: ParcelableStatus): SingleResponse<TranslationResult> {
val status = params[0]
2016-12-06 06:15:22 +01:00
val twitter = MicroBlogAPIFactory.getInstance(context, status.account_key
)
2016-06-29 15:47:52 +02:00
val prefs = context.getSharedPreferences(SHARED_PREFERENCES_NAME,
Context.MODE_PRIVATE)
2016-07-05 15:19:51 +02:00
if (twitter == null) return SingleResponse.Companion.getInstance<TranslationResult>()
2016-06-29 15:47:52 +02:00
try {
val prefDest = prefs.getString(SharedPreferenceConstants.KEY_TRANSLATION_DESTINATION, null)
val dest: String
if (TextUtils.isEmpty(prefDest)) {
dest = twitter.accountSettings.language
val editor = prefs.edit()
editor.putString(SharedPreferenceConstants.KEY_TRANSLATION_DESTINATION, dest)
editor.apply()
} else {
dest = prefDest
}
val statusId = if (status.is_retweet) status.retweet_id else status.id
2016-07-05 15:19:51 +02:00
return SingleResponse.Companion.getInstance(twitter.showTranslation(statusId, dest))
2016-06-29 15:47:52 +02:00
} catch (e: MicroBlogException) {
2016-07-05 15:19:51 +02:00
return SingleResponse.Companion.getInstance<TranslationResult>(e)
2016-06-29 15:47:52 +02:00
}
}
override fun onPostExecute(result: SingleResponse<TranslationResult>) {
2016-07-05 15:19:51 +02:00
if (result.data != null) {
2016-06-29 15:47:52 +02:00
fragment.displayTranslation(result.data)
} else if (result.hasException()) {
2017-01-26 14:28:43 +01:00
Utils.showErrorMessage(context, R.string.action_translate, result.exception, false)
2016-06-29 15:47:52 +02:00
}
}
}
2017-01-07 15:45:33 +01:00
private class DetailStatusViewHolder(
private val adapter: StatusAdapter,
itemView: View
) : ViewHolder(itemView), OnClickListener, ActionMenuView.OnMenuItemClickListener {
2016-06-29 15:47:52 +02:00
private val linkClickHandler: StatusLinkClickHandler
private val linkify: TwidereLinkify
2016-08-30 14:23:59 +02:00
2017-03-01 15:12:25 +01:00
private val locationView = itemView.locationView
private val retweetedByView = itemView.retweetedBy
2016-08-30 14:23:59 +02:00
2016-06-29 15:47:52 +02:00
init {
this.linkClickHandler = DetailStatusLinkClickHandler(adapter.context,
adapter.multiSelectManager, adapter, adapter.preferences)
this.linkify = TwidereLinkify(linkClickHandler)
initViews()
}
@UiThread
2016-12-04 04:58:03 +01:00
fun displayStatus(account: AccountDetails?,
2017-02-12 03:39:47 +01:00
status: ParcelableStatus?,
statusActivity: StatusActivity?,
translation: TranslationResult?) {
2016-06-29 15:47:52 +02:00
if (account == null || status == null) return
val fragment = adapter.fragment
val context = adapter.context
val formatter = adapter.bidiFormatter
val twitter = adapter.twitterWrapper
val nameFirst = adapter.nameFirst
val colorNameManager = adapter.userColorNameManager
2016-06-29 15:47:52 +02:00
2016-07-04 03:31:17 +02:00
linkClickHandler.status = status
2016-06-29 15:47:52 +02:00
if (status.retweet_id != null) {
val retweetedBy = colorNameManager.getDisplayName(status.retweeted_by_user_key!!,
2016-06-29 15:47:52 +02:00
status.retweeted_by_user_name, status.retweeted_by_user_screen_name, nameFirst)
2016-08-30 14:23:59 +02:00
retweetedByView.text = context.getString(R.string.name_retweeted, retweetedBy)
retweetedByView.visibility = View.VISIBLE
2016-06-29 15:47:52 +02:00
} else {
2016-08-30 14:23:59 +02:00
retweetedByView.text = null
retweetedByView.visibility = View.GONE
2016-06-29 15:47:52 +02:00
}
itemView.profileContainer.drawEnd(status.account_color)
val layoutPosition = layoutPosition
val skipLinksInText = status.extras != null && status.extras.support_entities
if (status.is_quote) {
2016-08-30 17:57:37 +02:00
itemView.quotedView.visibility = View.VISIBLE
2016-06-29 15:47:52 +02:00
val originalIdAvailable = !TextUtils.isEmpty(status.quoted_id)
val quoteContentAvailable = status.quoted_text_plain != null && status.quoted_text_unescaped != null
if (quoteContentAvailable) {
itemView.quotedName.visibility = View.VISIBLE
itemView.quotedText.visibility = View.VISIBLE
itemView.quotedName.name = colorNameManager.getUserNickname(status.quoted_user_key!!,
status.quoted_user_name)
itemView.quotedName.screenName = "@${status.quoted_user_screen_name}"
2016-06-29 15:47:52 +02:00
itemView.quotedName.updateText(formatter)
var quotedDisplayEnd = -1
if (status.extras.quoted_display_text_range != null) {
quotedDisplayEnd = status.extras.quoted_display_text_range!![1]
}
val quotedText = SpannableStringBuilder.valueOf(status.quoted_text_unescaped)
status.quoted_spans?.applyTo(quotedText)
2016-06-29 15:47:52 +02:00
linkify.applyAllLinks(quotedText, status.account_key, layoutPosition.toLong(),
status.is_possibly_sensitive, skipLinksInText)
if (quotedDisplayEnd != -1 && quotedDisplayEnd <= quotedText.length) {
itemView.quotedText.text = quotedText.subSequence(0, quotedDisplayEnd)
} else {
itemView.quotedText.text = quotedText
}
if (itemView.quotedText.length() == 0) {
// No text
itemView.quotedText.visibility = View.GONE
} else {
itemView.quotedText.visibility = View.VISIBLE
}
2017-01-21 07:27:34 +01:00
val quotedUserColor = colorNameManager.getUserColor(status.quoted_user_key!!)
if (quotedUserColor != 0) {
itemView.quotedView.drawStart(quotedUserColor)
} else {
itemView.quotedView.drawStart(ThemeUtils.getColorFromAttribute(context,
R.attr.quoteIndicatorBackgroundColor, 0))
}
2016-08-30 17:57:37 +02:00
val quotedMedia = status.quoted_media
if (quotedMedia?.isEmpty() ?: true) {
2017-01-21 07:27:34 +01:00
itemView.quotedMediaLabel.visibility = View.GONE
2016-08-30 17:57:37 +02:00
itemView.quotedMediaPreview.visibility = View.GONE
} else if (adapter.isDetailMediaExpanded) {
2017-01-21 07:27:34 +01:00
itemView.quotedMediaLabel.visibility = View.GONE
2016-08-30 17:57:37 +02:00
itemView.quotedMediaPreview.visibility = View.VISIBLE
2017-03-02 07:59:19 +01:00
itemView.quotedMediaPreview.displayMedia(adapter.requestManager,
2017-03-01 15:12:25 +01:00
media = quotedMedia, accountId = status.account_key,
mediaClickListener = adapter.fragment)
2016-08-30 17:57:37 +02:00
} else {
2017-01-21 07:27:34 +01:00
itemView.quotedMediaLabel.visibility = View.VISIBLE
2016-08-30 17:57:37 +02:00
itemView.quotedMediaPreview.visibility = View.GONE
}
2016-06-29 15:47:52 +02:00
} else {
itemView.quotedName.visibility = View.GONE
itemView.quotedText.visibility = View.VISIBLE
2017-01-21 07:27:34 +01:00
itemView.quotedMediaLabel.visibility = View.GONE
itemView.quotedMediaPreview.visibility = View.GONE
2016-06-29 15:47:52 +02:00
// Not available
2017-01-24 12:10:24 +01:00
val string = SpannableString.valueOf(context.getString(R.string.label_status_not_available))
2016-06-29 15:47:52 +02:00
string.setSpan(ForegroundColorSpan(ThemeUtils.getColorFromAttribute(context,
android.R.attr.textColorTertiary, itemView.text.currentTextColor)), 0,
string.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
itemView.quotedText.text = string
2017-01-21 07:27:34 +01:00
itemView.quotedView.drawStart(ThemeUtils.getColorFromAttribute(context,
R.attr.quoteIndicatorBackgroundColor, 0))
2016-06-29 15:47:52 +02:00
}
} else {
2016-08-30 17:57:37 +02:00
itemView.quotedView.visibility = View.GONE
2016-06-29 15:47:52 +02:00
}
itemView.profileContainer.drawStart(colorNameManager.getUserColor(status.user_key))
2016-06-29 15:47:52 +02:00
val timestamp: Long
if (status.is_retweet) {
timestamp = status.retweet_timestamp
} else {
timestamp = status.timestamp
}
itemView.name.name = colorNameManager.getUserNickname(status.user_key, status.user_name)
itemView.name.screenName = String.format("@%s", status.user_screen_name)
2016-06-29 15:47:52 +02:00
itemView.name.updateText(formatter)
2017-03-02 07:59:19 +01:00
adapter.requestManager.loadProfileImage(context, status).into(itemView.profileImage)
2016-06-29 15:47:52 +02:00
val typeIconRes = Utils.getUserTypeIconRes(status.user_is_verified, status.user_is_protected)
val typeDescriptionRes = Utils.getUserTypeDescriptionRes(status.user_is_verified, status.user_is_protected)
if (typeIconRes != 0 && typeDescriptionRes != 0) {
itemView.profileType.setImageResource(typeIconRes)
itemView.profileType.contentDescription = context.getString(typeDescriptionRes)
itemView.profileType.visibility = View.VISIBLE
} else {
itemView.profileType.setImageDrawable(null)
itemView.profileType.contentDescription = null
itemView.profileType.visibility = View.GONE
}
val timeString = Utils.formatToLongTimeString(context, timestamp)
if (!TextUtils.isEmpty(timeString) && !TextUtils.isEmpty(status.source)) {
2016-12-24 06:48:01 +01:00
itemView.timeSource.text = HtmlSpanBuilder.fromHtml(context.getString(R.string.status_format_time_source, timeString, status.source))
2016-06-29 15:47:52 +02:00
} else if (TextUtils.isEmpty(timeString) && !TextUtils.isEmpty(status.source)) {
2016-12-24 06:48:01 +01:00
itemView.timeSource.text = HtmlSpanBuilder.fromHtml(status.source)
2016-06-29 15:47:52 +02:00
} else if (!TextUtils.isEmpty(timeString) && TextUtils.isEmpty(status.source)) {
itemView.timeSource.text = timeString
}
itemView.timeSource.movementMethod = LinkMovementMethod.getInstance()
var displayEnd = -1
if (status.extras.display_text_range != null) {
displayEnd = status.extras.display_text_range!![1]
}
val text = SpannableStringBuilder.valueOf(status.text_unescaped).apply {
status.spans?.applyTo(this)
linkify.applyAllLinks(this, status.account_key, layoutPosition.toLong(),
status.is_possibly_sensitive, skipLinksInText)
}
2016-06-29 15:47:52 +02:00
if (displayEnd != -1 && displayEnd <= text.length) {
itemView.text.text = text.subSequence(0, displayEnd)
} else {
itemView.text.text = text
}
if (itemView.text.length() == 0) {
// No text
itemView.text.visibility = View.GONE
} else {
itemView.text.visibility = View.VISIBLE
}
2016-08-30 14:23:59 +02:00
val location: ParcelableLocation? = status.location
val placeFullName: String? = status.place_full_name
2016-06-29 15:47:52 +02:00
if (!TextUtils.isEmpty(placeFullName)) {
2016-08-30 14:23:59 +02:00
locationView.visibility = View.VISIBLE
locationView.text = placeFullName
locationView.isClickable = ParcelableLocationUtils.isValidLocation(location)
2016-06-29 15:47:52 +02:00
} else if (ParcelableLocationUtils.isValidLocation(location)) {
2016-08-30 14:23:59 +02:00
locationView.visibility = View.VISIBLE
2016-12-28 08:30:50 +01:00
locationView.setText(R.string.action_view_map)
2016-08-30 14:23:59 +02:00
locationView.isClickable = true
2016-06-29 15:47:52 +02:00
} else {
2016-08-30 14:23:59 +02:00
locationView.visibility = View.GONE
locationView.text = null
2016-06-29 15:47:52 +02:00
}
val interactUsersAdapter = itemView.countsUsers.adapter as CountsUsersAdapter
if (statusActivity != null) {
interactUsersAdapter.setUsers(statusActivity.retweeters)
interactUsersAdapter.setCounts(statusActivity)
} else {
interactUsersAdapter.setUsers(null)
interactUsersAdapter.setCounts(status)
}
if (interactUsersAdapter.itemCount > 0) {
itemView.countsUsers.visibility = View.VISIBLE
itemView.countsUsersHeightHolder.visibility = View.INVISIBLE
} else {
itemView.countsUsers.visibility = View.GONE
itemView.countsUsersHeightHolder.visibility = View.GONE
}
2016-08-30 14:23:59 +02:00
val media = status.media
2016-06-29 15:47:52 +02:00
2016-08-17 15:46:18 +02:00
if (media?.isEmpty() ?: true) {
2016-06-29 15:47:52 +02:00
itemView.mediaPreviewContainer.visibility = View.GONE
itemView.mediaPreview.visibility = View.GONE
itemView.mediaPreviewLoad.visibility = View.GONE
itemView.mediaPreview.displayMedia()
} else if (adapter.isDetailMediaExpanded) {
itemView.mediaPreviewContainer.visibility = View.VISIBLE
itemView.mediaPreview.visibility = View.VISIBLE
itemView.mediaPreviewLoad.visibility = View.GONE
2017-03-02 07:59:19 +01:00
itemView.mediaPreview.displayMedia(adapter.requestManager, media = media,
accountId = status.account_key, mediaClickListener = adapter.fragment)
2016-06-29 15:47:52 +02:00
} else {
itemView.mediaPreviewContainer.visibility = View.VISIBLE
itemView.mediaPreview.visibility = View.GONE
itemView.mediaPreviewLoad.visibility = View.VISIBLE
itemView.mediaPreview.displayMedia()
}
2017-01-25 05:00:13 +01:00
val fm = fragment.childFragmentManager
2016-06-29 15:47:52 +02:00
if (TwitterCardUtils.isCardSupported(status)) {
2016-07-06 15:21:34 +02:00
val size = TwitterCardUtils.getCardSize(status.card!!)
2017-01-25 05:00:13 +01:00
2016-06-29 15:47:52 +02:00
if (size != null) {
itemView.twitterCard.setCardSize(size.x, size.y)
} else {
itemView.twitterCard.setCardSize(0, 0)
}
2017-01-25 05:00:13 +01:00
val vc = TwitterCardViewFactory.from(status)
itemView.twitterCard.viewController = vc
if (vc != null) {
itemView.twitterCard.visibility = View.VISIBLE
2016-06-29 15:47:52 +02:00
} else {
itemView.twitterCard.visibility = View.GONE
}
2017-01-25 05:00:13 +01:00
2016-06-29 15:47:52 +02:00
} else {
2017-01-25 05:00:13 +01:00
itemView.twitterCard.viewController = null
2016-06-29 15:47:52 +02:00
itemView.twitterCard.visibility = View.GONE
}
MenuUtils.setupForStatus(context, fragment.preferences, itemView.menuBar.menu, status,
adapter.statusAccount!!, twitter, colorNameManager)
2016-06-29 15:47:52 +02:00
val lang = status.lang
if (!Utils.isOfficialCredentials(context, account) || !CheckUtils.isValidLocale(lang)) {
itemView.translateLabel.setText(R.string.unknown_language)
itemView.translateContainer.visibility = View.GONE
} else {
val locale = Locale(lang)
itemView.translateContainer.visibility = View.VISIBLE
if (translation != null) {
2017-01-26 14:28:43 +01:00
itemView.translateLabel.text = context.getString(R.string.label_translation)
2016-06-29 15:47:52 +02:00
itemView.translateResult.visibility = View.VISIBLE
itemView.translateResult.text = translation.text
} else {
2017-01-26 14:28:43 +01:00
itemView.translateLabel.text = context.getString(R.string.label_translate_from_language,
2016-06-29 15:47:52 +02:00
locale.displayLanguage)
itemView.translateResult.visibility = View.GONE
}
}
itemView.text.setTextIsSelectable(true)
itemView.translateResult.setTextIsSelectable(true)
itemView.text.movementMethod = LinkMovementMethod.getInstance()
2016-08-31 05:24:31 +02:00
itemView.quotedText.movementMethod = null
2016-06-29 15:47:52 +02:00
}
override fun onClick(v: View) {
val status = adapter.getStatus(layoutPosition) ?: return
val fragment = adapter.fragment
val preferences = fragment.preferences
when (v) {
itemView.mediaPreviewLoad -> {
if (adapter.sensitiveContentEnabled || !status.is_possibly_sensitive) {
adapter.isDetailMediaExpanded = true
} else {
val f = LoadSensitiveImageConfirmDialogFragment()
f.show(fragment.childFragmentManager, "load_sensitive_image_confirm")
}
}
itemView.profileContainer -> {
val activity = fragment.activity
IntentUtils.openUserProfile(activity, status.account_key, status.user_key,
2017-01-20 15:08:42 +01:00
status.user_screen_name, preferences.getBoolean(KEY_NEW_DOCUMENT_API), Referral.STATUS,
null)
2016-06-29 15:47:52 +02:00
}
2016-08-30 14:23:59 +02:00
retweetedByView -> {
2016-06-29 15:47:52 +02:00
if (status.retweet_id != null) {
IntentUtils.openUserProfile(adapter.context, status.account_key,
status.retweeted_by_user_key, status.retweeted_by_user_screen_name,
2017-01-20 15:08:42 +01:00
preferences.getBoolean(KEY_NEW_DOCUMENT_API), Referral.STATUS,
null)
2016-06-29 15:47:52 +02:00
}
}
2016-08-30 14:23:59 +02:00
locationView -> {
2016-06-29 15:47:52 +02:00
val location = status.location
if (!ParcelableLocationUtils.isValidLocation(location)) return
IntentUtils.openMap(adapter.context, location.latitude, location.longitude)
}
2016-08-30 17:57:37 +02:00
itemView.quotedView -> {
2017-01-25 05:15:54 +01:00
val quotedId = status.quoted_id ?: return
IntentUtils.openStatus(adapter.context, status.account_key, quotedId)
2016-06-29 15:47:52 +02:00
}
itemView.translateLabel -> {
fragment.loadTranslation(adapter.status)
}
}
}
override fun onMenuItemClick(item: MenuItem): Boolean {
val layoutPosition = layoutPosition
if (layoutPosition < 0) return false
val fragment = adapter.fragment
val status = adapter.getStatus(layoutPosition) ?: return false
val twitter = fragment.twitterWrapper
val manager = fragment.userColorNameManager
val activity = fragment.activity
val fm = fragment.fragmentManager
if (item.itemId == R.id.retweet) {
RetweetQuoteDialogFragment.show(fm, status)
return true
}
return MenuUtils.handleStatusClick(activity, fragment, fm, manager, twitter,
status, item)
}
private fun initViews() {
// menuBar.setOnMenuItemClickListener(this);
itemView.menuBar.setOnMenuItemClickListener(this)
val fragment = adapter.fragment
val activity = fragment.activity
val inflater = activity.menuInflater
val menu = itemView.menuBar.menu
inflater.inflate(R.menu.menu_detail_status, menu)
val favoriteItem = menu.findItem(R.id.favorite)
val provider = MenuItemCompat.getActionProvider(favoriteItem)
if (provider is FavoriteItemProvider) {
val defaultColor = ThemeUtils.getActionIconColor(activity)
provider.setDefaultColor(defaultColor)
val favoriteHighlight = ContextCompat.getColor(activity, R.color.highlight_favorite)
val likeHighlight = ContextCompat.getColor(activity, R.color.highlight_like)
val useStar = adapter.useStarsForLikes
provider.setActivatedColor(if (useStar) favoriteHighlight else likeHighlight)
provider.setIcon(if (useStar) R.drawable.ic_action_star else R.drawable.ic_action_heart)
provider.setUseStar(useStar)
provider.init(itemView.menuBar, favoriteItem)
}
ThemeUtils.wrapMenuIcon(itemView.menuBar, MENU_GROUP_STATUS_SHARE)
itemView.mediaPreviewLoad.setOnClickListener(this)
itemView.profileContainer.setOnClickListener(this)
2016-08-30 14:23:59 +02:00
retweetedByView.setOnClickListener(this)
locationView.setOnClickListener(this)
2016-08-30 17:57:37 +02:00
itemView.quotedView.setOnClickListener(this)
2016-06-29 15:47:52 +02:00
itemView.translateLabel.setOnClickListener(this)
val textSize = adapter.textSize
itemView.name.setPrimaryTextSize(textSize * 1.25f)
itemView.name.setSecondaryTextSize(textSize * 0.85f)
itemView.text.textSize = textSize * 1.25f
itemView.quotedName.setPrimaryTextSize(textSize * 1.25f)
itemView.quotedName.setSecondaryTextSize(textSize * 0.85f)
itemView.quotedText.textSize = textSize * 1.25f
2016-08-30 14:23:59 +02:00
locationView.textSize = textSize * 0.85f
2016-06-29 15:47:52 +02:00
itemView.timeSource.textSize = textSize * 0.85f
itemView.translateLabel.textSize = textSize * 0.85f
itemView.translateResult.textSize = textSize * 1.05f
itemView.countsUsersHeightHolder.count.textSize = textSize * 1.25f
itemView.countsUsersHeightHolder.label.textSize = textSize * 0.85f
itemView.name.nameFirst = adapter.nameFirst
itemView.quotedName.nameFirst = adapter.nameFirst
2016-06-29 15:47:52 +02:00
itemView.mediaPreview.style = adapter.mediaPreviewStyle
itemView.quotedMediaPreview.style = adapter.mediaPreviewStyle
2016-06-29 15:47:52 +02:00
itemView.text.customSelectionActionModeCallback = StatusActionModeCallback(itemView.text, activity)
2017-01-07 15:45:33 +01:00
itemView.profileImage.style = adapter.profileImageStyle
2016-06-29 15:47:52 +02:00
val layoutManager = LinearLayoutManager(adapter.context)
layoutManager.orientation = LinearLayoutManager.HORIZONTAL
itemView.countsUsers.layoutManager = layoutManager
val countsUsersAdapter = CountsUsersAdapter(fragment, adapter)
itemView.countsUsers.adapter = countsUsersAdapter
val resources = activity.resources
itemView.countsUsers.addItemDecoration(SpacingItemDecoration(resources.getDimensionPixelOffset(R.dimen.element_spacing_normal)))
2017-01-28 08:32:28 +01:00
// Apply font families
itemView.name.applyFontFamily(adapter.lightFont)
itemView.text.applyFontFamily(adapter.lightFont)
2017-01-28 08:32:28 +01:00
itemView.quotedName.applyFontFamily(adapter.lightFont)
itemView.quotedText.applyFontFamily(adapter.lightFont)
2017-01-28 08:32:28 +01:00
itemView.locationView.applyFontFamily(adapter.lightFont)
itemView.translateLabel.applyFontFamily(adapter.lightFont)
itemView.translateResult.applyFontFamily(adapter.lightFont)
2016-06-29 15:47:52 +02:00
}
private class CountsUsersAdapter(
private val fragment: StatusFragment,
private val statusAdapter: StatusAdapter
2017-03-02 07:59:19 +01:00
) : BaseRecyclerViewAdapter<ViewHolder>(statusAdapter.context, Glide.with(fragment)) {
2016-06-29 15:47:52 +02:00
private val inflater = LayoutInflater.from(statusAdapter.context)
2016-06-29 15:47:52 +02:00
private var counts: List<LabeledCount>? = null
private var users: List<ParcelableUser>? = null
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
2016-08-19 16:25:27 +02:00
when (holder.itemViewType) {
2016-06-29 15:47:52 +02:00
ITEM_VIEW_TYPE_USER -> {
(holder as ProfileImageViewHolder).displayUser(getUser(position)!!)
}
ITEM_VIEW_TYPE_COUNT -> {
(holder as CountViewHolder).displayCount(getCount(position)!!)
}
}
}
private fun getCount(position: Int): LabeledCount? {
if (counts == null) return null
if (position < countItemsCount) {
return counts!![position]
}
return null
}
override fun getItemCount(): Int {
return countItemsCount + usersCount
}
override fun getItemViewType(position: Int): Int {
val countItemsCount = countItemsCount
if (position < countItemsCount) {
return ITEM_VIEW_TYPE_COUNT
}
return ITEM_VIEW_TYPE_USER
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
when (viewType) {
ITEM_VIEW_TYPE_USER -> return ProfileImageViewHolder(this, inflater.inflate(R.layout.adapter_item_status_interact_user, parent, false))
ITEM_VIEW_TYPE_COUNT -> return CountViewHolder(this, inflater.inflate(R.layout.adapter_item_status_count_label, parent, false))
}
throw UnsupportedOperationException("Unsupported viewType " + viewType)
}
fun setUsers(users: List<ParcelableUser>?) {
this.users = users
notifyDataSetChanged()
}
fun setCounts(activity: StatusActivity?) {
if (activity != null) {
val counts = ArrayList<LabeledCount>()
val replyCount = activity.replyCount
if (replyCount > 0) {
counts.add(LabeledCount(KEY_REPLY_COUNT, replyCount))
}
val retweetCount = activity.retweetCount
if (retweetCount > 0) {
counts.add(LabeledCount(KEY_RETWEET_COUNT, retweetCount))
}
val favoriteCount = activity.favoriteCount
if (favoriteCount > 0) {
counts.add(LabeledCount(KEY_FAVORITE_COUNT, favoriteCount))
}
this.counts = counts
} else {
counts = null
}
notifyDataSetChanged()
}
fun setCounts(status: ParcelableStatus?) {
if (status != null) {
val counts = ArrayList<LabeledCount>()
if (status.reply_count > 0) {
counts.add(LabeledCount(KEY_REPLY_COUNT, status.reply_count))
}
if (status.retweet_count > 0) {
counts.add(LabeledCount(KEY_RETWEET_COUNT, status.retweet_count))
}
if (status.favorite_count > 0) {
counts.add(LabeledCount(KEY_FAVORITE_COUNT, status.favorite_count))
}
this.counts = counts
} else {
counts = null
}
notifyDataSetChanged()
}
val countItemsCount: Int
get() {
if (counts == null) return 0
return counts!!.size
}
2016-12-06 06:15:22 +01:00
private val usersCount: Int
2016-06-29 15:47:52 +02:00
get() {
if (users == null) return 0
return users!!.size
}
private fun notifyItemClick(position: Int) {
when (getItemViewType(position)) {
ITEM_VIEW_TYPE_COUNT -> {
val count = getCount(position)
val status = statusAdapter.status
if (count == null || status == null) return
when (count.type) {
KEY_RETWEET_COUNT -> {
if (status.is_retweet) {
IntentUtils.openStatusRetweeters(context, status.account_key,
status.retweet_id)
} else {
IntentUtils.openStatusRetweeters(context, status.account_key,
status.id)
}
}
KEY_FAVORITE_COUNT -> {
val account = statusAdapter.statusAccount ?: return
if (!Utils.isOfficialCredentials(context, account)) return
if (status.is_retweet) {
IntentUtils.openStatusFavoriters(context, status.account_key,
status.retweet_id)
} else {
IntentUtils.openStatusFavoriters(context, status.account_key,
status.id)
}
}
}
}
ITEM_VIEW_TYPE_USER -> {
fragment.onUserClick(getUser(position)!!)
}
}
}
private fun getUser(position: Int): ParcelableUser? {
val countItemsCount = countItemsCount
if (users == null || position < countItemsCount) return null
return users!![position - countItemsCount]
}
internal class ProfileImageViewHolder(private val adapter: CountsUsersAdapter, itemView: View) : ViewHolder(itemView), OnClickListener {
2017-03-01 15:12:25 +01:00
private val profileImageView = itemView.findViewById(R.id.profileImage) as ImageView
2016-06-29 15:47:52 +02:00
init {
itemView.setOnClickListener(this)
}
fun displayUser(item: ParcelableUser) {
2017-03-02 02:08:54 +01:00
val context = adapter.context
2017-03-02 07:59:19 +01:00
adapter.requestManager.loadProfileImage(context, item).into(profileImageView)
2016-06-29 15:47:52 +02:00
}
override fun onClick(v: View) {
adapter.notifyItemClick(layoutPosition)
}
}
internal class CountViewHolder(
private val adapter: CountsUsersAdapter,
itemView: View
) : ViewHolder(itemView), OnClickListener {
init {
itemView.setOnClickListener(this)
val textSize = adapter.textSize
itemView.count.textSize = textSize * 1.25f
itemView.label.textSize = textSize * 0.85f
}
override fun onClick(v: View) {
adapter.notifyItemClick(layoutPosition)
}
fun displayCount(count: LabeledCount) {
val label: String
when (count.type) {
KEY_REPLY_COUNT -> {
label = adapter.context.getString(R.string.replies)
}
KEY_RETWEET_COUNT -> {
2016-12-26 17:26:09 +01:00
label = adapter.context.getString(R.string.count_label_retweets)
2016-06-29 15:47:52 +02:00
}
KEY_FAVORITE_COUNT -> {
2016-12-26 17:26:09 +01:00
label = adapter.context.getString(R.string.title_favorites)
2016-06-29 15:47:52 +02:00
}
else -> {
throw UnsupportedOperationException("Unsupported type " + count.type)
}
}
itemView.count.text = Utils.getLocalizedNumber(Locale.getDefault(), count.count)
itemView.label.text = label
}
}
internal class LabeledCount(var type: Int, var count: Long)
companion object {
private val ITEM_VIEW_TYPE_USER = 1
private val ITEM_VIEW_TYPE_COUNT = 2
private val KEY_REPLY_COUNT = 1
private val KEY_RETWEET_COUNT = 2
private val KEY_FAVORITE_COUNT = 3
}
}
2016-07-04 03:31:17 +02:00
private class DetailStatusLinkClickHandler(
context: Context,
manager: MultiSelectManager,
private val adapter: StatusAdapter,
preferences: SharedPreferences
2016-07-04 03:31:17 +02:00
) : StatusLinkClickHandler(context, manager, preferences) {
2016-06-29 15:47:52 +02:00
2016-07-16 09:27:46 +02:00
override fun onLinkClick(link: String, orig: String?, accountKey: UserKey?,
2017-02-12 03:39:47 +01:00
extraId: Long, type: Int, sensitive: Boolean, start: Int, end: Int): Boolean {
2016-06-29 15:47:52 +02:00
val current = getCurrentMedia(link, extraId.toInt())
if (current != null && !current.open_browser) {
expandOrOpenMedia(current)
return true
}
return super.onLinkClick(link, orig, accountKey, extraId, type, sensitive, start, end)
}
private fun expandOrOpenMedia(current: ParcelableMedia) {
if (adapter.isDetailMediaExpanded) {
2017-01-20 15:08:42 +01:00
IntentUtils.openMedia(adapter.context, adapter.status!!, current,
preferences[newDocumentApiKey], preferences[displaySensitiveContentsKey])
2016-06-29 15:47:52 +02:00
return
}
adapter.isDetailMediaExpanded = true
}
override fun isMedia(link: String, extraId: Long): Boolean {
val current = getCurrentMedia(link, extraId.toInt())
return current != null && !current.open_browser
}
private fun getCurrentMedia(link: String, extraId: Int): ParcelableMedia? {
val status = adapter.getStatus(extraId)
val media = ParcelableMediaUtils.getAllMedia(status)
return StatusLinkClickHandler.findByLink(media, link)
}
}
private class SpacingItemDecoration(private val spacing: Int) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State?) {
if (ViewCompat.getLayoutDirection(parent) == ViewCompat.LAYOUT_DIRECTION_RTL) {
outRect.set(spacing, 0, 0, 0)
} else {
outRect.set(0, 0, spacing, 0)
}
}
}
}
private class SpaceViewHolder(itemView: View) : ViewHolder(itemView)
2017-03-01 15:12:25 +01:00
class StatusAdapter(
val fragment: StatusFragment
2017-03-02 07:59:19 +01:00
) : LoadMoreSupportAdapter<ViewHolder>(fragment.context, Glide.with(fragment)), IStatusesAdapter<List<ParcelableStatus>> {
2016-06-29 15:47:52 +02:00
private val inflater: LayoutInflater
override val twidereLinkify: TwidereLinkify
override var statusClickListener: StatusClickListener? = null
private var recyclerView: RecyclerView? = null
private var statusViewHolder: DetailStatusViewHolder? = null
2017-02-04 11:42:14 +01:00
private val itemCounts = ItemCounts(ITEM_TYPES_SUM)
2016-06-29 15:47:52 +02:00
private val cardBackgroundColor: Int
override val nameFirst = preferences[nameFirstKey]
override val mediaPreviewStyle = preferences[mediaPreviewStyleKey]
override val linkHighlightingStyle = preferences[linkHighlightOptionKey]
override val lightFont = preferences[lightFontKey]
override val mediaPreviewEnabled = preferences[mediaPreviewKey]
override val sensitiveContentEnabled = preferences[displaySensitiveContentsKey]
private val showCardActions = !preferences[hideCardActionsKey]
override val useStarsForLikes = preferences[iWantMyStarsBackKey]
2016-06-29 15:47:52 +02:00
private var mDetailMediaExpanded: Boolean = false
var status: ParcelableStatus? = null
2016-12-04 04:58:03 +01:00
internal set
2016-06-29 15:47:52 +02:00
var translationResult: TranslationResult? = null
2016-12-04 04:58:03 +01:00
internal set(translation) {
2016-06-29 15:47:52 +02:00
if (status == null || translation == null || !TextUtils.equals(InternalTwitterContentUtils.getOriginalId(status!!), translation.id)) {
2016-07-10 15:27:16 +02:00
field = null
2016-06-29 15:47:52 +02:00
} else {
2016-07-10 15:27:16 +02:00
field = translation
2016-06-29 15:47:52 +02:00
}
notifyDataSetChanged()
}
2016-12-04 04:58:03 +01:00
var statusActivity: StatusActivity? = null
internal set(value) {
val status = status ?: return
if (value != null && value.isStatus(status)) {
return
}
field = value
notifyDataSetChanged()
}
var statusAccount: AccountDetails? = null
internal set
2016-06-29 15:47:52 +02:00
private var data: List<ParcelableStatus>? = null
private var replyError: CharSequence? = null
private var conversationError: CharSequence? = null
2017-02-10 12:55:00 +01:00
private var replyStart: Int = 0
private var showingActionCardPosition: Int = 0
2016-06-29 15:47:52 +02:00
init {
setHasStableIds(true)
val context = fragment.activity
// There's always a space at the end of the list
2017-01-20 15:08:42 +01:00
itemCounts[ITEM_IDX_SPACE] = 1
itemCounts[ITEM_IDX_STATUS] = 1
itemCounts[ITEM_IDX_CONVERSATION_LOAD_MORE] = 1
itemCounts[ITEM_IDX_REPLY_LOAD_MORE] = 1
2016-06-29 15:47:52 +02:00
inflater = LayoutInflater.from(context)
cardBackgroundColor = ThemeUtils.getCardBackgroundColor(context,
ThemeUtils.getThemeBackgroundOption(context),
ThemeUtils.getUserThemeBackgroundAlpha(context))
2017-01-20 15:08:42 +01:00
val listener = StatusAdapterLinkClickHandler<List<ParcelableStatus>>(context, preferences)
2016-06-29 15:47:52 +02:00
listener.setAdapter(this)
twidereLinkify = TwidereLinkify(listener)
}
override fun getStatus(position: Int): ParcelableStatus? {
val itemType = getItemType(position)
when (itemType) {
ITEM_IDX_CONVERSATION -> {
2017-02-14 18:09:56 +01:00
return data?.get(position - getIndexStart(ITEM_IDX_CONVERSATION))
2016-06-29 15:47:52 +02:00
}
ITEM_IDX_REPLY -> {
2017-02-14 18:09:56 +01:00
if (replyStart < 0) return null
return data?.get(position - getIndexStart(ITEM_IDX_CONVERSATION)
- getTypeCount(ITEM_IDX_CONVERSATION) - getTypeCount(ITEM_IDX_STATUS) + replyStart)
2016-06-29 15:47:52 +02:00
}
ITEM_IDX_STATUS -> {
return status
}
}
return null
}
fun getIndexStart(index: Int): Int {
if (index == 0) return 0
2017-02-04 11:42:14 +01:00
return itemCounts.getItemStartPosition(index)
2016-06-29 15:47:52 +02:00
}
override fun getStatusId(position: Int): String? {
val status = getStatus(position)
return status?.id
}
override fun getStatusTimestamp(position: Int): Long {
val status = getStatus(position)
2016-12-06 06:15:22 +01:00
return status?.timestamp ?: -1
2016-06-29 15:47:52 +02:00
}
override fun getStatusPositionKey(position: Int): Long {
val status = getStatus(position) ?: return -1
return if (status.position_key > 0) status.timestamp else getStatusTimestamp(position)
}
override fun getAccountKey(position: Int): UserKey? {
val status = getStatus(position)
return status?.account_key
}
override fun findStatusById(accountKey: UserKey, statusId: String): ParcelableStatus? {
if (status != null && accountKey == status!!.account_key && TextUtils.equals(statusId, status!!.id)) {
return status
}
2016-12-06 06:15:22 +01:00
return data?.firstOrNull { accountKey == it.account_key && TextUtils.equals(it.id, statusId) }
2016-06-29 15:47:52 +02:00
}
override val statusCount: Int
get() = rawStatusCount
override val rawStatusCount: Int
get() {
return getTypeCount(ITEM_IDX_CONVERSATION) + getTypeCount(ITEM_IDX_STATUS) + getTypeCount(ITEM_IDX_REPLY)
}
override fun isCardActionsShown(position: Int): Boolean {
2017-02-10 12:55:00 +01:00
if (position == RecyclerView.NO_POSITION) return showCardActions
return showCardActions || showingActionCardPosition == position
2016-06-29 15:47:52 +02:00
}
override fun showCardActions(position: Int) {
2017-02-10 12:55:00 +01:00
if (showingActionCardPosition != RecyclerView.NO_POSITION) {
notifyItemChanged(showingActionCardPosition)
2016-06-29 15:47:52 +02:00
}
2017-02-10 12:55:00 +01:00
showingActionCardPosition = position
2016-06-29 15:47:52 +02:00
if (position != RecyclerView.NO_POSITION) {
notifyItemChanged(position)
}
}
override fun setData(data: List<ParcelableStatus>?): Boolean {
val status = this.status ?: return false
2017-02-10 12:55:00 +01:00
val changed = this.data != data
2016-06-29 15:47:52 +02:00
this.data = data
if (data == null || data.isEmpty()) {
setTypeCount(ITEM_IDX_CONVERSATION, 0)
setTypeCount(ITEM_IDX_REPLY, 0)
2017-02-10 12:55:00 +01:00
replyStart = -1
2016-06-29 15:47:52 +02:00
} else {
var sortId = status.sort_id
if (status.is_retweet) {
for (item in data) {
if (TextUtils.equals(status.retweet_id, item.id)) {
sortId = item.sort_id
break
}
}
}
var conversationCount = 0
var replyCount = 0
var replyStart = -1
2017-02-10 12:55:00 +01:00
data.forEachIndexed { i, item ->
2016-06-29 15:47:52 +02:00
if (item.sort_id < sortId) {
conversationCount++
2017-02-14 18:09:56 +01:00
} else if (status.id == item.id) {
this.status = item
} else if (item.sort_id > sortId) {
2016-06-29 15:47:52 +02:00
if (replyStart < 0) {
replyStart = i
}
replyCount++
}
}
setTypeCount(ITEM_IDX_CONVERSATION, conversationCount)
setTypeCount(ITEM_IDX_REPLY, replyCount)
2017-02-10 12:55:00 +01:00
this.replyStart = replyStart
2016-06-29 15:47:52 +02:00
}
notifyDataSetChanged()
updateItemDecoration()
return changed
}
override val showAccountsColor: Boolean
get() = false
var isDetailMediaExpanded: Boolean
get() {
if (mDetailMediaExpanded) return true
if (mediaPreviewEnabled) {
val status = this.status
return status != null && (sensitiveContentEnabled || !status.is_possibly_sensitive)
}
return false
}
set(expanded) {
mDetailMediaExpanded = expanded
notifyDataSetChanged()
updateItemDecoration()
}
override fun isGapItem(position: Int): Boolean {
return false
}
override val gapClickListener: IGapSupportedAdapter.GapClickListener?
get() = statusClickListener
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder? {
when (viewType) {
VIEW_TYPE_DETAIL_STATUS -> {
if (statusViewHolder != null) {
return statusViewHolder
}
val view = inflater.inflate(R.layout.header_status_compact, parent, false)
val cardView = view.findViewById(R.id.compact_card)
cardView.setBackgroundColor(cardBackgroundColor)
return DetailStatusViewHolder(this, view)
}
VIEW_TYPE_LIST_STATUS -> {
return ListParcelableStatusesAdapter.createStatusViewHolder(this, inflater, parent)
}
VIEW_TYPE_CONVERSATION_LOAD_INDICATOR, VIEW_TYPE_REPLIES_LOAD_INDICATOR -> {
2017-01-31 14:10:20 +01:00
val view = inflater.inflate(R.layout.list_item_load_indicator, parent,
2016-06-29 15:47:52 +02:00
false)
return LoadIndicatorViewHolder(view)
}
VIEW_TYPE_SPACE -> {
return SpaceViewHolder(Space(context))
}
VIEW_TYPE_REPLY_ERROR -> {
val view = inflater.inflate(R.layout.adapter_item_status_error, parent,
false)
return StatusErrorItemViewHolder(view)
}
}
return null
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
2016-08-19 16:25:27 +02:00
when (holder.itemViewType) {
2016-06-29 15:47:52 +02:00
VIEW_TYPE_DETAIL_STATUS -> {
val status = getStatus(position)
val detailHolder = holder as DetailStatusViewHolder
2016-12-04 04:58:03 +01:00
detailHolder.displayStatus(statusAccount, status, statusActivity,
2016-06-29 15:47:52 +02:00
translationResult)
}
VIEW_TYPE_LIST_STATUS -> {
val status = getStatus(position)
val statusHolder = holder as IStatusViewHolder
// Display 'in reply to' for first item
// useful to indicate whether first tweet has reply or not
// We only display that indicator for first conversation item
2016-08-19 16:25:27 +02:00
val itemType = getItemType(position)
2017-02-28 07:35:31 +01:00
val displayInReplyTo = itemType == ITEM_IDX_CONVERSATION && position - getItemTypeStart(position) == 0
statusHolder.displayStatus(status = status!!, displayInReplyTo = displayInReplyTo)
2016-06-29 15:47:52 +02:00
}
VIEW_TYPE_REPLY_ERROR -> {
val errorHolder = holder as StatusErrorItemViewHolder
errorHolder.showError(replyError!!)
}
VIEW_TYPE_CONVERSATION_ERROR -> {
val errorHolder = holder as StatusErrorItemViewHolder
errorHolder.showError(conversationError!!)
}
VIEW_TYPE_CONVERSATION_LOAD_INDICATOR -> {
val indicatorHolder = holder as LoadIndicatorViewHolder
indicatorHolder.setLoadProgressVisible(isConversationsLoading)
}
VIEW_TYPE_REPLIES_LOAD_INDICATOR -> {
val indicatorHolder = holder as LoadIndicatorViewHolder
indicatorHolder.setLoadProgressVisible(isRepliesLoading)
}
}
}
override fun onViewDetachedFromWindow(holder: ViewHolder?) {
if (holder is DetailStatusViewHolder) {
statusViewHolder = holder as DetailStatusViewHolder?
}
super.onViewDetachedFromWindow(holder)
}
override fun onViewAttachedToWindow(holder: ViewHolder?) {
if (holder === statusViewHolder) {
statusViewHolder = null
}
super.onViewAttachedToWindow(holder)
}
override fun getItemViewType(position: Int): Int {
return getItemViewTypeByItemType(getItemType(position))
}
2016-12-06 04:08:56 +01:00
override fun addGapLoadingId(id: ObjectId) {
}
override fun removeGapLoadingId(id: ObjectId) {
}
2016-06-29 15:47:52 +02:00
private fun getItemViewTypeByItemType(type: Int): Int {
when (type) {
ITEM_IDX_CONVERSATION, ITEM_IDX_REPLY -> return VIEW_TYPE_LIST_STATUS
ITEM_IDX_CONVERSATION_LOAD_MORE -> return VIEW_TYPE_CONVERSATION_LOAD_INDICATOR
ITEM_IDX_REPLY_LOAD_MORE -> return VIEW_TYPE_REPLIES_LOAD_INDICATOR
ITEM_IDX_STATUS -> return VIEW_TYPE_DETAIL_STATUS
ITEM_IDX_SPACE -> return VIEW_TYPE_SPACE
ITEM_IDX_REPLY_ERROR -> return VIEW_TYPE_REPLY_ERROR
ITEM_IDX_CONVERSATION_ERROR -> return VIEW_TYPE_CONVERSATION_ERROR
}
throw IllegalStateException()
}
fun getItemType(position: Int): Int {
var typeStart = 0
for (type in 0..ITEM_TYPES_SUM - 1) {
val typeCount = getTypeCount(type)
val typeEnd = typeStart + typeCount
if (position >= typeStart && position < typeEnd) return type
typeStart = typeEnd
}
throw IllegalStateException("Unknown position " + position)
}
fun getItemTypeStart(position: Int): Int {
var typeStart = 0
for (type in 0..ITEM_TYPES_SUM - 1) {
val typeCount = getTypeCount(type)
val typeEnd = typeStart + typeCount
if (position >= typeStart && position < typeEnd) return typeStart
typeStart = typeEnd
}
throw IllegalStateException()
}
override fun getItemId(position: Int): Long {
val status = getStatus(position)
if (status != null) return status.hashCode().toLong()
return getItemType(position).toLong()
}
override fun getItemCount(): Int {
if (status == null) return 0
2017-02-04 11:42:14 +01:00
return itemCounts.itemCount
2016-06-29 15:47:52 +02:00
}
override fun onAttachedToRecyclerView(recyclerView: RecyclerView?) {
super.onAttachedToRecyclerView(recyclerView)
this.recyclerView = recyclerView
}
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView?) {
super.onDetachedFromRecyclerView(recyclerView)
this.recyclerView = null
}
private fun setTypeCount(idx: Int, size: Int) {
2017-01-20 15:08:42 +01:00
itemCounts[idx] = size
2016-06-29 15:47:52 +02:00
notifyDataSetChanged()
}
fun getTypeCount(idx: Int): Int {
2017-01-20 15:08:42 +01:00
return itemCounts[idx]
2016-06-29 15:47:52 +02:00
}
fun setReplyError(error: CharSequence?) {
replyError = error
setTypeCount(ITEM_IDX_REPLY_ERROR, if (error != null) 1 else 0)
updateItemDecoration()
}
fun setConversationError(error: CharSequence?) {
conversationError = error
setTypeCount(ITEM_IDX_CONVERSATION_ERROR, if (error != null) 1 else 0)
updateItemDecoration()
}
2016-12-18 03:04:02 +01:00
fun setStatus(status: ParcelableStatus, account: AccountDetails?): Boolean {
2016-06-29 15:47:52 +02:00
val old = this.status
this.status = status
2016-12-04 04:58:03 +01:00
statusAccount = account
2016-06-29 15:47:52 +02:00
notifyDataSetChanged()
updateItemDecoration()
return !CompareUtils.objectEquals(old, status)
}
fun updateItemDecoration() {
if (recyclerView == null) return
}
fun getFirstPositionOfItem(itemIdx: Int): Int {
var position = 0
for (i in 0..ITEM_TYPES_SUM - 1) {
if (itemIdx == i) return position
position += getTypeCount(i)
}
return RecyclerView.NO_POSITION
}
fun getData(): List<ParcelableStatus>? {
return data
}
var isConversationsLoading: Boolean
get() = ILoadMoreSupportAdapter.START in loadMoreIndicatorPosition
2016-06-29 15:47:52 +02:00
set(loading) {
if (loading) {
loadMoreIndicatorPosition = loadMoreIndicatorPosition or ILoadMoreSupportAdapter.START
} else {
loadMoreIndicatorPosition = loadMoreIndicatorPosition and ILoadMoreSupportAdapter.START.inv()
}
updateItemDecoration()
}
var isRepliesLoading: Boolean
get() = ILoadMoreSupportAdapter.END in loadMoreIndicatorPosition
2016-06-29 15:47:52 +02:00
set(loading) {
if (loading) {
loadMoreIndicatorPosition = loadMoreIndicatorPosition or ILoadMoreSupportAdapter.END
} else {
loadMoreIndicatorPosition = loadMoreIndicatorPosition and ILoadMoreSupportAdapter.END.inv()
}
updateItemDecoration()
}
class StatusErrorItemViewHolder(itemView: View) : ViewHolder(itemView) {
private val textView = itemView.findViewById(android.R.id.text1) as TextView
2016-06-29 15:47:52 +02:00
init {
textView.movementMethod = LinkMovementMethod.getInstance()
textView.linksClickable = true
}
fun showError(text: CharSequence) {
textView.text = text
}
}
companion object {
const val VIEW_TYPE_LIST_STATUS = 0
const val VIEW_TYPE_DETAIL_STATUS = 1
const val VIEW_TYPE_CONVERSATION_LOAD_INDICATOR = 2
const val VIEW_TYPE_REPLIES_LOAD_INDICATOR = 3
const val VIEW_TYPE_REPLY_ERROR = 4
const val VIEW_TYPE_CONVERSATION_ERROR = 5
const val VIEW_TYPE_SPACE = 6
const val ITEM_IDX_CONVERSATION_LOAD_MORE = 0
const val ITEM_IDX_CONVERSATION_ERROR = 1
const val ITEM_IDX_CONVERSATION = 2
const val ITEM_IDX_STATUS = 3
const val ITEM_IDX_REPLY = 4
const val ITEM_IDX_REPLY_ERROR = 5
const val ITEM_IDX_REPLY_LOAD_MORE = 6
const val ITEM_IDX_SPACE = 7
const val ITEM_TYPES_SUM = 8
}
}
private class StatusListLinearLayoutManager(context: Context, private val recyclerView: RecyclerView) : FixedLinearLayoutManager(context) {
private var spaceHeight: Int = 0
init {
orientation = LinearLayoutManager.VERTICAL
}
override fun getDecoratedMeasuredHeight(child: View): Int {
if (getItemViewType(child) == StatusAdapter.VIEW_TYPE_SPACE) {
2017-03-02 02:24:09 +01:00
val height = calculateSpaceItemHeight(child, StatusAdapter.VIEW_TYPE_SPACE,
2017-02-21 16:12:03 +01:00
StatusAdapter.VIEW_TYPE_DETAIL_STATUS)
2017-03-02 02:24:09 +01:00
if (height >= 0) {
return height
}
2016-06-29 15:47:52 +02:00
}
return super.getDecoratedMeasuredHeight(child)
}
override fun setOrientation(orientation: Int) {
if (orientation != LinearLayoutManager.VERTICAL)
throw IllegalArgumentException("Only VERTICAL orientation supported")
super.setOrientation(orientation)
}
override fun computeVerticalScrollExtent(state: RecyclerView.State?): Int {
val firstPosition = findFirstVisibleItemPosition()
val lastPosition = Math.min(validScrollItemCount - 1, findLastVisibleItemPosition())
if (firstPosition < 0 || lastPosition < 0) return 0
val childCount = lastPosition - firstPosition + 1
if (childCount > 0) {
if (isSmoothScrollbarEnabled) {
var extent = childCount * 100
var view = findViewByPosition(firstPosition)
val top = view.top
var height = view.height
if (height > 0) {
extent += top * 100 / height
}
view = findViewByPosition(lastPosition)
val bottom = view.bottom
height = view.height
if (height > 0) {
extent -= (bottom - getHeight()) * 100 / height
}
return extent
} else {
return 1
}
}
return 0
}
override fun computeVerticalScrollOffset(state: RecyclerView.State?): Int {
val firstPosition = findFirstVisibleItemPosition()
val lastPosition = Math.min(validScrollItemCount - 1, findLastVisibleItemPosition())
if (firstPosition < 0 || lastPosition < 0) return 0
val childCount = lastPosition - firstPosition + 1
val skippedCount = skippedScrollItemCount
if (firstPosition >= skippedCount && childCount > 0) {
if (isSmoothScrollbarEnabled) {
val view = findViewByPosition(firstPosition)
val top = view.top
val height = view.height
if (height > 0) {
return Math.max((firstPosition - skippedCount) * 100 - top * 100 / height, 0)
}
} else {
val index: Int
val count = validScrollItemCount
if (firstPosition == 0) {
index = 0
} else if (firstPosition + childCount == count) {
index = count
} else {
index = firstPosition + childCount / 2
}
return (firstPosition + childCount * (index / count.toFloat())).toInt()
}
}
return 0
}
override fun computeVerticalScrollRange(state: RecyclerView.State?): Int {
val result: Int
if (isSmoothScrollbarEnabled) {
result = Math.max(validScrollItemCount * 100, 0)
} else {
result = validScrollItemCount
}
return result
}
private val skippedScrollItemCount: Int
get() {
val adapter = recyclerView.adapter as StatusAdapter
var skipped = 0
if (!adapter.isConversationsLoading) {
skipped += adapter.getTypeCount(StatusAdapter.ITEM_IDX_CONVERSATION_LOAD_MORE)
}
return skipped
}
private val validScrollItemCount: Int
get() {
val adapter = recyclerView.adapter as StatusAdapter
var count = 0
if (adapter.isConversationsLoading) {
count += adapter.getTypeCount(StatusAdapter.ITEM_IDX_CONVERSATION_LOAD_MORE)
}
count += adapter.getTypeCount(StatusAdapter.ITEM_IDX_CONVERSATION_ERROR)
count += adapter.getTypeCount(StatusAdapter.ITEM_IDX_CONVERSATION)
count += adapter.getTypeCount(StatusAdapter.ITEM_IDX_STATUS)
count += adapter.getTypeCount(StatusAdapter.ITEM_IDX_REPLY)
count += adapter.getTypeCount(StatusAdapter.ITEM_IDX_REPLY_ERROR)
if (adapter.isRepliesLoading) {
count += adapter.getTypeCount(StatusAdapter.ITEM_IDX_REPLY_LOAD_MORE)
}
2016-08-25 04:10:53 +02:00
val spaceHeight = calculateSpaceHeight()
2016-06-29 15:47:52 +02:00
if (spaceHeight > 0) {
count += adapter.getTypeCount(StatusAdapter.ITEM_IDX_SPACE)
}
return count
}
private fun calculateSpaceHeight(): Int {
val space = findViewByPosition(itemCount - 1) ?: return spaceHeight
2017-02-21 16:12:03 +01:00
spaceHeight = getDecoratedMeasuredHeight(space)
return spaceHeight
2016-06-29 15:47:52 +02:00
}
}
2017-01-17 18:59:44 +01:00
class StatusActivitySummaryLoader(
context: Context,
private val accountKey: UserKey,
private val statusId: String
2017-02-07 16:36:29 +01:00
) : FixedAsyncTaskLoader<StatusActivity>(context) {
2016-06-29 15:47:52 +02:00
override fun loadInBackground(): StatusActivity? {
val context = context
2017-01-17 18:59:44 +01:00
val details = AccountUtils.getAccountDetails(AccountManager.get(context), accountKey, true) ?: return null
2016-12-04 06:45:57 +01:00
if (AccountType.TWITTER != details.type) {
2016-06-29 15:47:52 +02:00
return null
}
2017-01-17 18:59:44 +01:00
val twitter = MicroBlogAPIFactory.getInstance(context, accountKey) ?: return null
2016-06-29 15:47:52 +02:00
val paging = Paging()
paging.setCount(10)
2017-01-17 18:59:44 +01:00
val activitySummary = StatusActivity(statusId, emptyList())
2016-06-29 15:47:52 +02:00
val retweeters = ArrayList<ParcelableUser>()
try {
2017-01-17 18:59:44 +01:00
for (status in twitter.getRetweets(statusId, paging)) {
val user = ParcelableUserUtils.fromUser(status.user, accountKey)
2016-06-29 15:47:52 +02:00
if (!DataStoreUtils.isFilteringUser(context, user.key.toString())) {
retweeters.add(user)
}
}
activitySummary.retweeters = retweeters
val countValues = ContentValues()
2017-01-17 18:59:44 +01:00
val status = twitter.showStatus(statusId)
2016-06-29 15:47:52 +02:00
activitySummary.favoriteCount = status.favoriteCount
activitySummary.retweetCount = status.retweetCount
activitySummary.replyCount = status.replyCount
countValues.put(Statuses.REPLY_COUNT, activitySummary.replyCount)
countValues.put(Statuses.FAVORITE_COUNT, activitySummary.favoriteCount)
countValues.put(Statuses.RETWEET_COUNT, activitySummary.retweetCount)
val cr = context.contentResolver
val statusWhere = Expression.and(
Expression.equalsArgs(Statuses.ACCOUNT_KEY),
Expression.or(
Expression.equalsArgs(Statuses.STATUS_ID),
Expression.equalsArgs(Statuses.RETWEET_ID)))
2017-01-17 18:59:44 +01:00
val statusWhereArgs = arrayOf(accountKey.toString(), statusId, statusId)
2016-06-29 15:47:52 +02:00
cr.update(Statuses.CONTENT_URI, countValues, statusWhere.sql, statusWhereArgs)
val activityWhere = Expression.and(
Expression.equalsArgs(Activities.ACCOUNT_KEY),
Expression.or(
Expression.equalsArgs(Activities.STATUS_ID),
Expression.equalsArgs(Activities.STATUS_RETWEET_ID)))
val pStatus = ParcelableStatusUtils.fromStatus(status,
2017-01-17 18:59:44 +01:00
accountKey, false)
2017-03-05 09:08:09 +01:00
cr.insert(CachedStatuses.CONTENT_URI, ObjectCursor.valuesCreatorFrom(ParcelableStatus::class.java).create(pStatus))
2016-06-29 15:47:52 +02:00
val activityCursor = cr.query(Activities.AboutMe.CONTENT_URI,
Activities.COLUMNS, activityWhere.sql, statusWhereArgs, null)!!
try {
activityCursor.moveToFirst()
2017-03-05 09:08:09 +01:00
val ci = ObjectCursor.indicesFrom(activityCursor, ParcelableActivity::class.java)
val vc = ObjectCursor.valuesCreatorFrom(ParcelableActivity::class.java)
2016-06-29 15:47:52 +02:00
while (!activityCursor.isAfterLast) {
val activity = ci.newObject(activityCursor)
val activityStatus = activity.getActivityStatus()
if (activityStatus != null) {
activityStatus.favorite_count = activitySummary.favoriteCount
activityStatus.reply_count = activitySummary.replyCount
activityStatus.retweet_count = activitySummary.retweetCount
}
2017-03-05 09:08:09 +01:00
cr.update(Activities.AboutMe.CONTENT_URI, vc.create(activity),
2016-06-29 15:47:52 +02:00
Expression.equals(Activities._ID, activity._id).sql, null)
activityCursor.moveToNext()
}
} finally {
activityCursor.close()
}
return activitySummary
} catch (e: MicroBlogException) {
return null
}
}
override fun onStartLoading() {
forceLoad()
}
}
data class StatusActivity(
var statusId: String,
var retweeters: List<ParcelableUser>,
var favoriteCount: Long = 0,
var replyCount: Long = -1,
var retweetCount: Long = 0
) {
fun isStatus(status: ParcelableStatus): Boolean {
return TextUtils.equals(statusId, if (status.is_retweet) status.retweet_id else status.id)
}
}
data class ReadPosition(var statusId: Long, var offsetTop: Int)
2017-01-17 18:59:44 +01:00
private class StatusDividerItemDecoration(
context: Context,
private val statusAdapter: StatusAdapter,
orientation: Int
) : DividerItemDecoration(context, orientation) {
2016-06-29 15:47:52 +02:00
override fun isDividerEnabled(childPos: Int): Boolean {
if (childPos >= statusAdapter.itemCount || childPos < 0) return false
val itemType = statusAdapter.getItemType(childPos)
when (itemType) {
2017-01-17 18:59:44 +01:00
StatusAdapter.ITEM_IDX_REPLY_LOAD_MORE, StatusAdapter.ITEM_IDX_REPLY_ERROR,
StatusAdapter.ITEM_IDX_SPACE -> return false
2016-06-29 15:47:52 +02:00
}
return true
}
}
companion object {
// Constants
private val LOADER_ID_DETAIL_STATUS = 1
private val LOADER_ID_STATUS_CONVERSATIONS = 2
private val LOADER_ID_STATUS_ACTIVITY = 3
private val STATE_LOADED = 1
private val STATE_LOADING = 2
private val STATE_ERROR = 3
}
}