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
|
2016-12-24 13:47:35 +01:00
|
|
|
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.FragmentManagerAccessor
|
|
|
|
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.AsyncTaskLoader
|
|
|
|
import android.support.v4.content.ContextCompat
|
|
|
|
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.LayoutParams
|
|
|
|
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
|
|
|
|
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.*
|
2016-12-24 13:47:35 +01:00
|
|
|
import org.mariotaku.kpreferences.get
|
2016-08-21 15:30:07 +02:00
|
|
|
import org.mariotaku.ktextension.findPositionByItemId
|
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.*
|
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
|
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
|
2016-06-29 15:47:52 +02:00
|
|
|
import org.mariotaku.twidere.model.message.FavoriteTaskEvent
|
|
|
|
import org.mariotaku.twidere.model.message.StatusListChangedEvent
|
|
|
|
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
|
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>>,
|
2016-06-29 15:47:52 +02:00
|
|
|
OnMediaClickListener, StatusClickListener, KeyboardShortcutCallback, ContentListSupport {
|
|
|
|
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
|
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
|
2016-07-02 06:37:42 +02: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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onMediaClick(holder: IStatusViewHolder, view: View, media: ParcelableMedia, statusPosition: Int) {
|
2016-12-08 15:56:21 +01:00
|
|
|
val status = adapter.getStatus(statusPosition) ?: return
|
2017-01-20 15:08:42 +01:00
|
|
|
IntentUtils.openMedia(activity, status, media, preferences[newDocumentApiKey],
|
|
|
|
preferences[displaySensitiveContentsKey])
|
2016-06-29 15:47:52 +02:00
|
|
|
|
|
|
|
val event = MediaEvent.create(activity, status, media, 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) {
|
2016-12-08 15:56:21 +01:00
|
|
|
val status = adapter.getStatus(position)
|
2016-06-29 15:47:52 +02:00
|
|
|
AbsStatusesFragment.handleStatusActionClick(context, fragmentManager, twitterWrapper,
|
|
|
|
holder as StatusViewHolder, status, id)
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
IntentUtils.openStatus(activity, status.account_key, status.quoted_id)
|
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,
|
2017-01-20 15:08:42 +01:00
|
|
|
status.user_screen_name, preferences.getBoolean(KEY_NEW_DOCUMENT_API), Referral.TIMELINE_STATUS,
|
|
|
|
null)
|
2016-06-29 15:47:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
override fun onMediaClick(view: View, media: ParcelableMedia?, accountKey: UserKey, extraId: Long) {
|
2016-12-08 15:56:21 +01:00
|
|
|
val status = adapter.status
|
2016-06-29 15:47:52 +02:00
|
|
|
if (status == null || media == null) return
|
2017-01-20 15:08:42 +01:00
|
|
|
IntentUtils.openMediaDirectly(activity, accountKey, status, media, preferences.getBoolean(KEY_NEW_DOCUMENT_API),
|
|
|
|
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,
|
|
|
|
keyCode: Int, event: KeyEvent,
|
|
|
|
metaState: Int): Boolean {
|
|
|
|
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 {
|
|
|
|
twitter.createFavoriteAsync(status.account_key, status.id)
|
|
|
|
}
|
|
|
|
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,
|
|
|
|
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>>,
|
|
|
|
data: SingleResponse<ParcelableStatus>) {
|
|
|
|
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
|
2016-12-15 13:27:55 +01:00
|
|
|
get() = layoutManager.findLastCompletelyVisibleItemPosition() >= adapter.itemCount - 1
|
2016-06-29 15:47:52 +02:00
|
|
|
|
|
|
|
override val reachingStart: Boolean
|
2016-12-15 13:27:55 +01:00
|
|
|
get() = layoutManager.findFirstCompletelyVisibleItemPosition() <= 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)
|
2017-01-15 18:55:53 +01:00
|
|
|
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)
|
|
|
|
return builder.create()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
internal class LoadTranslationTask(val fragment: StatusFragment) : AsyncTask<ParcelableStatus, Any, SingleResponse<TranslationResult>>() {
|
|
|
|
val context: Context
|
|
|
|
|
|
|
|
init {
|
|
|
|
context = fragment.activity
|
|
|
|
}
|
|
|
|
|
|
|
|
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()) {
|
|
|
|
Utils.showErrorMessage(context, R.string.translate, result.exception, false)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
|
|
|
private val locationView: TextView
|
|
|
|
private val retweetedByView: TextView
|
|
|
|
|
2016-06-29 15:47:52 +02:00
|
|
|
init {
|
|
|
|
this.linkClickHandler = DetailStatusLinkClickHandler(adapter.context,
|
|
|
|
adapter.multiSelectManager, adapter, adapter.preferences)
|
|
|
|
this.linkify = TwidereLinkify(linkClickHandler)
|
|
|
|
|
2016-08-30 14:23:59 +02:00
|
|
|
locationView = itemView.locationView
|
|
|
|
retweetedByView = itemView.retweetedBy
|
2016-06-29 15:47:52 +02:00
|
|
|
initViews()
|
|
|
|
}
|
|
|
|
|
|
|
|
@UiThread
|
2016-12-04 04:58:03 +01:00
|
|
|
fun displayStatus(account: AccountDetails?,
|
2016-06-29 15:47:52 +02:00
|
|
|
status: ParcelableStatus?,
|
|
|
|
statusActivity: StatusActivity?,
|
|
|
|
translation: TranslationResult?) {
|
|
|
|
if (account == null || status == null) return
|
|
|
|
val fragment = adapter.fragment
|
|
|
|
val context = adapter.context
|
|
|
|
val loader = adapter.mediaLoader
|
|
|
|
val formatter = adapter.bidiFormatter
|
|
|
|
val twitter = adapter.twitterWrapper
|
|
|
|
val nameFirst = adapter.nameFirst
|
2017-01-15 18:55:53 +01:00
|
|
|
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) {
|
2017-01-15 18:55:53 +01:00
|
|
|
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
|
|
|
|
|
2017-01-15 18:55:53 +01:00
|
|
|
itemView.quotedName.setName(colorNameManager.getUserNickname(status.quoted_user_key!!,
|
2016-06-29 15:47:52 +02:00
|
|
|
status.quoted_user_name))
|
|
|
|
itemView.quotedName.setScreenName(String.format("@%s", status.quoted_user_screen_name))
|
|
|
|
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)
|
|
|
|
ParcelableStatusUtils.applySpans(quotedText, status.quoted_spans)
|
|
|
|
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
|
|
|
|
itemView.quotedMediaPreview.displayMedia(quotedMedia, loader, status.account_key, -1,
|
|
|
|
adapter.fragment, null)
|
|
|
|
} 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
|
|
|
|
val string = SpannableString.valueOf(context.getString(R.string.status_not_available_text))
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2017-01-15 18:55:53 +01: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
|
|
|
|
}
|
|
|
|
|
2017-01-15 18:55:53 +01:00
|
|
|
itemView.name.setName(colorNameManager.getUserNickname(status.user_key, status.user_name))
|
2016-06-29 15:47:52 +02:00
|
|
|
itemView.name.setScreenName(String.format("@%s", status.user_screen_name))
|
|
|
|
itemView.name.updateText(formatter)
|
|
|
|
|
|
|
|
loader.displayProfileImage(itemView.profileImage, status)
|
|
|
|
|
|
|
|
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)
|
|
|
|
ParcelableStatusUtils.applySpans(text, status.spans)
|
|
|
|
linkify.applyAllLinks(text, status.account_key, layoutPosition.toLong(),
|
|
|
|
status.is_possibly_sensitive, skipLinksInText)
|
|
|
|
|
|
|
|
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
|
|
|
|
itemView.mediaPreview.displayMedia(media, loader, status.account_key, -1,
|
|
|
|
adapter.fragment, adapter.mediaLoadingHandler)
|
|
|
|
} else {
|
|
|
|
itemView.mediaPreviewContainer.visibility = View.VISIBLE
|
|
|
|
itemView.mediaPreview.visibility = View.GONE
|
|
|
|
itemView.mediaPreviewLoad.visibility = View.VISIBLE
|
|
|
|
itemView.mediaPreview.displayMedia()
|
|
|
|
}
|
|
|
|
|
|
|
|
if (TwitterCardUtils.isCardSupported(status)) {
|
2016-07-06 15:21:34 +02:00
|
|
|
val size = TwitterCardUtils.getCardSize(status.card!!)
|
2016-06-29 15:47:52 +02:00
|
|
|
itemView.twitterCard.visibility = View.VISIBLE
|
|
|
|
if (size != null) {
|
|
|
|
itemView.twitterCard.setCardSize(size.x, size.y)
|
|
|
|
} else {
|
|
|
|
itemView.twitterCard.setCardSize(0, 0)
|
|
|
|
}
|
2016-12-15 13:15:58 +01:00
|
|
|
val cardFragment = TwitterCardFragmentFactory.createCardFragment(status)
|
2016-06-29 15:47:52 +02:00
|
|
|
val fm = fragment.childFragmentManager
|
|
|
|
if (cardFragment != null && !FragmentManagerAccessor.isStateSaved(fm)) {
|
|
|
|
val ft = fm.beginTransaction()
|
|
|
|
ft.replace(R.id.twitterCard, cardFragment)
|
|
|
|
ft.commit()
|
|
|
|
} else {
|
|
|
|
itemView.twitterCard.visibility = View.GONE
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
itemView.twitterCard.visibility = View.GONE
|
|
|
|
}
|
|
|
|
|
|
|
|
MenuUtils.setupForStatus(context, fragment.preferences, itemView.menuBar.menu, status,
|
2017-01-15 18:55:53 +01:00
|
|
|
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) {
|
|
|
|
itemView.translateLabel.text = context.getString(R.string.translation)
|
|
|
|
itemView.translateResult.visibility = View.VISIBLE
|
|
|
|
itemView.translateResult.text = translation.text
|
|
|
|
} else {
|
|
|
|
itemView.translateLabel.text = context.getString(R.string.translate_from_language,
|
|
|
|
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 -> {
|
2016-06-29 15:47:52 +02:00
|
|
|
IntentUtils.openStatus(adapter.context, status.account_key, status.quoted_id)
|
|
|
|
}
|
|
|
|
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.setNameFirst(adapter.nameFirst)
|
|
|
|
itemView.quotedName.setNameFirst(adapter.nameFirst)
|
|
|
|
|
|
|
|
itemView.mediaPreview.setStyle(adapter.mediaPreviewStyle)
|
2016-08-30 17:57:37 +02:00
|
|
|
itemView.quotedMediaPreview.setStyle(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)))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private class CountsUsersAdapter(
|
|
|
|
private val fragment: StatusFragment,
|
|
|
|
private val statusAdapter: StatusAdapter
|
|
|
|
) : BaseRecyclerViewAdapter<ViewHolder>(statusAdapter.context) {
|
|
|
|
|
|
|
|
private val inflater: LayoutInflater
|
|
|
|
|
|
|
|
private var counts: List<LabeledCount>? = null
|
|
|
|
private var users: List<ParcelableUser>? = null
|
|
|
|
|
|
|
|
init {
|
|
|
|
inflater = LayoutInflater.from(statusAdapter.context)
|
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
|
|
|
private val profileImageView: ImageView
|
|
|
|
|
|
|
|
init {
|
|
|
|
itemView.setOnClickListener(this)
|
2016-07-02 06:05:23 +02:00
|
|
|
profileImageView = itemView.findViewById(R.id.profileImage) as ImageView
|
2016-06-29 15:47:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fun displayUser(item: ParcelableUser) {
|
|
|
|
adapter.mediaLoader.displayProfileImage(profileImageView, item)
|
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
2016-12-24 13:47:35 +01:00
|
|
|
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?,
|
2016-06-29 15:47:52 +02:00
|
|
|
extraId: Long, type: Int, sensitive: Boolean, start: Int, end: Int): Boolean {
|
|
|
|
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)
|
|
|
|
|
|
|
|
class StatusAdapter(val fragment: StatusFragment) : LoadMoreSupportAdapter<ViewHolder>(fragment.context), IStatusesAdapter<List<ParcelableStatus>> {
|
|
|
|
private val inflater: LayoutInflater
|
|
|
|
override val mediaLoadingHandler: MediaLoadingHandler
|
|
|
|
override val twidereLinkify: TwidereLinkify
|
|
|
|
|
|
|
|
override var statusClickListener: StatusClickListener? = null
|
|
|
|
private var recyclerView: RecyclerView? = null
|
|
|
|
private var statusViewHolder: DetailStatusViewHolder? = null
|
|
|
|
|
2017-01-20 15:08:42 +01:00
|
|
|
private val itemCounts: IntArray
|
2016-06-29 15:47:52 +02:00
|
|
|
|
|
|
|
override val nameFirst: Boolean
|
|
|
|
private val cardBackgroundColor: Int
|
|
|
|
override val mediaPreviewStyle: Int
|
|
|
|
override val linkHighlightingStyle: Int
|
|
|
|
override val mediaPreviewEnabled: Boolean
|
|
|
|
override val sensitiveContentEnabled: Boolean
|
|
|
|
private val mShowCardActions: Boolean
|
|
|
|
override val useStarsForLikes: Boolean
|
|
|
|
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
|
|
|
|
private var mReplyStart: Int = 0
|
|
|
|
private var mShowingActionCardPosition: Int = 0
|
|
|
|
|
|
|
|
init {
|
|
|
|
setHasStableIds(true)
|
|
|
|
val context = fragment.activity
|
2017-01-20 15:08:42 +01:00
|
|
|
itemCounts = IntArray(ITEM_TYPES_SUM)
|
2016-06-29 15:47:52 +02:00
|
|
|
// 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)
|
|
|
|
mediaLoadingHandler = MediaLoadingHandler(R.id.media_preview_progress)
|
|
|
|
cardBackgroundColor = ThemeUtils.getCardBackgroundColor(context,
|
|
|
|
ThemeUtils.getThemeBackgroundOption(context),
|
|
|
|
ThemeUtils.getUserThemeBackgroundAlpha(context))
|
2017-01-07 07:16:02 +01:00
|
|
|
nameFirst = preferences[nameFirstKey]
|
|
|
|
mediaPreviewStyle = preferences[mediaPreviewStyleKey]
|
2017-01-20 15:08:42 +01:00
|
|
|
linkHighlightingStyle = preferences[linkHighlightOptionKey]
|
2017-01-07 07:16:02 +01:00
|
|
|
mediaPreviewEnabled = preferences[mediaPreviewKey]
|
2016-06-29 15:47:52 +02:00
|
|
|
sensitiveContentEnabled = preferences.getBoolean(SharedPreferenceConstants.KEY_DISPLAY_SENSITIVE_CONTENTS, false)
|
2017-01-07 07:16:02 +01:00
|
|
|
mShowCardActions = !preferences[hideCardActionsKey]
|
|
|
|
useStarsForLikes = preferences[iWantMyStarsBackKey]
|
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 -> {
|
|
|
|
if (data == null) return null
|
|
|
|
return data!![position - getIndexStart(ITEM_IDX_CONVERSATION)]
|
|
|
|
}
|
|
|
|
ITEM_IDX_REPLY -> {
|
|
|
|
if (data == null || mReplyStart < 0) return null
|
|
|
|
return data!![position - getIndexStart(ITEM_IDX_CONVERSATION)
|
|
|
|
- getTypeCount(ITEM_IDX_CONVERSATION) - getTypeCount(ITEM_IDX_STATUS) + mReplyStart]
|
|
|
|
}
|
|
|
|
ITEM_IDX_STATUS -> {
|
|
|
|
return status
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
|
|
|
fun getIndexStart(index: Int): Int {
|
|
|
|
if (index == 0) return 0
|
2017-01-20 15:08:42 +01:00
|
|
|
return TwidereMathUtils.sum(itemCounts, 0, index - 1)
|
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 {
|
|
|
|
if (position == RecyclerView.NO_POSITION) return mShowCardActions
|
|
|
|
return mShowCardActions || mShowingActionCardPosition == position
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun showCardActions(position: Int) {
|
|
|
|
if (mShowingActionCardPosition != RecyclerView.NO_POSITION) {
|
|
|
|
notifyItemChanged(mShowingActionCardPosition)
|
|
|
|
}
|
|
|
|
mShowingActionCardPosition = position
|
|
|
|
if (position != RecyclerView.NO_POSITION) {
|
|
|
|
notifyItemChanged(position)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun setData(data: List<ParcelableStatus>?): Boolean {
|
|
|
|
val status = this.status ?: return false
|
|
|
|
val changed = !CompareUtils.objectEquals(data, data)
|
|
|
|
this.data = data
|
|
|
|
if (data == null || data.isEmpty()) {
|
|
|
|
setTypeCount(ITEM_IDX_CONVERSATION, 0)
|
|
|
|
setTypeCount(ITEM_IDX_REPLY, 0)
|
|
|
|
mReplyStart = -1
|
|
|
|
} 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
|
2016-08-21 15:30:07 +02:00
|
|
|
for (i in 0 until data.size) {
|
2016-06-29 15:47:52 +02:00
|
|
|
val item = data[i]
|
|
|
|
if (item.sort_id < sortId) {
|
|
|
|
conversationCount++
|
2016-08-21 15:30:07 +02:00
|
|
|
} else if (item.sort_id > sortId && status.id != item.id) {
|
2016-06-29 15:47:52 +02:00
|
|
|
if (replyStart < 0) {
|
|
|
|
replyStart = i
|
|
|
|
}
|
|
|
|
replyCount++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
setTypeCount(ITEM_IDX_CONVERSATION, conversationCount)
|
|
|
|
setTypeCount(ITEM_IDX_REPLY, replyCount)
|
|
|
|
mReplyStart = replyStart
|
|
|
|
}
|
|
|
|
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 -> {
|
|
|
|
val view = inflater.inflate(R.layout.card_item_load_indicator, parent,
|
|
|
|
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)
|
|
|
|
statusHolder.displayStatus(status!!, itemType == ITEM_IDX_CONVERSATION
|
|
|
|
&& position - getItemTypeStart(position) == 0)
|
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-01-20 15:08:42 +01:00
|
|
|
return TwidereMathUtils.sum(itemCounts)
|
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.has(loadMoreIndicatorPosition, ILoadMoreSupportAdapter.START)
|
|
|
|
set(loading) {
|
|
|
|
if (loading) {
|
|
|
|
loadMoreIndicatorPosition = loadMoreIndicatorPosition or ILoadMoreSupportAdapter.START
|
|
|
|
} else {
|
|
|
|
loadMoreIndicatorPosition = loadMoreIndicatorPosition and ILoadMoreSupportAdapter.START.inv()
|
|
|
|
}
|
|
|
|
updateItemDecoration()
|
|
|
|
}
|
|
|
|
|
|
|
|
var isRepliesLoading: Boolean
|
|
|
|
get() = ILoadMoreSupportAdapter.has(loadMoreIndicatorPosition, ILoadMoreSupportAdapter.END)
|
|
|
|
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: TextView
|
|
|
|
|
|
|
|
init {
|
|
|
|
textView = itemView.findViewById(android.R.id.text1) as TextView
|
|
|
|
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 {
|
|
|
|
var heightBeforeSpace = 0
|
|
|
|
if (getItemViewType(child) == StatusAdapter.VIEW_TYPE_SPACE) {
|
2016-08-21 15:30:07 +02:00
|
|
|
for (i in 0 until childCount) {
|
2016-06-29 15:47:52 +02:00
|
|
|
val childToMeasure = getChildAt(i)
|
|
|
|
val paramsToMeasure = childToMeasure.layoutParams as LayoutParams
|
|
|
|
val typeToMeasure = getItemViewType(childToMeasure)
|
|
|
|
if (typeToMeasure == StatusAdapter.VIEW_TYPE_SPACE) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if (typeToMeasure == StatusAdapter.VIEW_TYPE_DETAIL_STATUS || heightBeforeSpace != 0) {
|
|
|
|
heightBeforeSpace += super.getDecoratedMeasuredHeight(childToMeasure)
|
|
|
|
+paramsToMeasure.topMargin + paramsToMeasure.bottomMargin
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (heightBeforeSpace != 0) {
|
|
|
|
val spaceHeight = recyclerView.measuredHeight - heightBeforeSpace
|
|
|
|
this.spaceHeight = Math.max(0, spaceHeight)
|
2016-08-25 04:10:53 +02:00
|
|
|
return this.spaceHeight
|
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
|
|
|
|
return getDecoratedMeasuredHeight(space)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2017-01-17 18:59:44 +01:00
|
|
|
class StatusActivitySummaryLoader(
|
|
|
|
context: Context,
|
|
|
|
private val accountKey: UserKey,
|
|
|
|
private val statusId: String
|
|
|
|
) : AsyncTaskLoader<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)
|
2016-06-29 15:47:52 +02:00
|
|
|
cr.insert(CachedStatuses.CONTENT_URI, ParcelableStatusValuesCreator.create(pStatus))
|
|
|
|
|
|
|
|
val activityCursor = cr.query(Activities.AboutMe.CONTENT_URI,
|
|
|
|
Activities.COLUMNS, activityWhere.sql, statusWhereArgs, null)!!
|
|
|
|
try {
|
|
|
|
activityCursor.moveToFirst()
|
|
|
|
val ci = ParcelableActivityCursorIndices(activityCursor)
|
|
|
|
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
|
|
|
|
}
|
|
|
|
cr.update(Activities.AboutMe.CONTENT_URI, ParcelableActivityValuesCreator.create(activity),
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|