Closes #3095 --- Since I had to add `visibility` on the `onReblog` method, it is possible that I have broken something. Also, I kept the old method signature (which calls the new one), but it's possible that it is not needed. ~~I'm not 100% sure that unlisted boost works, as the visibility of the boost is not shown anywehere.~~ I've confirmed that private (followers-only) boosts are not visible on private browsing on the browser. EDIT: Confirmed visibility in devtools on web browser. Screenshots:  
This commit is contained in:
parent
9990d89a53
commit
d334bd0c40
18 changed files with 97 additions and 66 deletions
|
|
@ -678,7 +678,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
showConfirmReblog(listener, buttonState, position);
|
||||
return false;
|
||||
} else {
|
||||
listener.onReblog(!buttonState, position);
|
||||
listener.onReblog(!buttonState, position, Status.Visibility.PUBLIC);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
|
|
@ -739,13 +739,25 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
popup.inflate(R.menu.status_reblog);
|
||||
Menu menu = popup.getMenu();
|
||||
if (buttonState) {
|
||||
menu.findItem(R.id.menu_action_reblog).setVisible(false);
|
||||
menu.setGroupVisible(R.id.menu_action_reblog_group, false);
|
||||
} else {
|
||||
menu.findItem(R.id.menu_action_unreblog).setVisible(false);
|
||||
}
|
||||
popup.setOnMenuItemClickListener(item -> {
|
||||
listener.onReblog(!buttonState, position);
|
||||
if (!buttonState) {
|
||||
if (buttonState) {
|
||||
listener.onReblog(false, position, Status.Visibility.PUBLIC);
|
||||
} else {
|
||||
Status.Visibility visibility;
|
||||
if (item.getItemId() == R.id.menu_action_reblog_public) {
|
||||
visibility = Status.Visibility.PUBLIC;
|
||||
} else if (item.getItemId() == R.id.menu_action_reblog_unlisted) {
|
||||
visibility = Status.Visibility.UNLISTED;
|
||||
} else if (item.getItemId() == R.id.menu_action_reblog_private) {
|
||||
visibility = Status.Visibility.PRIVATE;
|
||||
} else {
|
||||
visibility = Status.Visibility.PUBLIC;
|
||||
}
|
||||
listener.onReblog(true, position, visibility);
|
||||
reblogButton.playAnimation();
|
||||
reblogButton.setChecked(true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ import com.keylesspalace.tusky.appstore.EventHub
|
|||
import com.keylesspalace.tusky.appstore.PreferenceChangedEvent
|
||||
import com.keylesspalace.tusky.components.account.AccountActivity
|
||||
import com.keylesspalace.tusky.databinding.FragmentTimelineBinding
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.fragment.SFragment
|
||||
import com.keylesspalace.tusky.interfaces.ReselectableFragment
|
||||
import com.keylesspalace.tusky.interfaces.StatusActionListener
|
||||
|
|
@ -231,7 +232,7 @@ class ConversationsFragment :
|
|||
adapter?.refresh()
|
||||
}
|
||||
|
||||
override fun onReblog(reblog: Boolean, position: Int) {
|
||||
override fun onReblog(reblog: Boolean, position: Int, visibility: Status.Visibility) {
|
||||
// its impossible to reblog private messages
|
||||
}
|
||||
|
||||
|
|
@ -335,7 +336,7 @@ class ConversationsFragment :
|
|||
}
|
||||
}
|
||||
|
||||
override fun onVoteInPoll(position: Int, choices: MutableList<Int>) {
|
||||
override fun onVoteInPoll(position: Int, choices: List<Int>) {
|
||||
adapter?.peek(position)?.let { conversation ->
|
||||
viewModel.voteInPoll(choices, conversation)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ import com.keylesspalace.tusky.components.systemnotifications.NotificationChanne
|
|||
import com.keylesspalace.tusky.components.systemnotifications.NotificationService
|
||||
import com.keylesspalace.tusky.databinding.FragmentTimelineNotificationsBinding
|
||||
import com.keylesspalace.tusky.databinding.NotificationsFilterBinding
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.fragment.SFragment
|
||||
import com.keylesspalace.tusky.interfaces.AccountActionListener
|
||||
import com.keylesspalace.tusky.interfaces.ReselectableFragment
|
||||
|
|
@ -335,9 +336,9 @@ class NotificationsFragment :
|
|||
viewModel.remove(notification.id)
|
||||
}
|
||||
|
||||
override fun onReblog(reblog: Boolean, position: Int) {
|
||||
override fun onReblog(reblog: Boolean, position: Int, visibility: Status.Visibility) {
|
||||
val status = notificationsAdapter?.peek(position)?.asStatusOrNull() ?: return
|
||||
viewModel.reblog(reblog, status)
|
||||
viewModel.reblog(reblog, status, visibility)
|
||||
}
|
||||
|
||||
override val onMoreTranslate: (translate: Boolean, position: Int) -> Unit
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ import com.keylesspalace.tusky.db.AppDatabase
|
|||
import com.keylesspalace.tusky.db.entity.NotificationPolicyEntity
|
||||
import com.keylesspalace.tusky.entity.Filter
|
||||
import com.keylesspalace.tusky.entity.Notification
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.network.FilterModel
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.settings.PrefKeys
|
||||
|
|
@ -204,8 +205,8 @@ class NotificationsViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
fun reblog(reblog: Boolean, status: StatusViewData.Concrete): Job = viewModelScope.launch {
|
||||
timelineCases.reblog(status.actionableId, reblog).onFailure { t ->
|
||||
fun reblog(reblog: Boolean, status: StatusViewData.Concrete, visibility: Status.Visibility = Status.Visibility.PUBLIC): Job = viewModelScope.launch {
|
||||
timelineCases.reblog(status.actionableId, reblog, visibility).onFailure { t ->
|
||||
ifExpected(t) {
|
||||
Log.w(TAG, "Failed to reblog status " + status.actionableId, t)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import com.keylesspalace.tusky.R
|
|||
import com.keylesspalace.tusky.components.notifications.NotificationActionListener
|
||||
import com.keylesspalace.tusky.components.notifications.NotificationsPagingAdapter
|
||||
import com.keylesspalace.tusky.databinding.FragmentNotificationRequestDetailsBinding
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.fragment.SFragment
|
||||
import com.keylesspalace.tusky.interfaces.AccountActionListener
|
||||
import com.keylesspalace.tusky.interfaces.StatusActionListener
|
||||
|
|
@ -160,9 +161,9 @@ class NotificationRequestDetailsFragment : SFragment(R.layout.fragment_notificat
|
|||
viewModel.remove(notification)
|
||||
}
|
||||
|
||||
override fun onReblog(reblog: Boolean, position: Int) {
|
||||
override fun onReblog(reblog: Boolean, position: Int, visibility: Status.Visibility) {
|
||||
val status = adapter?.peek(position)?.asStatusOrNull() ?: return
|
||||
viewModel.reblog(reblog, status)
|
||||
viewModel.reblog(reblog, status, visibility)
|
||||
}
|
||||
|
||||
override val onMoreTranslate: ((Boolean, Int) -> Unit)?
|
||||
|
|
|
|||
|
|
@ -158,8 +158,8 @@ class NotificationRequestDetailsViewModel @AssistedInject constructor(
|
|||
currentSource?.invalidate()
|
||||
}
|
||||
|
||||
fun reblog(reblog: Boolean, status: StatusViewData.Concrete) = viewModelScope.launch {
|
||||
timelineCases.reblog(status.actionableId, reblog).onFailure { t ->
|
||||
fun reblog(reblog: Boolean, status: StatusViewData.Concrete, visibility: Status.Visibility = Status.Visibility.PUBLIC) = viewModelScope.launch {
|
||||
timelineCases.reblog(status.actionableId, reblog, visibility).onFailure { t ->
|
||||
ifExpected(t) {
|
||||
Log.w(TAG, "Failed to reblog status " + status.actionableId, t)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,9 +139,9 @@ class SearchViewModel @Inject constructor(
|
|||
updateStatusViewData(statusViewData.copy(isExpanded = expanded))
|
||||
}
|
||||
|
||||
fun reblog(statusViewData: StatusViewData.Concrete, reblog: Boolean) {
|
||||
fun reblog(statusViewData: StatusViewData.Concrete, reblog: Boolean, visibility: Status.Visibility = Status.Visibility.PUBLIC) {
|
||||
viewModelScope.launch {
|
||||
timelineCases.reblog(statusViewData.id, reblog).fold({
|
||||
timelineCases.reblog(statusViewData.id, reblog, visibility).fold({
|
||||
updateStatus(
|
||||
statusViewData.status.copy(
|
||||
reblogged = reblog,
|
||||
|
|
@ -162,7 +162,7 @@ class SearchViewModel @Inject constructor(
|
|||
updateStatusViewData(statusViewData.copy(isCollapsed = collapsed))
|
||||
}
|
||||
|
||||
fun voteInPoll(statusViewData: StatusViewData.Concrete, choices: MutableList<Int>) {
|
||||
fun voteInPoll(statusViewData: StatusViewData.Concrete, choices: List<Int>) {
|
||||
val votedPoll = statusViewData.status.actionableStatus.poll!!.votedCopy(choices)
|
||||
updateStatus(statusViewData.status.copy(poll = votedPoll))
|
||||
viewModelScope.launch {
|
||||
|
|
|
|||
|
|
@ -251,7 +251,7 @@ class SearchStatusesFragment : SearchFragment<StatusViewData.Concrete>(), Status
|
|||
}
|
||||
}
|
||||
|
||||
override fun onVoteInPoll(position: Int, choices: MutableList<Int>) {
|
||||
override fun onVoteInPoll(position: Int, choices: List<Int>) {
|
||||
adapter?.peek(position)?.let {
|
||||
viewModel.voteInPoll(it, choices)
|
||||
}
|
||||
|
|
@ -265,9 +265,9 @@ class SearchStatusesFragment : SearchFragment<StatusViewData.Concrete>(), Status
|
|||
}
|
||||
}
|
||||
|
||||
override fun onReblog(reblog: Boolean, position: Int) {
|
||||
override fun onReblog(reblog: Boolean, position: Int, visibility: Status.Visibility) {
|
||||
adapter?.peek(position)?.let { status ->
|
||||
viewModel.reblog(status, reblog)
|
||||
viewModel.reblog(status, reblog, visibility)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -415,9 +415,9 @@ class TimelineFragment :
|
|||
super.reply(status.status)
|
||||
}
|
||||
|
||||
override fun onReblog(reblog: Boolean, position: Int) {
|
||||
override fun onReblog(reblog: Boolean, position: Int, visibility: Status.Visibility) {
|
||||
val status = adapter?.peek(position)?.asStatusOrNull() ?: return
|
||||
viewModel.reblog(reblog, status)
|
||||
viewModel.reblog(reblog, status, visibility)
|
||||
}
|
||||
|
||||
private fun onTranslate(position: Int) {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import com.keylesspalace.tusky.components.preference.PreferencesFragment.Reading
|
|||
import com.keylesspalace.tusky.components.timeline.util.ifExpected
|
||||
import com.keylesspalace.tusky.db.AccountManager
|
||||
import com.keylesspalace.tusky.entity.Filter
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.network.FilterModel
|
||||
import com.keylesspalace.tusky.settings.PrefKeys
|
||||
import com.keylesspalace.tusky.usecase.TimelineCases
|
||||
|
|
@ -107,9 +108,9 @@ abstract class TimelineViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
fun reblog(reblog: Boolean, status: StatusViewData.Concrete): Job = viewModelScope.launch {
|
||||
fun reblog(reblog: Boolean, status: StatusViewData.Concrete, visibility: Status.Visibility = Status.Visibility.PUBLIC): Job = viewModelScope.launch {
|
||||
try {
|
||||
timelineCases.reblog(status.actionableId, reblog).getOrThrow()
|
||||
timelineCases.reblog(status.actionableId, reblog, visibility).getOrThrow()
|
||||
} catch (t: Exception) {
|
||||
ifExpected(t) {
|
||||
Log.d(TAG, "Failed to reblog status " + status.actionableId, t)
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import com.keylesspalace.tusky.components.accountlist.AccountListActivity
|
|||
import com.keylesspalace.tusky.components.accountlist.AccountListActivity.Companion.newIntent
|
||||
import com.keylesspalace.tusky.components.viewthread.edits.ViewEditsFragment
|
||||
import com.keylesspalace.tusky.databinding.FragmentViewThreadBinding
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.fragment.SFragment
|
||||
import com.keylesspalace.tusky.interfaces.StatusActionListener
|
||||
import com.keylesspalace.tusky.settings.PrefKeys
|
||||
|
|
@ -324,9 +325,9 @@ class ViewThreadFragment :
|
|||
super.reply(viewData.status)
|
||||
}
|
||||
|
||||
override fun onReblog(reblog: Boolean, position: Int) {
|
||||
override fun onReblog(reblog: Boolean, position: Int, visibility: Status.Visibility) {
|
||||
val status = adapter?.currentList?.getOrNull(position) ?: return
|
||||
viewModel.reblog(reblog, status)
|
||||
viewModel.reblog(reblog, status, visibility)
|
||||
}
|
||||
|
||||
override val onMoreTranslate: ((translate: Boolean, position: Int) -> Unit) =
|
||||
|
|
|
|||
|
|
@ -196,9 +196,9 @@ class ViewThreadViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
fun reblog(reblog: Boolean, status: StatusViewData.Concrete): Job = viewModelScope.launch {
|
||||
fun reblog(reblog: Boolean, status: StatusViewData.Concrete, visibility: Status.Visibility = Status.Visibility.PUBLIC): Job = viewModelScope.launch {
|
||||
try {
|
||||
timelineCases.reblog(status.actionableId, reblog).getOrThrow()
|
||||
timelineCases.reblog(status.actionableId, reblog, visibility).getOrThrow()
|
||||
} catch (t: Exception) {
|
||||
ifExpected(t) {
|
||||
Log.d(TAG, "Failed to reblog status " + status.actionableId, t)
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ import kotlinx.coroutines.launch
|
|||
* up what needs to be where. */
|
||||
abstract class SFragment(@LayoutRes contentLayoutId: Int) : Fragment(contentLayoutId) {
|
||||
protected abstract fun removeItem(position: Int)
|
||||
protected abstract fun onReblog(reblog: Boolean, position: Int)
|
||||
protected abstract fun onReblog(reblog: Boolean, position: Int, visibility: Status.Visibility)
|
||||
|
||||
/** `null` if translation is not supported on this screen */
|
||||
protected abstract val onMoreTranslate: ((translate: Boolean, position: Int) -> Unit)?
|
||||
|
|
@ -318,12 +318,12 @@ abstract class SFragment(@LayoutRes contentLayoutId: Int) : Fragment(contentLayo
|
|||
}
|
||||
|
||||
R.id.status_unreblog_private -> {
|
||||
onReblog(false, position)
|
||||
onReblog(false, position, Status.Visibility.PUBLIC)
|
||||
return@setOnMenuItemClickListener true
|
||||
}
|
||||
|
||||
R.id.status_reblog_private -> {
|
||||
onReblog(true, position)
|
||||
onReblog(true, position, Status.Visibility.PUBLIC)
|
||||
return@setOnMenuItemClickListener true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,60 +12,55 @@
|
|||
*
|
||||
* 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.interfaces
|
||||
|
||||
package com.keylesspalace.tusky.interfaces;
|
||||
import android.view.View
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public interface StatusActionListener extends LinkListener {
|
||||
void onReply(int position);
|
||||
void onReblog(final boolean reblog, final int position);
|
||||
void onFavourite(final boolean favourite, final int position);
|
||||
void onBookmark(final boolean bookmark, final int position);
|
||||
void onMore(@NonNull View view, final int position);
|
||||
void onViewMedia(int position, int attachmentIndex, @Nullable View view);
|
||||
void onViewThread(int position);
|
||||
interface StatusActionListener : LinkListener {
|
||||
fun onReply(position: Int)
|
||||
fun onReblog(reblog: Boolean, position: Int, visibility: Status.Visibility = Status.Visibility.PUBLIC)
|
||||
fun onFavourite(favourite: Boolean, position: Int)
|
||||
fun onBookmark(bookmark: Boolean, position: Int)
|
||||
fun onMore(view: View, position: Int)
|
||||
fun onViewMedia(position: Int, attachmentIndex: Int, view: View?)
|
||||
fun onViewThread(position: Int)
|
||||
|
||||
/**
|
||||
* Open reblog author for the status.
|
||||
* @param position At which position in the list status is located
|
||||
*/
|
||||
void onOpenReblog(int position);
|
||||
void onExpandedChange(boolean expanded, int position);
|
||||
void onContentHiddenChange(boolean isShowing, int position);
|
||||
void onLoadMore(int position);
|
||||
fun onOpenReblog(position: Int)
|
||||
fun onExpandedChange(expanded: Boolean, position: Int)
|
||||
fun onContentHiddenChange(isShowing: Boolean, position: Int)
|
||||
fun onLoadMore(position: Int)
|
||||
|
||||
/**
|
||||
* Called when the status {@link android.widget.ToggleButton} responsible for collapsing long
|
||||
* Called when the status [android.widget.ToggleButton] responsible for collapsing long
|
||||
* status content is interacted with.
|
||||
*
|
||||
* @param isCollapsed Whether the status content is shown in a collapsed state or fully.
|
||||
* @param position The position of the status in the list.
|
||||
*/
|
||||
void onContentCollapsedChange(boolean isCollapsed, int position);
|
||||
fun onContentCollapsedChange(isCollapsed: Boolean, position: Int)
|
||||
|
||||
/**
|
||||
* called when the reblog count has been clicked
|
||||
* @param position The position of the status in the list.
|
||||
*/
|
||||
default void onShowReblogs(int position) {}
|
||||
fun onShowReblogs(position: Int) {}
|
||||
|
||||
/**
|
||||
* called when the favourite count has been clicked
|
||||
* @param position The position of the status in the list.
|
||||
*/
|
||||
default void onShowFavs(int position) {}
|
||||
fun onShowFavs(position: Int) {}
|
||||
|
||||
void onVoteInPoll(int position, @NonNull List<Integer> choices);
|
||||
fun onVoteInPoll(position: Int, choices: List<Int>)
|
||||
|
||||
default void onShowEdits(int position) {}
|
||||
fun onShowEdits(position: Int) {}
|
||||
|
||||
void clearWarningAction(int position);
|
||||
fun clearWarningAction(position: Int)
|
||||
|
||||
void onUntranslate(int position);
|
||||
fun onUntranslate(position: Int)
|
||||
}
|
||||
|
|
@ -252,8 +252,9 @@ interface MastodonApi {
|
|||
@DELETE("api/v1/statuses/{id}")
|
||||
suspend fun deleteStatus(@Path("id") statusId: String): NetworkResult<DeletedStatus>
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST("api/v1/statuses/{id}/reblog")
|
||||
suspend fun reblogStatus(@Path("id") statusId: String): NetworkResult<Status>
|
||||
suspend fun reblogStatus(@Path("id") statusId: String, @Field("visibility") visibility: String?): NetworkResult<Status>
|
||||
|
||||
@POST("api/v1/statuses/{id}/unreblog")
|
||||
suspend fun unreblogStatus(@Path("id") statusId: String): NetworkResult<Status>
|
||||
|
|
|
|||
|
|
@ -44,9 +44,9 @@ class TimelineCases @Inject constructor(
|
|||
private val eventHub: EventHub
|
||||
) {
|
||||
|
||||
suspend fun reblog(statusId: String, reblog: Boolean): NetworkResult<Status> {
|
||||
suspend fun reblog(statusId: String, reblog: Boolean, visibility: Status.Visibility = Status.Visibility.PUBLIC): NetworkResult<Status> {
|
||||
return if (reblog) {
|
||||
mastodonApi.reblogStatus(statusId)
|
||||
mastodonApi.reblogStatus(statusId, visibility.stringValue)
|
||||
} else {
|
||||
mastodonApi.unreblogStatus(statusId)
|
||||
}.onSuccess { status ->
|
||||
|
|
|
|||
|
|
@ -1,9 +1,23 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/menu_action_reblog"
|
||||
android:icon="@drawable/ic_reblog_24dp"
|
||||
android:title="@string/action_reblog" />
|
||||
<group
|
||||
android:id="@+id/menu_action_reblog_group">
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_action_reblog_public"
|
||||
android:icon="@drawable/ic_reblog_24dp"
|
||||
android:title="@string/action_reblog_public" />
|
||||
<item
|
||||
android:id="@+id/menu_action_reblog_unlisted"
|
||||
android:icon="@drawable/ic_reblog_24dp"
|
||||
android:title="@string/action_reblog_unlisted" />
|
||||
<item
|
||||
android:id="@+id/menu_action_reblog_private"
|
||||
android:icon="@drawable/ic_reblog_24dp"
|
||||
android:title="@string/action_reblog_private" />
|
||||
|
||||
</group>
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_action_unreblog"
|
||||
android:icon="@drawable/ic_reblog_24dp"
|
||||
|
|
|
|||
|
|
@ -118,6 +118,9 @@
|
|||
<string name="action_quick_reply">Quick Reply</string>
|
||||
<string name="action_reply">Reply</string>
|
||||
<string name="action_reblog">Boost</string>
|
||||
<string name="action_reblog_private">Followers-Only Boost</string>
|
||||
<string name="action_reblog_public">Public Boost</string>
|
||||
<string name="action_reblog_unlisted">Unlisted Boost</string>
|
||||
<string name="action_unreblog">Remove boost</string>
|
||||
<string name="action_favourite">Favorite</string>
|
||||
<string name="action_unfavourite">Remove favorite</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue