2021-06-17 18:54:56 +02:00
|
|
|
/* Copyright 2021 Tusky Contributors
|
2019-02-12 19:22:37 +01:00
|
|
|
*
|
|
|
|
* This file is a part of Tusky.
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*
|
|
|
|
* Tusky 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 Tusky; if not,
|
|
|
|
* see <http://www.gnu.org/licenses>. */
|
|
|
|
|
|
|
|
package com.keylesspalace.tusky.components.conversation
|
|
|
|
|
|
|
|
import android.content.Intent
|
|
|
|
import android.os.Bundle
|
|
|
|
import android.view.LayoutInflater
|
|
|
|
import android.view.View
|
|
|
|
import android.view.ViewGroup
|
2021-06-17 18:54:56 +02:00
|
|
|
import androidx.appcompat.app.AlertDialog
|
|
|
|
import androidx.appcompat.widget.PopupMenu
|
2020-02-25 19:49:41 +01:00
|
|
|
import androidx.fragment.app.viewModels
|
2021-06-17 18:54:56 +02:00
|
|
|
import androidx.lifecycle.lifecycleScope
|
|
|
|
import androidx.paging.ExperimentalPagingApi
|
|
|
|
import androidx.paging.LoadState
|
2019-10-22 21:18:20 +02:00
|
|
|
import androidx.preference.PreferenceManager
|
2019-02-12 19:22:37 +01:00
|
|
|
import androidx.recyclerview.widget.DividerItemDecoration
|
|
|
|
import androidx.recyclerview.widget.LinearLayoutManager
|
|
|
|
import androidx.recyclerview.widget.SimpleItemAnimator
|
|
|
|
import com.keylesspalace.tusky.AccountActivity
|
|
|
|
import com.keylesspalace.tusky.R
|
|
|
|
import com.keylesspalace.tusky.ViewTagActivity
|
2021-03-13 21:27:20 +01:00
|
|
|
import com.keylesspalace.tusky.databinding.FragmentTimelineBinding
|
2019-02-12 19:22:37 +01:00
|
|
|
import com.keylesspalace.tusky.di.Injectable
|
|
|
|
import com.keylesspalace.tusky.di.ViewModelFactory
|
|
|
|
import com.keylesspalace.tusky.fragment.SFragment
|
2019-04-08 15:40:16 +02:00
|
|
|
import com.keylesspalace.tusky.interfaces.ReselectableFragment
|
2019-02-12 19:22:37 +01:00
|
|
|
import com.keylesspalace.tusky.interfaces.StatusActionListener
|
2020-12-23 19:13:37 +01:00
|
|
|
import com.keylesspalace.tusky.settings.PrefKeys
|
2021-06-17 18:54:56 +02:00
|
|
|
import com.keylesspalace.tusky.util.*
|
|
|
|
import kotlinx.coroutines.flow.collectLatest
|
|
|
|
import kotlinx.coroutines.launch
|
|
|
|
import java.io.IOException
|
2020-10-25 18:36:00 +01:00
|
|
|
import com.keylesspalace.tusky.util.CardViewMode
|
|
|
|
import com.keylesspalace.tusky.util.StatusDisplayOptions
|
|
|
|
import com.keylesspalace.tusky.util.hide
|
2021-03-13 21:27:20 +01:00
|
|
|
import com.keylesspalace.tusky.util.viewBinding
|
2021-06-11 20:15:40 +02:00
|
|
|
import com.keylesspalace.tusky.viewdata.AttachmentViewData
|
2019-02-12 19:22:37 +01:00
|
|
|
import javax.inject.Inject
|
|
|
|
|
2019-04-08 15:40:16 +02:00
|
|
|
class ConversationsFragment : SFragment(), StatusActionListener, Injectable, ReselectableFragment {
|
2019-02-12 19:22:37 +01:00
|
|
|
|
|
|
|
@Inject
|
|
|
|
lateinit var viewModelFactory: ViewModelFactory
|
|
|
|
|
2020-02-25 19:49:41 +01:00
|
|
|
private val viewModel: ConversationsViewModel by viewModels { viewModelFactory }
|
2019-02-12 19:22:37 +01:00
|
|
|
|
2021-03-13 21:27:20 +01:00
|
|
|
private val binding by viewBinding(FragmentTimelineBinding::bind)
|
|
|
|
|
2019-02-12 19:22:37 +01:00
|
|
|
private lateinit var adapter: ConversationAdapter
|
2021-06-17 18:54:56 +02:00
|
|
|
private lateinit var loadStateAdapter: ConversationLoadStateAdapter
|
2019-02-12 19:22:37 +01:00
|
|
|
|
2019-04-08 15:40:16 +02:00
|
|
|
private var layoutManager: LinearLayoutManager? = null
|
|
|
|
|
2021-06-17 18:54:56 +02:00
|
|
|
private var initialRefreshDone: Boolean = false
|
|
|
|
|
2019-02-12 19:22:37 +01:00
|
|
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
|
|
|
return inflater.inflate(R.layout.fragment_timeline, container, false)
|
|
|
|
}
|
|
|
|
|
2021-06-17 18:54:56 +02:00
|
|
|
@ExperimentalPagingApi
|
2019-02-12 19:22:37 +01:00
|
|
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
|
|
val preferences = PreferenceManager.getDefaultSharedPreferences(view.context)
|
|
|
|
|
2019-12-30 21:37:20 +01:00
|
|
|
val statusDisplayOptions = StatusDisplayOptions(
|
2021-06-17 18:54:56 +02:00
|
|
|
animateAvatars = preferences.getBoolean("animateGifAvatars", false),
|
|
|
|
mediaPreviewEnabled = accountManager.activeAccount?.mediaPreviewEnabled ?: true,
|
|
|
|
useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false),
|
|
|
|
showBotOverlay = preferences.getBoolean("showBotOverlay", true),
|
|
|
|
useBlurhash = preferences.getBoolean("useBlurhash", true),
|
|
|
|
cardViewMode = CardViewMode.NONE,
|
|
|
|
confirmReblogs = preferences.getBoolean("confirmReblogs", true),
|
|
|
|
hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false),
|
|
|
|
animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
|
2019-12-30 21:37:20 +01:00
|
|
|
)
|
2019-02-12 19:22:37 +01:00
|
|
|
|
2021-06-17 18:54:56 +02:00
|
|
|
adapter = ConversationAdapter(statusDisplayOptions, this)
|
|
|
|
loadStateAdapter = ConversationLoadStateAdapter(adapter::retry)
|
2019-02-12 19:22:37 +01:00
|
|
|
|
2021-03-13 21:27:20 +01:00
|
|
|
binding.recyclerView.addItemDecoration(DividerItemDecoration(view.context, DividerItemDecoration.VERTICAL))
|
2019-04-08 15:40:16 +02:00
|
|
|
layoutManager = LinearLayoutManager(view.context)
|
2021-03-13 21:27:20 +01:00
|
|
|
binding.recyclerView.layoutManager = layoutManager
|
2021-06-17 18:54:56 +02:00
|
|
|
binding.recyclerView.adapter = adapter.withLoadStateFooter(loadStateAdapter)
|
2021-03-13 21:27:20 +01:00
|
|
|
(binding.recyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
2019-02-12 19:22:37 +01:00
|
|
|
|
2021-03-13 21:27:20 +01:00
|
|
|
binding.progressBar.hide()
|
|
|
|
binding.statusView.hide()
|
2019-02-12 19:22:37 +01:00
|
|
|
|
|
|
|
initSwipeToRefresh()
|
|
|
|
|
2021-06-17 18:54:56 +02:00
|
|
|
lifecycleScope.launch {
|
|
|
|
viewModel.conversationFlow.collectLatest { pagingData ->
|
|
|
|
adapter.submitData(pagingData)
|
|
|
|
}
|
2020-10-25 18:36:00 +01:00
|
|
|
}
|
2019-02-12 19:22:37 +01:00
|
|
|
|
2021-06-17 18:54:56 +02:00
|
|
|
adapter.addLoadStateListener { loadStates ->
|
|
|
|
|
|
|
|
loadStates.refresh.let { refreshState ->
|
|
|
|
if (refreshState is LoadState.Error) {
|
|
|
|
binding.statusView.show()
|
|
|
|
if (refreshState.error is IOException) {
|
|
|
|
binding.statusView.setup(R.drawable.elephant_offline, R.string.error_network) {
|
|
|
|
adapter.refresh()
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
binding.statusView.setup(R.drawable.elephant_error, R.string.error_generic) {
|
|
|
|
adapter.refresh()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
binding.statusView.hide()
|
|
|
|
}
|
|
|
|
|
|
|
|
binding.progressBar.visible(refreshState == LoadState.Loading && adapter.itemCount == 0)
|
|
|
|
|
|
|
|
if (refreshState is LoadState.NotLoading && !initialRefreshDone) {
|
|
|
|
// jump to top after the initial refresh finished
|
|
|
|
binding.recyclerView.scrollToPosition(0)
|
|
|
|
initialRefreshDone = true
|
|
|
|
}
|
|
|
|
|
|
|
|
if (refreshState != LoadState.Loading) {
|
|
|
|
binding.swipeRefreshLayout.isRefreshing = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-02-12 19:22:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private fun initSwipeToRefresh() {
|
2021-03-13 21:27:20 +01:00
|
|
|
binding.swipeRefreshLayout.setOnRefreshListener {
|
2021-06-17 18:54:56 +02:00
|
|
|
adapter.refresh()
|
2019-02-12 19:22:37 +01:00
|
|
|
}
|
2021-03-13 21:27:20 +01:00
|
|
|
binding.swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
2019-02-12 19:22:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
override fun onReblog(reblog: Boolean, position: Int) {
|
|
|
|
// its impossible to reblog private messages
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onFavourite(favourite: Boolean, position: Int) {
|
2021-06-17 18:54:56 +02:00
|
|
|
adapter.item(position)?.let { conversation ->
|
|
|
|
viewModel.favourite(favourite, conversation)
|
|
|
|
}
|
2019-02-12 19:22:37 +01:00
|
|
|
}
|
|
|
|
|
2019-11-19 10:15:32 +01:00
|
|
|
override fun onBookmark(favourite: Boolean, position: Int) {
|
2021-06-17 18:54:56 +02:00
|
|
|
adapter.item(position)?.let { conversation ->
|
|
|
|
viewModel.bookmark(favourite, conversation)
|
|
|
|
}
|
2019-11-19 10:15:32 +01:00
|
|
|
}
|
|
|
|
|
2019-02-12 19:22:37 +01:00
|
|
|
override fun onMore(view: View, position: Int) {
|
2021-06-17 18:54:56 +02:00
|
|
|
adapter.item(position)?.let { conversation ->
|
|
|
|
|
|
|
|
val popup = PopupMenu(requireContext(), view)
|
|
|
|
popup.inflate(R.menu.conversation_more)
|
|
|
|
|
|
|
|
if (conversation.lastStatus.muted) {
|
|
|
|
popup.menu.removeItem(R.id.status_mute_conversation)
|
|
|
|
} else {
|
|
|
|
popup.menu.removeItem(R.id.status_unmute_conversation)
|
|
|
|
}
|
|
|
|
|
|
|
|
popup.setOnMenuItemClickListener { item ->
|
|
|
|
when (item.itemId) {
|
|
|
|
R.id.status_mute_conversation -> viewModel.muteConversation(conversation)
|
|
|
|
R.id.status_unmute_conversation -> viewModel.muteConversation(conversation)
|
|
|
|
R.id.conversation_delete -> deleteConversation(conversation)
|
|
|
|
}
|
|
|
|
true
|
|
|
|
}
|
|
|
|
popup.show()
|
2019-02-12 19:22:37 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-04 19:24:27 +01:00
|
|
|
override fun onViewMedia(position: Int, attachmentIndex: Int, view: View?) {
|
2021-06-17 18:54:56 +02:00
|
|
|
adapter.item(position)?.let { conversation ->
|
|
|
|
viewMedia(attachmentIndex, AttachmentViewData.list(conversation.lastStatus.toStatus()), view)
|
2019-02-12 19:22:37 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onViewThread(position: Int) {
|
2021-06-17 18:54:56 +02:00
|
|
|
adapter.item(position)?.let { conversation ->
|
|
|
|
viewThread(conversation.lastStatus.id, conversation.lastStatus.url)
|
2019-02-12 19:22:37 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onOpenReblog(position: Int) {
|
|
|
|
// there are no reblogs in search results
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onExpandedChange(expanded: Boolean, position: Int) {
|
2021-06-17 18:54:56 +02:00
|
|
|
adapter.item(position)?.let { conversation ->
|
|
|
|
viewModel.expandHiddenStatus(expanded, conversation)
|
|
|
|
}
|
2019-02-12 19:22:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
override fun onContentHiddenChange(isShowing: Boolean, position: Int) {
|
2021-06-17 18:54:56 +02:00
|
|
|
adapter.item(position)?.let { conversation ->
|
|
|
|
viewModel.showContent(isShowing, conversation)
|
|
|
|
}
|
2019-02-12 19:22:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
override fun onLoadMore(position: Int) {
|
|
|
|
// not using the old way of pagination
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onContentCollapsedChange(isCollapsed: Boolean, position: Int) {
|
2021-06-17 18:54:56 +02:00
|
|
|
adapter.item(position)?.let { conversation ->
|
|
|
|
viewModel.collapseLongStatus(isCollapsed, conversation)
|
|
|
|
}
|
2019-02-12 19:22:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
override fun onViewAccount(id: String) {
|
|
|
|
val intent = AccountActivity.getIntent(requireContext(), id)
|
|
|
|
startActivity(intent)
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onViewTag(tag: String) {
|
|
|
|
val intent = Intent(context, ViewTagActivity::class.java)
|
|
|
|
intent.putExtra("hashtag", tag)
|
|
|
|
startActivity(intent)
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun removeItem(position: Int) {
|
2021-06-17 18:54:56 +02:00
|
|
|
// not needed
|
2019-02-12 19:22:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
override fun onReply(position: Int) {
|
2021-06-17 18:54:56 +02:00
|
|
|
adapter.item(position)?.let { conversation ->
|
|
|
|
reply(conversation.lastStatus.toStatus())
|
2019-02-12 19:22:37 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-17 18:54:56 +02:00
|
|
|
private fun deleteConversation(conversation: ConversationEntity) {
|
|
|
|
AlertDialog.Builder(requireContext())
|
|
|
|
.setMessage(R.string.dialog_delete_conversation_warning)
|
|
|
|
.setNegativeButton(android.R.string.cancel, null)
|
|
|
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
|
|
|
viewModel.remove(conversation)
|
|
|
|
}
|
|
|
|
.show()
|
|
|
|
}
|
|
|
|
|
2019-04-08 15:40:16 +02:00
|
|
|
private fun jumpToTop() {
|
|
|
|
if (isAdded) {
|
|
|
|
layoutManager?.scrollToPosition(0)
|
2021-03-13 21:27:20 +01:00
|
|
|
binding.recyclerView.stopScroll()
|
2019-04-08 15:40:16 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onReselect() {
|
|
|
|
jumpToTop()
|
|
|
|
}
|
|
|
|
|
2019-04-22 10:11:00 +02:00
|
|
|
override fun onVoteInPoll(position: Int, choices: MutableList<Int>) {
|
2021-06-17 18:54:56 +02:00
|
|
|
adapter.item(position)?.let { conversation ->
|
|
|
|
viewModel.voteInPoll(choices, conversation)
|
|
|
|
}
|
2019-04-22 10:11:00 +02:00
|
|
|
}
|
|
|
|
|
2019-02-12 19:22:37 +01:00
|
|
|
companion object {
|
|
|
|
fun newInstance() = ConversationsFragment()
|
|
|
|
}
|
|
|
|
}
|