ComposeActivity: preview ability for Pleroma, WIP

This commit is contained in:
Alibek Omarov 2020-06-23 18:18:41 +03:00
parent a38acd34a5
commit 467bfefde6
9 changed files with 178 additions and 17 deletions

View File

@ -14,6 +14,7 @@ data class UnfollowEvent(val accountId: String) : Dispatchable
data class BlockEvent(val accountId: String) : Dispatchable
data class MuteEvent(val accountId: String) : Dispatchable
data class StatusDeletedEvent(val statusId: String) : Dispatchable
data class StatusPreviewEvent(val status: Status) : Dispatchable
data class StatusComposedEvent(val status: Status) : Dispatchable
data class StatusScheduledEvent(val status: Status) : Dispatchable
data class ProfileEditedEvent(val newProfileData: Account) : Dispatchable

View File

@ -70,6 +70,7 @@ import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.adapter.ComposeAutoCompleteAdapter
import com.keylesspalace.tusky.adapter.EmojiAdapter
import com.keylesspalace.tusky.adapter.OnEmojiSelectedListener
import com.keylesspalace.tusky.appstore.*
import com.keylesspalace.tusky.components.compose.dialog.makeCaptionDialog
import com.keylesspalace.tusky.components.compose.dialog.showAddPollDialog
import com.keylesspalace.tusky.components.compose.view.ComposeOptionsListener
@ -108,12 +109,16 @@ class ComposeActivity : BaseActivity(),
@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 stickerBehavior: BottomSheetBehavior<*>
private lateinit var previewBehavior: BottomSheetBehavior<*>
// this only exists when a status is trying to be sent, but uploads are still occurring
private var finishingUploadDialog: ProgressDialog? = null
@ -192,6 +197,12 @@ class ComposeActivity : BaseActivity(),
viewModel.setupComplete.value = true
stickerKeyboard.isSticky = true
eventHub.events.subscribe { event ->
when(event) {
is StatusPreviewEvent -> onStatusPreviewReady(event.status)
}
}
}
private fun uriToFilename(uri: Uri): String {
@ -383,6 +394,7 @@ class ComposeActivity : BaseActivity(),
}
if(instanceData.software.equals("pleroma")) {
composePreviewButton.visibility = View.VISIBLE
reenableAttachments()
}
}
@ -455,13 +467,15 @@ class ComposeActivity : BaseActivity(),
scheduleBehavior = BottomSheetBehavior.from(composeScheduleView)
emojiBehavior = BottomSheetBehavior.from(emojiView)
stickerBehavior = BottomSheetBehavior.from(stickerKeyboard)
previewBehavior = BottomSheetBehavior.from(previewScroll)
emojiView.layoutManager = GridLayoutManager(this, 3, GridLayoutManager.HORIZONTAL, false)
enableButton(composeEmojiButton, clickable = false, colorActive = false)
enableButton(composeStickerButton, false, false)
// Setup the interface buttons.
composeTootButton.setOnClickListener { onSendClicked() }
composeTootButton.setOnClickListener { onSendClicked(false) }
composePreviewButton.setOnClickListener { onSendClicked(true) }
composeAddMediaButton.setOnClickListener { openPickDialog() }
composeToggleVisibilityButton.setOnClickListener { showComposeOptions() }
composeContentWarningButton.setOnClickListener { onContentWarningChanged() }
@ -771,6 +785,7 @@ class ComposeActivity : BaseActivity(),
composeScheduleButton.isClickable = enable
composeFormattingSyntax.isClickable = enable
composeTootButton.isEnabled = enable
composePreviewButton.isEnabled = enable
composeStickerButton.isEnabled = enable
}
@ -796,6 +811,7 @@ class ComposeActivity : BaseActivity(),
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
stickerBehavior.state = BottomSheetBehavior.STATE_HIDDEN
scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN
previewBehavior.state = BottomSheetBehavior.STATE_HIDDEN
} else {
composeOptionsBehavior.state = BottomSheetBehavior.STATE_HIDDEN
}
@ -816,6 +832,7 @@ class ComposeActivity : BaseActivity(),
addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
stickerBehavior.state = BottomSheetBehavior.STATE_HIDDEN
previewBehavior.state = BottomSheetBehavior.STATE_HIDDEN
} else {
scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN
}
@ -833,6 +850,7 @@ class ComposeActivity : BaseActivity(),
composeOptionsBehavior.state = BottomSheetBehavior.STATE_HIDDEN
addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN
scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN
previewBehavior.state = BottomSheetBehavior.STATE_HIDDEN
} else {
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
}
@ -847,6 +865,7 @@ class ComposeActivity : BaseActivity(),
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
stickerBehavior.state = BottomSheetBehavior.STATE_HIDDEN
scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN
previewBehavior.state = BottomSheetBehavior.STATE_HIDDEN
} else {
addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN
}
@ -947,13 +966,31 @@ class ComposeActivity : BaseActivity(),
return composeScheduleView.verifyScheduledTime(composeScheduleView.getDateTime(viewModel.scheduledAt.value))
}
private fun onSendClicked() {
private fun onSendClicked(preview: Boolean) {
if(preview && previewBehavior.state == BottomSheetBehavior.STATE_EXPANDED) {
previewBehavior.state = BottomSheetBehavior.STATE_HIDDEN
return
}
if (verifyScheduledTime()) {
sendStatus()
sendStatus(preview)
} else {
showScheduleView()
}
}
private fun onStatusPreviewReady(status: Status) {
enableButtons(true)
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
stickerBehavior.state = BottomSheetBehavior.STATE_HIDDEN
// Log.d("ComposeActivityPreview", "Preview: " + status.content)
}
/** This is for the fancy keyboards which can insert images and stuff. */
override fun onCommitContent(inputContentInfo: InputContentInfoCompat, flags: Int, opts: Bundle?): Boolean {
@ -977,7 +1014,7 @@ class ComposeActivity : BaseActivity(),
return false
}
private fun sendStatus() {
private fun sendStatus(preview: Boolean) {
enableButtons(false)
val contentText = composeEditField.text.toString()
var spoilerText = ""
@ -993,9 +1030,10 @@ class ComposeActivity : BaseActivity(),
this, getString(R.string.dialog_title_finishing_media_upload),
getString(R.string.dialog_message_uploading_media), true, true)
viewModel.sendStatus(contentText, spoilerText).observe(this, Observer {
viewModel.sendStatus(contentText, spoilerText, preview).observe(this, Observer {
finishingUploadDialog?.dismiss()
deleteDraftAndFinish()
if(!preview)
deleteDraftAndFinish()
})
} else {
@ -1156,6 +1194,7 @@ class ComposeActivity : BaseActivity(),
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN
stickerBehavior.state = BottomSheetBehavior.STATE_HIDDEN
previewBehavior.state = BottomSheetBehavior.STATE_HIDDEN
return
}
@ -1168,7 +1207,7 @@ class ComposeActivity : BaseActivity(),
if (event.isCtrlPressed) {
if (keyCode == KeyEvent.KEYCODE_ENTER) {
// send toot by pressing CTRL + ENTER
this.onSendClicked()
this.onSendClicked(false)
return true
}
}
@ -1229,6 +1268,7 @@ class ComposeActivity : BaseActivity(),
composeOptionsBehavior.state = BottomSheetBehavior.STATE_HIDDEN
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN
previewBehavior.state = BottomSheetBehavior.STATE_HIDDEN
} else {
stickerBehavior.state = BottomSheetBehavior.STATE_HIDDEN
}

View File

@ -302,7 +302,8 @@ class ComposeViewModel
*/
fun sendStatus(
content: String,
spoilerText: String
spoilerText: String,
preview: Boolean
): LiveData<Unit> {
return media
.filter { items -> items.all { it.uploadPercent == -1 } }
@ -330,6 +331,7 @@ class ComposeViewModel
replyingStatusContent = null,
replyingStatusAuthorUsername = null,
formattingSyntax = formattingSyntax,
preview = preview,
savedJsonUrls = null,
accountId = accountManager.activeAccount!!.id,
savedTootUid = 0,

View File

@ -30,7 +30,8 @@ data class Instance (
@SerializedName("contact_account") val contactAccount: Account,
@SerializedName("max_toot_chars") val maxTootChars: Int?,
@SerializedName("max_bio_chars") val maxBioChars: Int?,
@SerializedName("poll_limits") val pollLimits: PollLimits?
@SerializedName("poll_limits") val pollLimits: PollLimits?,
val pleroma: InstancePleroma
) {
override fun hashCode(): Int {
return uri.hashCode()
@ -45,6 +46,14 @@ data class Instance (
}
}
data class InstancePleroma (
val metadata: InstancePleromaMetadata
)
data class InstancePleromaMetadata (
val features: List<String>
)
data class PollLimits (
@SerializedName("max_options") val maxOptions: Int?,
@SerializedName("max_option_chars") val maxOptionChars: Int?

View File

@ -28,7 +28,8 @@ data class NewStatus(
@SerializedName("media_ids") val mediaIds: List<String>?,
@SerializedName("scheduled_at") val scheduledAt: String?,
val poll: NewPoll?,
var content_type: String?
var content_type: String?,
val preview: Boolean?
)
@Parcelize

View File

@ -103,6 +103,7 @@ class SendStatusBroadcastReceiver : BroadcastReceiver() {
null,
null,
"",
false,
account.id,
0,
randomAlphanumericString(16),

View File

@ -16,8 +16,7 @@ import androidx.core.app.ServiceCompat
import androidx.core.content.ContextCompat
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.appstore.EventHub
import com.keylesspalace.tusky.appstore.StatusComposedEvent
import com.keylesspalace.tusky.appstore.StatusScheduledEvent
import com.keylesspalace.tusky.appstore.*
import com.keylesspalace.tusky.components.notifications.NotificationHelper
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.db.AppDatabase
@ -132,6 +131,7 @@ class SendTootService : Service(), Injectable {
tootToSend.retries++
val contentType : String? = if(tootToSend.formattingSyntax.length == 0) null else tootToSend.formattingSyntax
val preview : Boolean? = if(tootToSend.preview) tootToSend.preview else null
val newStatus = NewStatus(
tootToSend.text,
@ -142,7 +142,8 @@ class SendTootService : Service(), Injectable {
tootToSend.mediaIds,
tootToSend.scheduledAt,
tootToSend.poll,
contentType
contentType,
preview
)
val sendCall = mastodonApi.createStatus(
@ -166,7 +167,9 @@ class SendTootService : Service(), Injectable {
saveTootHelper.deleteDraft(tootToSend.savedTootUid)
}
if (scheduled) {
if (tootToSend.preview) {
response.body()?.let(::StatusPreviewEvent)?.let(eventHub::dispatch)
} else if (scheduled) {
response.body()?.let(::StatusScheduledEvent)?.let(eventHub::dispatch)
} else {
response.body()?.let(::StatusComposedEvent)?.let(eventHub::dispatch)
@ -328,6 +331,7 @@ data class TootToSend(
val replyingStatusAuthorUsername: String?,
val savedJsonUrls: List<String>?,
val formattingSyntax: String,
val preview: Boolean,
val accountId: Long,
val savedTootUid: Int,
val idempotencyKey: String,

View File

@ -0,0 +1,71 @@
package com.keylesspalace.tusky.view
import android.view.*
import android.content.*
import android.util.*
import android.widget.*
import android.app.*
import android.text.*
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
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.ViewDataUtils
import com.keylesspalace.tusky.util.StatusDisplayOptions
import com.keylesspalace.tusky.R
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("animateGifAvatars", false),
mediaPreviewEnabled = true,
useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false),
showBotOverlay = false,
useBlurhash = preferences.getBoolean("useBlurhash", true),
cardViewMode = CardViewMode.NONE,
confirmReblogs = preferences.getBoolean("confirmReblogs", true)
)
viewHolder = StatusViewHolder(this)
}
fun setupWithStatus(status: Status) {
val concrete = ViewDataUtils.statusToViewData(status, 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 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) { }
}
}

View File

@ -301,10 +301,29 @@
android:layout_height="300dp"
android:background="?attr/colorSurface"
android:elevation="12dp"
android:layout_marginBottom="56dp"
android:paddingBottom="60dp"
app:behavior_hideable="true"
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>
<RelativeLayout
android:layout_width="match_parent"
@ -429,16 +448,29 @@
android:layout_height="wrap_content"
android:textColor="?android:textColorTertiary"
android:textSize="?attr/status_text_medium"
android:layout_toLeftOf="@+id/composeTootButton"
android:layout_toLeftOf="@+id/composePreviewButton"
android:layout_centerVertical="true"
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_eye_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"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"/>