ComposeActivity: preview ability for Pleroma

Based on these commits by a1ba:
- c3c92fadcd
- f9f2f9aa5b
This commit is contained in:
fruye 2022-11-13 20:08:14 +01:00
parent 4bddbb0d50
commit 6a66056216
9 changed files with 178 additions and 15 deletions

View File

@ -16,6 +16,7 @@ data class StatusDeletedEvent(val statusId: String) : Event
data class StatusComposedEvent(val status: Status) : Event
data class StatusScheduledEvent(val status: Status) : Event
data class StatusEditedEvent(val originalId: String, val status: Status) : Event
data class StatusPreviewEvent(val status: Status) : Event
data class ProfileEditedEvent(val newProfileData: Account) : Event
data class PreferenceChangedEvent(val preferenceKey: String) : Event
data class MainTabsChangedEvent(val newTabs: List<TabData>) : Event

View File

@ -78,6 +78,7 @@ import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.adapter.EmojiAdapter
import com.keylesspalace.tusky.adapter.LocaleAdapter
import com.keylesspalace.tusky.adapter.OnEmojiSelectedListener
import com.keylesspalace.tusky.appstore.*
import com.keylesspalace.tusky.components.compose.ComposeViewModel.ConfirmationKind
import com.keylesspalace.tusky.components.compose.dialog.CaptionDialog
import com.keylesspalace.tusky.components.compose.dialog.makeFocusDialog
@ -140,10 +141,14 @@ class ComposeActivity :
@Inject
lateinit var viewModelFactory: ViewModelFactory
@Inject
lateinit var eventHub: EventHub
private lateinit var composeOptionsBehavior: BottomSheetBehavior<*>
private lateinit var addMediaBehavior: BottomSheetBehavior<*>
private lateinit var emojiBehavior: BottomSheetBehavior<*>
private lateinit var scheduleBehavior: BottomSheetBehavior<*>
private lateinit var previewBehavior: BottomSheetBehavior<*>
/** The account that is being used to compose the status */
private lateinit var activeAccount: AccountEntity
@ -427,6 +432,7 @@ class ComposeActivity :
updateVisibleCharactersLeft()
}
}
binding.composePreviewButton.visibility = View.VISIBLE
lifecycleScope.launch {
viewModel.emoji.collect(::setEmojiList)
@ -489,6 +495,14 @@ class ComposeActivity :
}
}
}
lifecycleScope.launch {
eventHub.events.collect { event ->
when (event) {
is StatusPreviewEvent -> onStatusPreviewReady(event.status)
}
}
}
}
private fun setupButtons() {
@ -498,11 +512,13 @@ class ComposeActivity :
addMediaBehavior = BottomSheetBehavior.from(binding.addMediaBottomSheet)
scheduleBehavior = BottomSheetBehavior.from(binding.composeScheduleView)
emojiBehavior = BottomSheetBehavior.from(binding.emojiView)
previewBehavior = BottomSheetBehavior.from(binding.previewScroll)
enableButton(binding.composeEmojiButton, clickable = false, colorActive = false)
// Setup the interface buttons.
binding.composeTootButton.setOnClickListener { onSendClicked() }
binding.composeTootButton.setOnClickListener { onSendClicked(false) }
binding.composePreviewButton.setOnClickListener { onSendClicked(true) }
binding.composeAddMediaButton.setOnClickListener { openPickDialog() }
binding.composeToggleVisibilityButton.setOnClickListener { showComposeOptions() }
binding.composeContentWarningButton.setOnClickListener { onContentWarningChanged() }
@ -547,6 +563,7 @@ class ComposeActivity :
addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN
previewBehavior.state = BottomSheetBehavior.STATE_HIDDEN
return
}
@ -767,6 +784,7 @@ class ComposeActivity :
addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
scheduleBehavior.setState(BottomSheetBehavior.STATE_HIDDEN)
previewBehavior.state = BottomSheetBehavior.STATE_HIDDEN
} else {
composeOptionsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN)
}
@ -786,6 +804,7 @@ class ComposeActivity :
composeOptionsBehavior.state = BottomSheetBehavior.STATE_HIDDEN
addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN
emojiBehavior.setState(BottomSheetBehavior.STATE_HIDDEN)
previewBehavior.state = BottomSheetBehavior.STATE_HIDDEN
} else {
scheduleBehavior.setState(BottomSheetBehavior.STATE_HIDDEN)
}
@ -802,6 +821,7 @@ class ComposeActivity :
composeOptionsBehavior.state = BottomSheetBehavior.STATE_HIDDEN
addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN
scheduleBehavior.setState(BottomSheetBehavior.STATE_HIDDEN)
previewBehavior.state = BottomSheetBehavior.STATE_HIDDEN
} else {
emojiBehavior.setState(BottomSheetBehavior.STATE_HIDDEN)
}
@ -815,6 +835,7 @@ class ComposeActivity :
composeOptionsBehavior.state = BottomSheetBehavior.STATE_HIDDEN
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
scheduleBehavior.setState(BottomSheetBehavior.STATE_HIDDEN)
previewBehavior.state = BottomSheetBehavior.STATE_HIDDEN
} else {
addMediaBehavior.setState(BottomSheetBehavior.STATE_HIDDEN)
}
@ -929,9 +950,13 @@ class ComposeActivity :
return binding.composeScheduleView.verifyScheduledTime(binding.composeScheduleView.getDateTime(viewModel.scheduledAt.value))
}
private fun onSendClicked() {
private fun onSendClicked(preview: Boolean) {
if(preview && previewBehavior.state != BottomSheetBehavior.STATE_HIDDEN) {
previewBehavior.state = BottomSheetBehavior.STATE_HIDDEN
}
if (verifyScheduledTime()) {
sendStatus()
sendStatus(preview)
} else {
showScheduleView()
}
@ -954,7 +979,17 @@ class ComposeActivity :
return contentInfo
}
private fun sendStatus() {
private fun onStatusPreviewReady(status: Status) {
enableButtons(true, viewModel.editing)
binding.previewView.setupWithStatus(status)
previewBehavior.state = BottomSheetBehavior.STATE_EXPANDED
addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN
composeOptionsBehavior.state = BottomSheetBehavior.STATE_HIDDEN
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN
}
private fun sendStatus(preview: Boolean) {
enableButtons(false, viewModel.editing)
val contentText = binding.composeEditField.text.toString()
var spoilerText = ""
@ -967,8 +1002,9 @@ class ComposeActivity :
enableButtons(true, viewModel.editing)
} else if (characterCount <= maximumTootCharacters) {
lifecycleScope.launch {
viewModel.sendStatus(contentText, spoilerText, activeAccount.id)
deleteDraftAndFinish()
viewModel.sendStatus(contentText, spoilerText, activeAccount.id, preview)
if (!preview)
deleteDraftAndFinish()
}
} else {
binding.composeEditField.error = getString(R.string.error_compose_character_limit)
@ -1114,7 +1150,7 @@ class ComposeActivity :
if (event.isCtrlPressed) {
if (keyCode == KeyEvent.KEYCODE_ENTER) {
// send toot by pressing CTRL + ENTER
this.onSendClicked()
this.onSendClicked(false)
return true
}
}

View File

@ -310,7 +310,8 @@ class ComposeViewModel @Inject constructor(
suspend fun sendStatus(
content: String,
spoilerText: String,
accountId: Long
accountId: Long,
preview: Boolean
) {
if (!scheduledTootId.isNullOrEmpty()) {
api.deleteScheduledStatus(scheduledTootId!!)
@ -342,7 +343,8 @@ class ComposeViewModel @Inject constructor(
idempotencyKey = randomAlphanumericString(16),
retries = 0,
language = postLanguage,
statusId = originalStatusId
statusId = originalStatusId,
preview = preview,
)
serviceClient.sendToot(tootToSend)

View File

@ -29,7 +29,8 @@ data class NewStatus(
@SerializedName("media_attributes") val mediaAttributes: List<MediaAttribute>?,
@SerializedName("scheduled_at") val scheduledAt: String?,
val poll: NewPoll?,
val language: String?
val language: String?,
val preview: Boolean?,
)
@Parcelize

View File

@ -97,7 +97,8 @@ class SendStatusBroadcastReceiver : BroadcastReceiver() {
idempotencyKey = randomAlphanumericString(16),
retries = 0,
language = null,
statusId = null
statusId = null,
preview = false,
)
)

View File

@ -23,6 +23,7 @@ import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.appstore.EventHub
import com.keylesspalace.tusky.appstore.StatusComposedEvent
import com.keylesspalace.tusky.appstore.StatusEditedEvent
import com.keylesspalace.tusky.appstore.StatusPreviewEvent
import com.keylesspalace.tusky.appstore.StatusScheduledEvent
import com.keylesspalace.tusky.components.compose.MediaUploader
import com.keylesspalace.tusky.components.compose.UploadEvent
@ -219,7 +220,8 @@ class SendStatusService : Service(), Injectable {
focus = media.focus?.toMastodonApiString(),
thumbnail = null
)
}
},
preview = if (statusToSend.preview) true else null,
)
val sendResult = if (isNew) {
@ -250,7 +252,9 @@ class SendStatusService : Service(), Injectable {
val scheduled = !statusToSend.scheduledAt.isNullOrEmpty()
if (scheduled) {
if (statusToSend.preview) {
eventHub.dispatch(StatusPreviewEvent(sentStatus))
} else if (scheduled) {
eventHub.dispatch(StatusScheduledEvent(sentStatus))
} else if (!isNew) {
eventHub.dispatch(StatusEditedEvent(statusToSend.statusId!!, sentStatus))
@ -463,7 +467,8 @@ data class StatusToSend(
val idempotencyKey: String,
var retries: Int,
val language: String?,
val statusId: String?
val statusId: String?,
val preview : Boolean,
) : Parcelable
@Parcelize

View File

@ -0,0 +1,76 @@
package com.keylesspalace.tusky.view
import android.view.*
import android.content.*
import android.util.*
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.preference.PreferenceManager
import com.keylesspalace.tusky.adapter.StatusViewHolder
import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.interfaces.StatusActionListener
import com.keylesspalace.tusky.util.CardViewMode
import com.keylesspalace.tusky.util.StatusDisplayOptions
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.settings.PrefKeys
import com.keylesspalace.tusky.viewdata.StatusViewData
import java.util.*;
class StatusView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0)
: ConstraintLayout(context, attrs, defStyleAttr) {
private var viewHolder : StatusViewHolder
private var statusDisplayOptions : StatusDisplayOptions
init {
View.inflate(context, R.layout.item_status, this)
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
statusDisplayOptions = StatusDisplayOptions(
animateAvatars = preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false),
mediaPreviewEnabled = true, // FIXME: get value from AccountManager
useAbsoluteTime = preferences.getBoolean(PrefKeys.ABSOLUTE_TIME_VIEW, false),
showBotOverlay = preferences.getBoolean(PrefKeys.SHOW_BOT_OVERLAY, true),
useBlurhash = preferences.getBoolean(PrefKeys.USE_BLURHASH, true),
cardViewMode = CardViewMode.NONE,
confirmReblogs = false,
confirmFavourites = false,
hideStats = false,
animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false),
showStatsInline = false,
showSensitiveMedia = false, // FIXME: get value from AccountManager
openSpoiler = false, // FIXME: get value from AccountManager
bigEmojis = preferences.getBoolean(PrefKeys.BIG_EMOJIS, true),
)
viewHolder = StatusViewHolder(this)
}
fun setupWithStatus(status: Status) {
val concrete = StatusViewData.Concrete(status, false, false, false)
viewHolder.setupWithStatus(concrete, DummyStatusActionListener(), statusDisplayOptions)
}
class DummyStatusActionListener: StatusActionListener {
override fun onReply(position: Int) { }
override fun onReblog(reblog: Boolean, position: Int) { }
override fun onFavourite(favourite: Boolean, position: Int) { }
override fun onBookmark(bookmark: Boolean, position: Int) { }
override fun onMore(view: View, position: Int) { }
override fun onViewMedia(position: Int, attachmentIndex: Int, view: View?) { }
override fun onViewThread(position: Int) { }
override fun onViewReplyTo(position: Int) { }
override fun onOpenReblog(position: Int) { }
override fun onExpandedChange(expanded: Boolean, position: Int) { }
override fun onContentHiddenChange(isShowing: Boolean, position: Int) { }
override fun onLoadMore(position: Int) { }
override fun onContentCollapsedChange(isCollapsed: Boolean, position: Int) { }
override fun onVoteInPoll(position: Int, choices: MutableList<Int>) { }
override fun onViewAccount(id: String) { }
override fun onViewTag(id: String) { }
override fun onViewUrl(id: String) { }
override fun clearWarningAction(position: Int) { }
}
}

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M19,3H5C3.89,3 3,3.9 3,5v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V5C21,3.9 20.11,3 19,3zM19,19H5V7h14V19zM13.5,13c0,0.83 -0.67,1.5 -1.5,1.5s-1.5,-0.67 -1.5,-1.5c0,-0.83 0.67,-1.5 1.5,-1.5S13.5,12.17 13.5,13zM12,9c-2.73,0 -5.06,1.66 -6,4c0.94,2.34 3.27,4 6,4s5.06,-1.66 6,-4C17.06,10.66 14.73,9 12,9zM12,15.5c-1.38,0 -2.5,-1.12 -2.5,-2.5c0,-1.38 1.12,-2.5 2.5,-2.5c1.38,0 2.5,1.12 2.5,2.5C14.5,14.38 13.38,15.5 12,15.5z"
android:fillColor="#000000"/>
</vector>

View File

@ -278,6 +278,25 @@
app:behavior_peekHeight="0dp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior" />
<ScrollView
android:id="@+id/previewScroll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorSurface"
android:elevation="12dp"
android:paddingBottom="60dp"
app:behavior_hideable="true"
app:behavior_peekHeight="0dp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
<com.keylesspalace.tusky.view.StatusView
android:id="@+id/previewView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorSurface" />
</ScrollView>
<LinearLayout
android:id="@+id/composeBottomBar"
android:layout_width="match_parent"
@ -373,12 +392,25 @@
android:textStyle="bold"
tools:text="500" />
<com.google.android.material.button.MaterialButton
android:id="@+id/composePreviewButton"
style="@style/TuskyButton"
android:padding="4dp"
android:layout_width="32dp"
android:layout_height="wrap_content"
android:gravity="center"
app:icon="@drawable/ic_preview_24dp"
android:layout_marginStart="10dp"
android:visibility="gone"
android:layout_toLeftOf="@+id/composeTootButton"
android:layout_centerVertical="true"/>
<com.keylesspalace.tusky.components.compose.view.TootButton
android:id="@+id/composeTootButton"
style="@style/TuskyButton"
android:layout_width="@dimen/toot_button_width"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginStart="4dp"
android:textSize="?attr/status_text_medium" />
</LinearLayout>