[append-text] Implement append-text feature

This commit is contained in:
kyori19 2023-07-29 06:17:54 +09:00
parent e63102712b
commit b1eb412970
No known key found for this signature in database
GPG Key ID: F7BDE7DD42BF366A
7 changed files with 254 additions and 78 deletions

View File

@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 51,
"identityHash": "446158bf571fbd08787628bb829fa3c0",
"identityHash": "a1f530a7165bdd0e44932d9bb12ea4d3",
"entities": [
{
"tableName": "DraftEntity",
@ -104,7 +104,7 @@
},
{
"tableName": "AccountEntity",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `domain` TEXT NOT NULL, `accessToken` TEXT NOT NULL, `clientId` TEXT, `clientSecret` TEXT, `isActive` INTEGER NOT NULL, `accountId` TEXT NOT NULL, `username` TEXT NOT NULL, `displayName` TEXT NOT NULL, `profilePictureUrl` TEXT NOT NULL, `notificationsEnabled` INTEGER NOT NULL, `notificationsMentioned` INTEGER NOT NULL, `notificationsFollowed` INTEGER NOT NULL, `notificationsFollowRequested` INTEGER NOT NULL, `notificationsReblogged` INTEGER NOT NULL, `notificationsFavorited` INTEGER NOT NULL, `notificationsPolls` INTEGER NOT NULL, `notificationsSubscriptions` INTEGER NOT NULL, `notificationsSignUps` INTEGER NOT NULL, `notificationsUpdates` INTEGER NOT NULL, `notificationsReports` INTEGER NOT NULL, `notificationSound` INTEGER NOT NULL, `notificationVibration` INTEGER NOT NULL, `notificationLight` INTEGER NOT NULL, `defaultPostPrivacy` INTEGER NOT NULL, `defaultMediaSensitivity` INTEGER NOT NULL, `defaultPostLanguage` TEXT NOT NULL, `alwaysShowSensitiveMedia` INTEGER NOT NULL, `alwaysOpenSpoiler` INTEGER NOT NULL, `mediaPreviewEnabled` INTEGER NOT NULL, `lastNotificationId` TEXT NOT NULL, `notificationMarkerId` TEXT NOT NULL DEFAULT '0', `emojis` TEXT NOT NULL, `tabPreferences` TEXT NOT NULL, `notificationsFilter` TEXT NOT NULL, `oauthScopes` TEXT NOT NULL, `unifiedPushUrl` TEXT NOT NULL, `pushPubKey` TEXT NOT NULL, `pushPrivKey` TEXT NOT NULL, `pushAuth` TEXT NOT NULL, `pushServerKey` TEXT NOT NULL, `lastVisibleHomeTimelineStatusId` TEXT)",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `domain` TEXT NOT NULL, `accessToken` TEXT NOT NULL, `clientId` TEXT, `clientSecret` TEXT, `isActive` INTEGER NOT NULL, `accountId` TEXT NOT NULL, `username` TEXT NOT NULL, `displayName` TEXT NOT NULL, `profilePictureUrl` TEXT NOT NULL, `notificationsEnabled` INTEGER NOT NULL, `notificationsMentioned` INTEGER NOT NULL, `notificationsFollowed` INTEGER NOT NULL, `notificationsFollowRequested` INTEGER NOT NULL, `notificationsReblogged` INTEGER NOT NULL, `notificationsFavorited` INTEGER NOT NULL, `notificationsPolls` INTEGER NOT NULL, `notificationsSubscriptions` INTEGER NOT NULL, `notificationsSignUps` INTEGER NOT NULL, `notificationsUpdates` INTEGER NOT NULL, `notificationsReports` INTEGER NOT NULL, `notificationSound` INTEGER NOT NULL, `notificationVibration` INTEGER NOT NULL, `notificationLight` INTEGER NOT NULL, `defaultPostPrivacy` INTEGER NOT NULL, `defaultMediaSensitivity` INTEGER NOT NULL, `defaultPostLanguage` TEXT NOT NULL, `alwaysShowSensitiveMedia` INTEGER NOT NULL, `alwaysOpenSpoiler` INTEGER NOT NULL, `mediaPreviewEnabled` INTEGER NOT NULL, `lastNotificationId` TEXT NOT NULL, `notificationMarkerId` TEXT NOT NULL DEFAULT '0', `emojis` TEXT NOT NULL, `tabPreferences` TEXT NOT NULL, `notificationsFilter` TEXT NOT NULL, `oauthScopes` TEXT NOT NULL, `unifiedPushUrl` TEXT NOT NULL, `pushPubKey` TEXT NOT NULL, `pushPrivKey` TEXT NOT NULL, `pushAuth` TEXT NOT NULL, `pushServerKey` TEXT NOT NULL, `lastVisibleHomeTimelineStatusId` TEXT, `appendText` TEXT NOT NULL DEFAULT '', `appendTextEnabled` INTEGER NOT NULL DEFAULT FALSE)",
"fields": [
{
"fieldPath": "id",
@ -358,6 +358,20 @@
"columnName": "lastVisibleHomeTimelineStatusId",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "appendText",
"columnName": "appendText",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "appendTextEnabled",
"columnName": "appendTextEnabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "FALSE"
}
],
"primaryKey": {
@ -996,7 +1010,7 @@
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '446158bf571fbd08787628bb829fa3c0')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a1f530a7165bdd0e44932d9bb12ea4d3')"
]
}
}
}

View File

@ -31,6 +31,7 @@ import android.os.Build
import android.os.Bundle
import android.os.Parcelable
import android.provider.MediaStore
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.style.URLSpan
import android.util.Log
@ -141,6 +142,7 @@ class ComposeActivity :
private lateinit var addMediaBehavior: BottomSheetBehavior<*>
private lateinit var emojiBehavior: BottomSheetBehavior<*>
private lateinit var scheduleBehavior: BottomSheetBehavior<*>
private lateinit var appendTextBehavior: BottomSheetBehavior<*>
/** The account that is being used to compose the status */
private lateinit var activeAccount: AccountEntity
@ -281,6 +283,7 @@ class ComposeActivity :
setupComposeField(preferences, viewModel.startingText)
setupContentWarningField(composeOptions?.contentWarning)
setupPollView()
setupAppendTextField()
applyShareIntent(intent, savedInstanceState)
/* Finally, overwrite state with data from saved instance state. */
@ -382,6 +385,13 @@ class ComposeActivity :
binding.composeContentWarningField.doOnTextChanged { _, _, _, _ -> updateVisibleCharactersLeft() }
}
private fun setupAppendTextField() {
binding.composeAppendTextView.setup(activeAccount.appendTextEnabled, activeAccount.appendText)
binding.composeAppendTextView.onEnabledChange { _, enabled -> updateAppendTextEnabled(enabled) }
binding.composeAppendTextView.onTextChange { text -> updateAppendText(text.toString()) }
}
private fun setupComposeField(preferences: SharedPreferences, startingText: String?) {
binding.composeEditField.setOnReceiveContentListener(this)
@ -495,6 +505,7 @@ class ComposeActivity :
addMediaBehavior = BottomSheetBehavior.from(binding.addMediaBottomSheet)
scheduleBehavior = BottomSheetBehavior.from(binding.composeScheduleView)
emojiBehavior = BottomSheetBehavior.from(binding.emojiView)
appendTextBehavior = BottomSheetBehavior.from(binding.composeAppendTextView)
enableButton(binding.composeEmojiButton, clickable = false, colorActive = false)
@ -508,6 +519,7 @@ class ComposeActivity :
binding.composeScheduleButton.setOnClickListener { onScheduleClick() }
binding.composeScheduleView.setResetOnClickListener { resetSchedule() }
binding.composeScheduleView.setListener(this)
binding.composeAppendTextButton.setOnClickListener { showAppendText() }
binding.atButton.setOnClickListener { atButtonClicked() }
binding.hashButton.setOnClickListener { hashButtonClicked() }
binding.descriptionMissingWarningButton.setOnClickListener {
@ -538,12 +550,14 @@ class ComposeActivity :
if (composeOptionsBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
addMediaBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
emojiBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
scheduleBehavior.state == BottomSheetBehavior.STATE_EXPANDED
scheduleBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
appendTextBehavior.state == BottomSheetBehavior.STATE_EXPANDED
) {
composeOptionsBehavior.state = BottomSheetBehavior.STATE_HIDDEN
addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN
appendTextBehavior.state = BottomSheetBehavior.STATE_HIDDEN
return
}
@ -763,6 +777,7 @@ class ComposeActivity :
composeOptionsBehavior.state = BottomSheetBehavior.STATE_EXPANDED
addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
appendTextBehavior.state = BottomSheetBehavior.STATE_HIDDEN
scheduleBehavior.setState(BottomSheetBehavior.STATE_HIDDEN)
} else {
composeOptionsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN)
@ -782,6 +797,7 @@ class ComposeActivity :
scheduleBehavior.state = BottomSheetBehavior.STATE_EXPANDED
composeOptionsBehavior.state = BottomSheetBehavior.STATE_HIDDEN
addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN
appendTextBehavior.state = BottomSheetBehavior.STATE_HIDDEN
emojiBehavior.setState(BottomSheetBehavior.STATE_HIDDEN)
} else {
scheduleBehavior.setState(BottomSheetBehavior.STATE_HIDDEN)
@ -798,6 +814,7 @@ class ComposeActivity :
emojiBehavior.state = BottomSheetBehavior.STATE_EXPANDED
composeOptionsBehavior.state = BottomSheetBehavior.STATE_HIDDEN
addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN
appendTextBehavior.state = BottomSheetBehavior.STATE_HIDDEN
scheduleBehavior.setState(BottomSheetBehavior.STATE_HIDDEN)
} else {
emojiBehavior.setState(BottomSheetBehavior.STATE_HIDDEN)
@ -806,11 +823,38 @@ class ComposeActivity :
}
}
private fun showAppendText() {
if (appendTextBehavior.state == BottomSheetBehavior.STATE_HIDDEN || appendTextBehavior.state == BottomSheetBehavior.STATE_COLLAPSED) {
appendTextBehavior.state = BottomSheetBehavior.STATE_EXPANDED
composeOptionsBehavior.state = BottomSheetBehavior.STATE_HIDDEN
addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
scheduleBehavior.setState(BottomSheetBehavior.STATE_HIDDEN)
} else {
appendTextBehavior.setState(BottomSheetBehavior.STATE_HIDDEN)
}
}
private fun updateAppendTextEnabled(enabled: Boolean) {
activeAccount.appendTextEnabled = enabled
updateVisibleCharactersLeft()
accountManager.saveAccount(activeAccount)
}
private fun updateAppendText(text: String) {
activeAccount.appendText = text
if (activeAccount.appendTextEnabled) {
updateVisibleCharactersLeft()
}
accountManager.saveAccount(activeAccount)
}
private fun openPickDialog() {
if (addMediaBehavior.state == BottomSheetBehavior.STATE_HIDDEN || addMediaBehavior.state == BottomSheetBehavior.STATE_COLLAPSED) {
addMediaBehavior.state = BottomSheetBehavior.STATE_EXPANDED
composeOptionsBehavior.state = BottomSheetBehavior.STATE_HIDDEN
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
appendTextBehavior.state = BottomSheetBehavior.STATE_HIDDEN
scheduleBehavior.setState(BottomSheetBehavior.STATE_HIDDEN)
} else {
addMediaBehavior.setState(BottomSheetBehavior.STATE_HIDDEN)
@ -893,8 +937,13 @@ class ComposeActivity :
@VisibleForTesting
fun calculateTextLength(): Int {
val text = SpannableStringBuilder(binding.composeEditField.text)
if (activeAccount.appendTextEnabled && activeAccount.appendText.isNotEmpty()) {
text.append(" ${activeAccount.appendText}")
}
return statusLength(
binding.composeEditField.text,
text,
binding.composeContentWarningField.text,
charactersReservedPerUrl
)
@ -950,7 +999,11 @@ class ComposeActivity :
private fun sendStatus() {
enableButtons(false, viewModel.editing)
val contentText = binding.composeEditField.text.toString()
var contentText = binding.composeEditField.text.toString()
if (activeAccount.appendTextEnabled && activeAccount.appendText.isNotEmpty()) {
contentText += " ${activeAccount.appendText}"
}
var spoilerText = ""
if (viewModel.showContentWarning.value) {
spoilerText = binding.composeContentWarningField.text.toString()

View File

@ -0,0 +1,28 @@
package com.keylesspalace.tusky.components.compose.view
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.widget.doAfterTextChanged
import com.keylesspalace.tusky.databinding.ViewComposeAppendTextBinding
class ComposeAppendTextView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
private val binding = ViewComposeAppendTextBinding.inflate(
(context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater),
this,
)
val onEnabledChange = binding.switchAppendText::setOnCheckedChangeListener
val onTextChange = binding.editAppendText::doAfterTextChanged
fun setup(enabled: Boolean, appendText: String) {
binding.switchAppendText.isChecked = enabled
binding.editAppendText.setText(appendText)
}
}

View File

@ -100,7 +100,12 @@ data class AccountEntity(
* ID of the status at the top of the visible list in the home timeline when the
* user navigated away.
*/
var lastVisibleHomeTimelineStatusId: String? = null
var lastVisibleHomeTimelineStatusId: String? = null,
@ColumnInfo(defaultValue = "")
var appendText: String = "",
@ColumnInfo(defaultValue = "FALSE")
var appendTextEnabled: Boolean = false,
) {
val identifier: String

View File

@ -278,6 +278,20 @@
app:behavior_peekHeight="0dp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior" />
<com.keylesspalace.tusky.components.compose.view.ComposeAppendTextView
android:id="@+id/composeAppendTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorSurface"
android:elevation="12dp"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="16dp"
android:paddingBottom="52dp"
app:behavior_hideable="true"
app:behavior_peekHeight="0dp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior" />
<LinearLayout
android:id="@+id/composeBottomBar"
android:layout_width="match_parent"
@ -292,77 +306,97 @@
android:paddingEnd="8dp"
android:paddingBottom="4dp">
<ImageButton
android:id="@+id/composeAddMediaButton"
style="@style/TuskyImageButton"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginEnd="4dp"
android:contentDescription="@string/action_add_media"
android:padding="4dp"
app:srcCompat="@drawable/ic_attach_file_24dp"
app:tooltipText="@string/action_add_media" />
<ImageButton
android:id="@+id/composeToggleVisibilityButton"
style="@style/TuskyImageButton"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginEnd="4dp"
android:contentDescription="@string/action_toggle_visibility"
android:padding="4dp"
android:tint="?android:attr/textColorTertiary"
app:tooltipText="@string/action_toggle_visibility"
tools:src="@drawable/ic_public_24dp" />
<ImageButton
android:id="@+id/composeHideMediaButton"
style="@style/TuskyImageButton"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginEnd="4dp"
android:contentDescription="@string/action_hide_media"
android:padding="4dp"
app:tooltipText="@string/action_hide_media"
tools:src="@drawable/ic_eye_24dp" />
<ImageButton
android:id="@+id/composeContentWarningButton"
style="@style/TuskyImageButton"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginEnd="4dp"
android:contentDescription="@string/action_content_warning"
android:padding="4dp"
app:srcCompat="@drawable/ic_cw_24dp"
app:tooltipText="@string/action_content_warning" />
<ImageButton
android:id="@+id/composeEmojiButton"
style="@style/TuskyImageButton"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginEnd="4dp"
android:contentDescription="@string/action_emoji_keyboard"
android:padding="4dp"
app:srcCompat="@drawable/ic_emoji_24dp"
app:tooltipText="@string/action_emoji_keyboard" />
<ImageButton
android:id="@+id/composeScheduleButton"
style="@style/TuskyImageButton"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginEnd="4dp"
android:contentDescription="@string/action_schedule_post"
android:padding="4dp"
app:srcCompat="@drawable/ic_access_time"
app:tooltipText="@string/action_schedule_post" />
<Space
<HorizontalScrollView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
android:layout_height="match_parent"
android:layout_weight="1">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical">
<ImageButton
android:id="@+id/composeAddMediaButton"
style="@style/TuskyImageButton"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginEnd="4dp"
android:contentDescription="@string/action_add_media"
android:padding="4dp"
app:srcCompat="@drawable/ic_attach_file_24dp"
app:tooltipText="@string/action_add_media" />
<ImageButton
android:id="@+id/composeToggleVisibilityButton"
style="@style/TuskyImageButton"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginEnd="4dp"
android:contentDescription="@string/action_toggle_visibility"
android:padding="4dp"
android:tint="?android:attr/textColorTertiary"
app:tooltipText="@string/action_toggle_visibility"
tools:src="@drawable/ic_public_24dp" />
<ImageButton
android:id="@+id/composeHideMediaButton"
style="@style/TuskyImageButton"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginEnd="4dp"
android:contentDescription="@string/action_hide_media"
android:padding="4dp"
app:tooltipText="@string/action_hide_media"
tools:src="@drawable/ic_eye_24dp" />
<ImageButton
android:id="@+id/composeContentWarningButton"
style="@style/TuskyImageButton"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginEnd="4dp"
android:contentDescription="@string/action_content_warning"
android:padding="4dp"
app:srcCompat="@drawable/ic_cw_24dp"
app:tooltipText="@string/action_content_warning" />
<ImageButton
android:id="@+id/composeEmojiButton"
style="@style/TuskyImageButton"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginEnd="4dp"
android:contentDescription="@string/action_emoji_keyboard"
android:padding="4dp"
app:srcCompat="@drawable/ic_emoji_24dp"
app:tooltipText="@string/action_emoji_keyboard" />
<ImageButton
android:id="@+id/composeScheduleButton"
style="@style/TuskyImageButton"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginEnd="4dp"
android:contentDescription="@string/action_schedule_post"
android:padding="4dp"
app:srcCompat="@drawable/ic_access_time"
app:tooltipText="@string/action_schedule_post" />
<ImageButton
android:id="@+id/composeAppendTextButton"
style="@style/TuskyImageButton"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginEnd="4dp"
android:contentDescription="@string/action_append_text"
android:padding="4dp"
app:srcCompat="@drawable/ic_hashtag"
app:tooltipText="@string/action_append_text" />
</LinearLayout>
</HorizontalScrollView>
<TextView
android:id="@+id/composeCharactersLeftView"

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/switchAppendText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/desc_append_text"
android:textColor="?android:textColorTertiary"
android:textSize="?attr/status_text_medium"
app:layout_constraintBottom_toTopOf="@+id/appendTextInputLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/appendTextInputLayout"
style="@style/TuskyTextInput"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingBottom="8dp"
android:hint="@string/action_append_text"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/editAppendText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
</merge>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="action_append_text">Append Text</string>
<string name="desc_append_text">Append fixed text at the end of every posts</string>
</resources>