2021-06-17 18:54:56 +02:00
|
|
|
/* Copyright 2021 Tusky Contributors
|
2019-07-19 20:10:20 +02: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.search.fragments
|
|
|
|
|
|
|
|
import android.Manifest
|
|
|
|
import android.app.DownloadManager
|
|
|
|
import android.content.ClipData
|
|
|
|
import android.content.ClipboardManager
|
|
|
|
import android.content.Context
|
|
|
|
import android.content.Intent
|
|
|
|
import android.content.pm.PackageManager
|
|
|
|
import android.net.Uri
|
2022-11-16 20:43:49 +01:00
|
|
|
import android.os.Build
|
2019-07-19 20:10:20 +02:00
|
|
|
import android.os.Environment
|
2019-08-28 19:54:46 +02:00
|
|
|
import android.util.Log
|
2019-07-19 20:10:20 +02:00
|
|
|
import android.view.View
|
|
|
|
import android.widget.Toast
|
|
|
|
import androidx.appcompat.app.AlertDialog
|
|
|
|
import androidx.appcompat.widget.PopupMenu
|
|
|
|
import androidx.core.app.ActivityOptionsCompat
|
|
|
|
import androidx.core.view.ViewCompat
|
2022-12-08 10:18:12 +01:00
|
|
|
import androidx.lifecycle.lifecycleScope
|
2021-06-17 18:54:56 +02:00
|
|
|
import androidx.paging.PagingData
|
|
|
|
import androidx.paging.PagingDataAdapter
|
2019-10-22 21:18:20 +02:00
|
|
|
import androidx.preference.PreferenceManager
|
2019-07-19 20:10:20 +02:00
|
|
|
import androidx.recyclerview.widget.DividerItemDecoration
|
|
|
|
import androidx.recyclerview.widget.LinearLayoutManager
|
2022-12-08 10:18:12 +01:00
|
|
|
import at.connyduck.calladapter.networkresult.fold
|
|
|
|
import com.google.android.material.snackbar.Snackbar
|
2019-12-19 19:09:40 +01:00
|
|
|
import com.keylesspalace.tusky.BaseActivity
|
|
|
|
import com.keylesspalace.tusky.R
|
|
|
|
import com.keylesspalace.tusky.ViewMediaActivity
|
|
|
|
import com.keylesspalace.tusky.components.compose.ComposeActivity
|
|
|
|
import com.keylesspalace.tusky.components.compose.ComposeActivity.ComposeOptions
|
2019-07-19 20:10:20 +02:00
|
|
|
import com.keylesspalace.tusky.components.report.ReportActivity
|
|
|
|
import com.keylesspalace.tusky.components.search.adapter.SearchStatusesAdapter
|
|
|
|
import com.keylesspalace.tusky.db.AccountEntity
|
|
|
|
import com.keylesspalace.tusky.entity.Attachment
|
|
|
|
import com.keylesspalace.tusky.entity.Status
|
2020-03-24 21:06:04 +01:00
|
|
|
import com.keylesspalace.tusky.entity.Status.Mention
|
2019-07-19 20:10:20 +02:00
|
|
|
import com.keylesspalace.tusky.interfaces.AccountSelectionListener
|
|
|
|
import com.keylesspalace.tusky.interfaces.StatusActionListener
|
2020-12-23 19:13:37 +01:00
|
|
|
import com.keylesspalace.tusky.settings.PrefKeys
|
2020-03-02 19:34:31 +01:00
|
|
|
import com.keylesspalace.tusky.util.CardViewMode
|
2019-12-30 21:37:20 +01:00
|
|
|
import com.keylesspalace.tusky.util.StatusDisplayOptions
|
2022-02-25 18:56:21 +01:00
|
|
|
import com.keylesspalace.tusky.util.openLink
|
2020-07-27 10:28:59 +02:00
|
|
|
import com.keylesspalace.tusky.view.showMuteAccountDialog
|
2019-07-19 20:10:20 +02:00
|
|
|
import com.keylesspalace.tusky.viewdata.AttachmentViewData
|
|
|
|
import com.keylesspalace.tusky.viewdata.StatusViewData
|
2021-06-17 18:54:56 +02:00
|
|
|
import kotlinx.coroutines.flow.Flow
|
2022-12-08 10:18:12 +01:00
|
|
|
import kotlinx.coroutines.launch
|
2019-07-19 20:10:20 +02:00
|
|
|
|
2022-02-25 18:57:49 +01:00
|
|
|
class SearchStatusesFragment : SearchFragment<StatusViewData.Concrete>(), StatusActionListener {
|
2019-07-19 20:10:20 +02:00
|
|
|
|
2022-02-25 18:57:49 +01:00
|
|
|
override val data: Flow<PagingData<StatusViewData.Concrete>>
|
2021-06-17 18:54:56 +02:00
|
|
|
get() = viewModel.statusesFlow
|
2019-07-19 20:10:20 +02:00
|
|
|
|
2020-01-13 13:57:44 +01:00
|
|
|
private val searchAdapter
|
2020-03-03 21:27:26 +01:00
|
|
|
get() = super.adapter as SearchStatusesAdapter
|
2020-01-13 13:57:44 +01:00
|
|
|
|
2022-02-25 18:57:49 +01:00
|
|
|
override fun createAdapter(): PagingDataAdapter<StatusViewData.Concrete, *> {
|
2021-03-13 21:27:20 +01:00
|
|
|
val preferences = PreferenceManager.getDefaultSharedPreferences(binding.searchRecyclerView.context)
|
2019-12-30 21:37:20 +01:00
|
|
|
val statusDisplayOptions = StatusDisplayOptions(
|
2021-06-28 21:13:24 +02:00
|
|
|
animateAvatars = preferences.getBoolean("animateGifAvatars", false),
|
|
|
|
mediaPreviewEnabled = viewModel.mediaPreviewEnabled,
|
|
|
|
useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false),
|
|
|
|
showBotOverlay = preferences.getBoolean("showBotOverlay", true),
|
|
|
|
useBlurhash = preferences.getBoolean("useBlurhash", true),
|
|
|
|
cardViewMode = CardViewMode.NONE,
|
|
|
|
confirmReblogs = preferences.getBoolean("confirmReblogs", true),
|
2021-12-29 13:44:00 +01:00
|
|
|
confirmFavourites = preferences.getBoolean("confirmFavourites", false),
|
2021-06-28 21:13:24 +02:00
|
|
|
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-07-19 20:10:20 +02:00
|
|
|
|
2021-03-13 21:27:20 +01:00
|
|
|
binding.searchRecyclerView.addItemDecoration(DividerItemDecoration(binding.searchRecyclerView.context, DividerItemDecoration.VERTICAL))
|
|
|
|
binding.searchRecyclerView.layoutManager = LinearLayoutManager(binding.searchRecyclerView.context)
|
2019-12-30 21:37:20 +01:00
|
|
|
return SearchStatusesAdapter(statusDisplayOptions, this)
|
2019-07-19 20:10:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
override fun onContentHiddenChange(isShowing: Boolean, position: Int) {
|
2022-02-25 18:57:49 +01:00
|
|
|
searchAdapter.peek(position)?.let {
|
2019-07-19 20:10:20 +02:00
|
|
|
viewModel.contentHiddenChange(it, isShowing)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onReply(position: Int) {
|
2022-04-21 18:46:43 +02:00
|
|
|
searchAdapter.peek(position)?.let { status ->
|
2019-07-19 20:10:20 +02:00
|
|
|
reply(status)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onFavourite(favourite: Boolean, position: Int) {
|
2022-02-25 18:57:49 +01:00
|
|
|
searchAdapter.peek(position)?.let { status ->
|
2019-07-19 20:10:20 +02:00
|
|
|
viewModel.favorite(status, favourite)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-19 10:15:32 +01:00
|
|
|
override fun onBookmark(bookmark: Boolean, position: Int) {
|
2022-02-25 18:57:49 +01:00
|
|
|
searchAdapter.peek(position)?.let { status ->
|
2019-11-19 10:15:32 +01:00
|
|
|
viewModel.bookmark(status, bookmark)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-19 20:10:20 +02:00
|
|
|
override fun onMore(view: View, position: Int) {
|
2022-02-25 18:57:49 +01:00
|
|
|
searchAdapter.peek(position)?.status?.let {
|
2019-07-19 20:10:20 +02:00
|
|
|
more(it, view, position)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onViewMedia(position: Int, attachmentIndex: Int, view: View?) {
|
2022-02-25 18:57:49 +01:00
|
|
|
searchAdapter.peek(position)?.status?.actionableStatus?.let { actionable ->
|
2019-07-19 20:10:20 +02:00
|
|
|
when (actionable.attachments[attachmentIndex].type) {
|
2019-09-08 19:35:43 +02:00
|
|
|
Attachment.Type.GIFV, Attachment.Type.VIDEO, Attachment.Type.IMAGE, Attachment.Type.AUDIO -> {
|
2019-07-19 20:10:20 +02:00
|
|
|
val attachments = AttachmentViewData.list(actionable)
|
2021-06-28 21:13:24 +02:00
|
|
|
val intent = ViewMediaActivity.newIntent(
|
|
|
|
context, attachments,
|
|
|
|
attachmentIndex
|
|
|
|
)
|
2019-07-19 20:10:20 +02:00
|
|
|
if (view != null) {
|
|
|
|
val url = actionable.attachments[attachmentIndex].url
|
|
|
|
ViewCompat.setTransitionName(view, url)
|
2021-06-28 21:13:24 +02:00
|
|
|
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(
|
|
|
|
requireActivity(),
|
|
|
|
view, url
|
|
|
|
)
|
2019-07-19 20:10:20 +02:00
|
|
|
startActivity(intent, options.toBundle())
|
|
|
|
} else {
|
|
|
|
startActivity(intent)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Attachment.Type.UNKNOWN -> {
|
2022-02-25 18:56:21 +01:00
|
|
|
context?.openLink(actionable.attachments[attachmentIndex].url)
|
2019-07-19 20:10:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onViewThread(position: Int) {
|
2022-02-25 18:57:49 +01:00
|
|
|
searchAdapter.peek(position)?.status?.let { status ->
|
2019-07-19 20:10:20 +02:00
|
|
|
val actionableStatus = status.actionableStatus
|
|
|
|
bottomSheetActivity?.viewThread(actionableStatus.id, actionableStatus.url)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onOpenReblog(position: Int) {
|
2022-02-25 18:57:49 +01:00
|
|
|
searchAdapter.peek(position)?.status?.let { status ->
|
2019-07-19 20:10:20 +02:00
|
|
|
bottomSheetActivity?.viewAccount(status.account.id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onExpandedChange(expanded: Boolean, position: Int) {
|
2022-02-25 18:57:49 +01:00
|
|
|
searchAdapter.peek(position)?.let {
|
2019-07-19 20:10:20 +02:00
|
|
|
viewModel.expandedChange(it, expanded)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onLoadMore(position: Int) {
|
2020-01-13 13:57:44 +01:00
|
|
|
// Not possible here
|
2019-07-19 20:10:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
override fun onContentCollapsedChange(isCollapsed: Boolean, position: Int) {
|
2022-02-25 18:57:49 +01:00
|
|
|
searchAdapter.peek(position)?.let {
|
2019-07-19 20:10:20 +02:00
|
|
|
viewModel.collapsedChange(it, isCollapsed)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onVoteInPoll(position: Int, choices: MutableList<Int>) {
|
2022-02-25 18:57:49 +01:00
|
|
|
searchAdapter.peek(position)?.let {
|
2019-07-19 20:10:20 +02:00
|
|
|
viewModel.voteInPoll(it, choices)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun removeItem(position: Int) {
|
2022-02-25 18:57:49 +01:00
|
|
|
searchAdapter.peek(position)?.let {
|
2019-07-19 20:10:20 +02:00
|
|
|
viewModel.removeItem(it)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onReblog(reblog: Boolean, position: Int) {
|
2022-02-25 18:57:49 +01:00
|
|
|
searchAdapter.peek(position)?.let { status ->
|
2019-07-19 20:10:20 +02:00
|
|
|
viewModel.reblog(status, reblog)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
fun newInstance() = SearchStatusesFragment()
|
|
|
|
}
|
|
|
|
|
2022-04-21 18:46:43 +02:00
|
|
|
private fun reply(status: StatusViewData.Concrete) {
|
|
|
|
val actionableStatus = status.actionable
|
2020-01-13 13:57:44 +01:00
|
|
|
val mentionedUsernames = actionableStatus.mentions.map { it.username }
|
2021-06-28 21:13:24 +02:00
|
|
|
.toMutableSet()
|
|
|
|
.apply {
|
|
|
|
add(actionableStatus.account.username)
|
|
|
|
remove(viewModel.activeAccount?.username)
|
|
|
|
}
|
2020-01-13 13:57:44 +01:00
|
|
|
|
2021-06-28 21:13:24 +02:00
|
|
|
val intent = ComposeActivity.startIntent(
|
|
|
|
requireContext(),
|
|
|
|
ComposeOptions(
|
2020-01-13 13:57:44 +01:00
|
|
|
inReplyToId = status.actionableId,
|
|
|
|
replyVisibility = actionableStatus.visibility,
|
|
|
|
contentWarning = actionableStatus.spoilerText,
|
2019-12-19 19:09:40 +01:00
|
|
|
mentionedUsernames = mentionedUsernames,
|
|
|
|
replyingStatusAuthor = actionableStatus.account.localUsername,
|
2022-08-31 18:53:57 +02:00
|
|
|
replyingStatusContent = status.content.toString(),
|
|
|
|
language = actionableStatus.language,
|
Fix saving changes to statuses when editing (#3103)
* Fix saving changes to statuses when editing
With the previous code backing out of a status editing operation where changes
had been made (whether it was editing an existing status, a scheduled status,
or a draft) would prompt the user to save the changes as a new draft.
See https://github.com/tuskyapp/Tusky/issues/2704 and
https://github.com/tuskyapp/Tusky/issues/2705 for more detail.
The fix:
- Create an enum to represent the four different kinds of edits that can
happen
- Editing a new status (i.e., composing it for the first time)
- Editing a posted status
- Editing a draft
- Editing a scheduled status
- Store this in ComposeOptions, and set it appropriately everywhere
ComposeOptions is created.
- Check the edit kind when backing out of ComposeActivity, and use this to
show one of three different dialogs as appropriate so the user can:
- Save as new draft or discard changes
- Continue editing or discard changes
- Update existing draft or discard changes
Also fix ComposeViewModel.didChange(), which erroneously reported false if the
old text started with the new text (e.g., if the old text was "hello, world"
and it was edited to "hello", didChange() would not consider that to be a
change).
Fixes https://github.com/tuskyapp/Tusky/issues/2704,
https://github.com/tuskyapp/Tusky/issues/2705
* Use orEmpty extension function
2022-12-31 13:04:49 +01:00
|
|
|
kind = ComposeActivity.ComposeKind.NEW
|
2021-06-28 21:13:24 +02:00
|
|
|
)
|
|
|
|
)
|
2022-04-28 20:37:46 +02:00
|
|
|
bottomSheetActivity?.startActivityWithSlideInAnimation(intent)
|
2019-07-19 20:10:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private fun more(status: Status, view: View, position: Int) {
|
|
|
|
val id = status.actionableId
|
|
|
|
val accountId = status.actionableStatus.account.id
|
|
|
|
val accountUsername = status.actionableStatus.account.username
|
|
|
|
val statusUrl = status.actionableStatus.url
|
|
|
|
val loggedInAccountId = viewModel.activeAccount?.accountId
|
|
|
|
|
|
|
|
val popup = PopupMenu(view.context, view)
|
2020-03-24 21:06:04 +01:00
|
|
|
val statusIsByCurrentUser = loggedInAccountId?.equals(accountId) == true
|
2019-07-19 20:10:20 +02:00
|
|
|
// Give a different menu depending on whether this is the user's own toot or not.
|
2020-03-24 21:06:04 +01:00
|
|
|
if (statusIsByCurrentUser) {
|
2019-07-19 20:10:20 +02:00
|
|
|
popup.inflate(R.menu.status_more_for_user)
|
|
|
|
val menu = popup.menu
|
|
|
|
menu.findItem(R.id.status_open_as).isVisible = !statusUrl.isNullOrBlank()
|
|
|
|
when (status.visibility) {
|
|
|
|
Status.Visibility.PUBLIC, Status.Visibility.UNLISTED -> {
|
|
|
|
val textId = getString(if (status.isPinned()) R.string.unpin_action else R.string.pin_action)
|
|
|
|
menu.add(0, R.id.pin, 1, textId)
|
|
|
|
}
|
|
|
|
Status.Visibility.PRIVATE -> {
|
|
|
|
var reblogged = status.reblogged
|
|
|
|
if (status.reblog != null) reblogged = status.reblog.reblogged
|
|
|
|
menu.findItem(R.id.status_reblog_private).isVisible = !reblogged
|
|
|
|
menu.findItem(R.id.status_unreblog_private).isVisible = reblogged
|
|
|
|
}
|
|
|
|
Status.Visibility.UNKNOWN, Status.Visibility.DIRECT -> {
|
2021-06-28 21:13:24 +02:00
|
|
|
} // Ignore
|
2019-07-19 20:10:20 +02:00
|
|
|
}
|
2020-03-24 21:06:04 +01:00
|
|
|
} else {
|
|
|
|
popup.inflate(R.menu.status_more)
|
|
|
|
val menu = popup.menu
|
|
|
|
menu.findItem(R.id.status_download_media).isVisible = status.attachments.isNotEmpty()
|
2019-07-19 20:10:20 +02:00
|
|
|
}
|
|
|
|
|
2020-01-13 13:57:44 +01:00
|
|
|
val openAsItem = popup.menu.findItem(R.id.status_open_as)
|
2022-02-25 18:55:58 +01:00
|
|
|
val openAsText = bottomSheetActivity?.openAsText
|
|
|
|
if (openAsText == null) {
|
|
|
|
openAsItem.isVisible = false
|
|
|
|
} else {
|
|
|
|
openAsItem.title = openAsText
|
2019-07-19 20:10:20 +02:00
|
|
|
}
|
|
|
|
|
2020-03-24 21:06:04 +01:00
|
|
|
val mutable = statusIsByCurrentUser || accountIsInMentions(viewModel.activeAccount, status.mentions)
|
|
|
|
val muteConversationItem = popup.menu.findItem(R.id.status_mute_conversation).apply {
|
|
|
|
isVisible = mutable
|
|
|
|
}
|
|
|
|
if (mutable) {
|
|
|
|
muteConversationItem.setTitle(
|
2021-06-28 21:13:24 +02:00
|
|
|
if (status.muted == true) {
|
|
|
|
R.string.action_unmute_conversation
|
|
|
|
} else {
|
|
|
|
R.string.action_mute_conversation
|
|
|
|
}
|
|
|
|
)
|
2020-03-24 21:06:04 +01:00
|
|
|
}
|
|
|
|
|
2019-07-19 20:10:20 +02:00
|
|
|
popup.setOnMenuItemClickListener { item ->
|
|
|
|
when (item.itemId) {
|
2022-03-27 12:23:25 +02:00
|
|
|
R.id.post_share_content -> {
|
2020-01-13 13:57:44 +01:00
|
|
|
val statusToShare: Status = status.actionableStatus
|
2019-07-19 20:10:20 +02:00
|
|
|
|
|
|
|
val sendIntent = Intent()
|
|
|
|
sendIntent.action = Intent.ACTION_SEND
|
|
|
|
|
2020-01-13 13:57:44 +01:00
|
|
|
val stringToShare = statusToShare.account.username +
|
2021-06-28 21:13:24 +02:00
|
|
|
" - " +
|
2022-08-07 19:36:09 +02:00
|
|
|
statusToShare.content
|
2019-07-19 20:10:20 +02:00
|
|
|
sendIntent.putExtra(Intent.EXTRA_TEXT, stringToShare)
|
|
|
|
sendIntent.type = "text/plain"
|
2022-03-27 12:23:25 +02:00
|
|
|
startActivity(Intent.createChooser(sendIntent, resources.getText(R.string.send_post_content_to)))
|
2019-07-19 20:10:20 +02:00
|
|
|
return@setOnMenuItemClickListener true
|
|
|
|
}
|
2022-03-27 12:23:25 +02:00
|
|
|
R.id.post_share_link -> {
|
2019-07-19 20:10:20 +02:00
|
|
|
val sendIntent = Intent()
|
|
|
|
sendIntent.action = Intent.ACTION_SEND
|
|
|
|
sendIntent.putExtra(Intent.EXTRA_TEXT, statusUrl)
|
|
|
|
sendIntent.type = "text/plain"
|
2022-03-27 12:23:25 +02:00
|
|
|
startActivity(Intent.createChooser(sendIntent, resources.getText(R.string.send_post_link_to)))
|
2019-07-19 20:10:20 +02:00
|
|
|
return@setOnMenuItemClickListener true
|
|
|
|
}
|
|
|
|
R.id.status_copy_link -> {
|
2020-01-13 13:57:44 +01:00
|
|
|
val clipboard = requireActivity().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
2019-10-22 21:18:20 +02:00
|
|
|
clipboard.setPrimaryClip(ClipData.newPlainText(null, statusUrl))
|
2019-07-19 20:10:20 +02:00
|
|
|
return@setOnMenuItemClickListener true
|
|
|
|
}
|
|
|
|
R.id.status_open_as -> {
|
|
|
|
showOpenAsDialog(statusUrl!!, item.title)
|
|
|
|
return@setOnMenuItemClickListener true
|
|
|
|
}
|
|
|
|
R.id.status_download_media -> {
|
|
|
|
requestDownloadAllMedia(status)
|
|
|
|
return@setOnMenuItemClickListener true
|
|
|
|
}
|
2020-03-24 21:06:04 +01:00
|
|
|
R.id.status_mute_conversation -> {
|
2022-02-25 18:57:49 +01:00
|
|
|
searchAdapter.peek(position)?.let { foundStatus ->
|
2020-03-24 21:06:04 +01:00
|
|
|
viewModel.muteConversation(foundStatus, status.muted != true)
|
|
|
|
}
|
|
|
|
return@setOnMenuItemClickListener true
|
|
|
|
}
|
2019-07-19 20:10:20 +02:00
|
|
|
R.id.status_mute -> {
|
2020-03-30 21:03:27 +02:00
|
|
|
onMute(accountId, accountUsername)
|
2019-07-19 20:10:20 +02:00
|
|
|
return@setOnMenuItemClickListener true
|
|
|
|
}
|
|
|
|
R.id.status_block -> {
|
2020-03-30 21:03:27 +02:00
|
|
|
onBlock(accountId, accountUsername)
|
2019-07-19 20:10:20 +02:00
|
|
|
return@setOnMenuItemClickListener true
|
|
|
|
}
|
|
|
|
R.id.status_report -> {
|
2019-08-17 09:08:58 +02:00
|
|
|
openReportPage(accountId, accountUsername, id)
|
2019-07-19 20:10:20 +02:00
|
|
|
return@setOnMenuItemClickListener true
|
|
|
|
}
|
|
|
|
R.id.status_unreblog_private -> {
|
|
|
|
onReblog(false, position)
|
|
|
|
return@setOnMenuItemClickListener true
|
|
|
|
}
|
|
|
|
R.id.status_reblog_private -> {
|
|
|
|
onReblog(true, position)
|
|
|
|
return@setOnMenuItemClickListener true
|
|
|
|
}
|
|
|
|
R.id.status_delete -> {
|
|
|
|
showConfirmDeleteDialog(id, position)
|
|
|
|
return@setOnMenuItemClickListener true
|
|
|
|
}
|
|
|
|
R.id.status_delete_and_redraft -> {
|
|
|
|
showConfirmEditDialog(id, position, status)
|
|
|
|
return@setOnMenuItemClickListener true
|
|
|
|
}
|
2022-12-08 10:18:12 +01:00
|
|
|
R.id.status_edit -> {
|
|
|
|
editStatus(id, position, status)
|
|
|
|
return@setOnMenuItemClickListener true
|
|
|
|
}
|
2019-07-19 20:10:20 +02:00
|
|
|
R.id.pin -> {
|
|
|
|
viewModel.pinAccount(status, !status.isPinned())
|
|
|
|
return@setOnMenuItemClickListener true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
false
|
|
|
|
}
|
|
|
|
popup.show()
|
|
|
|
}
|
|
|
|
|
2020-03-30 21:03:27 +02:00
|
|
|
private fun onBlock(accountId: String, accountUsername: String) {
|
|
|
|
AlertDialog.Builder(requireContext())
|
2021-06-28 21:13:24 +02:00
|
|
|
.setMessage(getString(R.string.dialog_block_warning, accountUsername))
|
|
|
|
.setPositiveButton(android.R.string.ok) { _, _ -> viewModel.blockAccount(accountId) }
|
|
|
|
.setNegativeButton(android.R.string.cancel, null)
|
|
|
|
.show()
|
2020-03-30 21:03:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private fun onMute(accountId: String, accountUsername: String) {
|
2020-07-27 10:28:59 +02:00
|
|
|
showMuteAccountDialog(
|
|
|
|
this.requireActivity(),
|
2020-10-25 18:36:00 +01:00
|
|
|
accountUsername
|
2021-01-15 21:05:36 +01:00
|
|
|
) { notifications, duration ->
|
|
|
|
viewModel.muteAccount(accountId, notifications, duration)
|
2020-10-25 18:36:00 +01:00
|
|
|
}
|
2020-03-30 21:03:27 +02:00
|
|
|
}
|
|
|
|
|
2021-06-11 20:15:40 +02:00
|
|
|
private fun accountIsInMentions(account: AccountEntity?, mentions: List<Mention>): Boolean {
|
2020-03-24 21:06:04 +01:00
|
|
|
return mentions.firstOrNull {
|
|
|
|
account?.username == it.username && account.domain == Uri.parse(it.url)?.host
|
|
|
|
} != null
|
|
|
|
}
|
|
|
|
|
2022-11-04 19:22:38 +01:00
|
|
|
private fun showOpenAsDialog(statusUrl: String, dialogTitle: CharSequence?) {
|
2021-06-28 21:13:24 +02:00
|
|
|
bottomSheetActivity?.showAccountChooserDialog(
|
|
|
|
dialogTitle, false,
|
|
|
|
object : AccountSelectionListener {
|
|
|
|
override fun onAccountSelected(account: AccountEntity) {
|
2022-02-25 18:55:58 +01:00
|
|
|
bottomSheetActivity?.openAsAccount(statusUrl, account)
|
2021-06-28 21:13:24 +02:00
|
|
|
}
|
2019-07-19 20:10:20 +02:00
|
|
|
}
|
2021-06-28 21:13:24 +02:00
|
|
|
)
|
2019-07-19 20:10:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private fun downloadAllMedia(status: Status) {
|
|
|
|
Toast.makeText(context, R.string.downloading_media, Toast.LENGTH_SHORT).show()
|
|
|
|
for ((_, url) in status.attachments) {
|
|
|
|
val uri = Uri.parse(url)
|
|
|
|
val filename = uri.lastPathSegment
|
|
|
|
|
2020-01-13 13:57:44 +01:00
|
|
|
val downloadManager = requireActivity().getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
2019-07-19 20:10:20 +02:00
|
|
|
val request = DownloadManager.Request(uri)
|
|
|
|
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename)
|
|
|
|
downloadManager.enqueue(request)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun requestDownloadAllMedia(status: Status) {
|
2022-11-16 20:43:49 +01:00
|
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
|
|
|
val permissions = arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
|
|
|
(activity as BaseActivity).requestPermissions(permissions) { _, grantResults ->
|
|
|
|
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
|
|
|
downloadAllMedia(status)
|
|
|
|
} else {
|
|
|
|
Toast.makeText(
|
|
|
|
context,
|
|
|
|
R.string.error_media_download_permission,
|
|
|
|
Toast.LENGTH_SHORT
|
|
|
|
).show()
|
|
|
|
}
|
2019-07-19 20:10:20 +02:00
|
|
|
}
|
2022-11-16 20:43:49 +01:00
|
|
|
} else {
|
|
|
|
downloadAllMedia(status)
|
2019-07-19 20:10:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-17 09:08:58 +02:00
|
|
|
private fun openReportPage(accountId: String, accountUsername: String, statusId: String) {
|
|
|
|
startActivity(ReportActivity.getIntent(requireContext(), accountId, accountUsername, statusId))
|
2019-07-19 20:10:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private fun showConfirmDeleteDialog(id: String, position: Int) {
|
|
|
|
context?.let {
|
|
|
|
AlertDialog.Builder(it)
|
2022-03-20 20:21:42 +01:00
|
|
|
.setMessage(R.string.dialog_delete_post_warning)
|
2021-06-28 21:13:24 +02:00
|
|
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
2023-01-10 21:20:00 +01:00
|
|
|
viewModel.deleteStatusAsync(id)
|
2021-06-28 21:13:24 +02:00
|
|
|
removeItem(position)
|
|
|
|
}
|
|
|
|
.setNegativeButton(android.R.string.cancel, null)
|
|
|
|
.show()
|
2019-07-19 20:10:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun showConfirmEditDialog(id: String, position: Int, status: Status) {
|
|
|
|
activity?.let {
|
|
|
|
AlertDialog.Builder(it)
|
2022-03-20 20:21:42 +01:00
|
|
|
.setMessage(R.string.dialog_redraft_post_warning)
|
2021-06-28 21:13:24 +02:00
|
|
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
2023-01-10 21:20:00 +01:00
|
|
|
lifecycleScope.launch {
|
|
|
|
viewModel.deleteStatusAsync(id).await().fold(
|
2021-06-28 21:13:24 +02:00
|
|
|
{ deletedStatus ->
|
|
|
|
removeItem(position)
|
|
|
|
|
|
|
|
val redraftStatus = if (deletedStatus.isEmpty()) {
|
|
|
|
status.toDeletedStatus()
|
|
|
|
} else {
|
|
|
|
deletedStatus
|
|
|
|
}
|
|
|
|
|
|
|
|
val intent = ComposeActivity.startIntent(
|
|
|
|
requireContext(),
|
|
|
|
ComposeOptions(
|
2023-03-01 21:06:55 +01:00
|
|
|
content = redraftStatus.text.orEmpty(),
|
2021-06-28 21:13:24 +02:00
|
|
|
inReplyToId = redraftStatus.inReplyToId,
|
|
|
|
visibility = redraftStatus.visibility,
|
|
|
|
contentWarning = redraftStatus.spoilerText,
|
|
|
|
mediaAttachments = redraftStatus.attachments,
|
|
|
|
sensitive = redraftStatus.sensitive,
|
2022-08-31 18:53:57 +02:00
|
|
|
poll = redraftStatus.poll?.toNewPoll(status.createdAt),
|
|
|
|
language = redraftStatus.language,
|
Fix saving changes to statuses when editing (#3103)
* Fix saving changes to statuses when editing
With the previous code backing out of a status editing operation where changes
had been made (whether it was editing an existing status, a scheduled status,
or a draft) would prompt the user to save the changes as a new draft.
See https://github.com/tuskyapp/Tusky/issues/2704 and
https://github.com/tuskyapp/Tusky/issues/2705 for more detail.
The fix:
- Create an enum to represent the four different kinds of edits that can
happen
- Editing a new status (i.e., composing it for the first time)
- Editing a posted status
- Editing a draft
- Editing a scheduled status
- Store this in ComposeOptions, and set it appropriately everywhere
ComposeOptions is created.
- Check the edit kind when backing out of ComposeActivity, and use this to
show one of three different dialogs as appropriate so the user can:
- Save as new draft or discard changes
- Continue editing or discard changes
- Update existing draft or discard changes
Also fix ComposeViewModel.didChange(), which erroneously reported false if the
old text started with the new text (e.g., if the old text was "hello, world"
and it was edited to "hello", didChange() would not consider that to be a
change).
Fixes https://github.com/tuskyapp/Tusky/issues/2704,
https://github.com/tuskyapp/Tusky/issues/2705
* Use orEmpty extension function
2022-12-31 13:04:49 +01:00
|
|
|
kind = ComposeActivity.ComposeKind.NEW
|
2021-06-28 21:13:24 +02:00
|
|
|
)
|
|
|
|
)
|
|
|
|
startActivity(intent)
|
|
|
|
},
|
|
|
|
{ error ->
|
|
|
|
Log.w("SearchStatusesFragment", "error deleting status", error)
|
|
|
|
Toast.makeText(context, R.string.error_generic, Toast.LENGTH_SHORT).show()
|
|
|
|
}
|
|
|
|
)
|
2023-01-10 21:20:00 +01:00
|
|
|
}
|
2021-06-28 21:13:24 +02:00
|
|
|
}
|
|
|
|
.setNegativeButton(android.R.string.cancel, null)
|
|
|
|
.show()
|
2019-07-19 20:10:20 +02:00
|
|
|
}
|
|
|
|
}
|
2022-12-08 10:18:12 +01:00
|
|
|
|
|
|
|
private fun editStatus(id: String, position: Int, status: Status) {
|
|
|
|
lifecycleScope.launch {
|
|
|
|
mastodonApi.statusSource(id).fold(
|
|
|
|
{ source ->
|
|
|
|
val composeOptions = ComposeOptions(
|
|
|
|
content = source.text,
|
|
|
|
inReplyToId = status.inReplyToId,
|
|
|
|
visibility = status.visibility,
|
|
|
|
contentWarning = source.spoilerText,
|
|
|
|
mediaAttachments = status.attachments,
|
|
|
|
sensitive = status.sensitive,
|
|
|
|
language = status.language,
|
|
|
|
statusId = source.id,
|
|
|
|
poll = status.poll?.toNewPoll(status.createdAt),
|
Fix saving changes to statuses when editing (#3103)
* Fix saving changes to statuses when editing
With the previous code backing out of a status editing operation where changes
had been made (whether it was editing an existing status, a scheduled status,
or a draft) would prompt the user to save the changes as a new draft.
See https://github.com/tuskyapp/Tusky/issues/2704 and
https://github.com/tuskyapp/Tusky/issues/2705 for more detail.
The fix:
- Create an enum to represent the four different kinds of edits that can
happen
- Editing a new status (i.e., composing it for the first time)
- Editing a posted status
- Editing a draft
- Editing a scheduled status
- Store this in ComposeOptions, and set it appropriately everywhere
ComposeOptions is created.
- Check the edit kind when backing out of ComposeActivity, and use this to
show one of three different dialogs as appropriate so the user can:
- Save as new draft or discard changes
- Continue editing or discard changes
- Update existing draft or discard changes
Also fix ComposeViewModel.didChange(), which erroneously reported false if the
old text started with the new text (e.g., if the old text was "hello, world"
and it was edited to "hello", didChange() would not consider that to be a
change).
Fixes https://github.com/tuskyapp/Tusky/issues/2704,
https://github.com/tuskyapp/Tusky/issues/2705
* Use orEmpty extension function
2022-12-31 13:04:49 +01:00
|
|
|
kind = ComposeActivity.ComposeKind.EDIT_POSTED,
|
2022-12-08 10:18:12 +01:00
|
|
|
)
|
|
|
|
startActivity(ComposeActivity.startIntent(requireContext(), composeOptions))
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Snackbar.make(
|
|
|
|
requireView(),
|
|
|
|
getString(R.string.error_status_source_load),
|
|
|
|
Snackbar.LENGTH_SHORT
|
|
|
|
).show()
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2019-07-19 20:10:20 +02:00
|
|
|
}
|