From b1eb4129705b9c1f90e2439d73cc0485ea6e4faa Mon Sep 17 00:00:00 2001 From: kyori19 Date: Sat, 29 Jul 2023 06:17:54 +0900 Subject: [PATCH] [append-text] Implement append-text feature --- .../51.json | 22 ++- .../components/compose/ComposeActivity.kt | 59 +++++- .../compose/view/ComposeAppendTextView.kt | 28 +++ .../keylesspalace/tusky/db/AccountEntity.kt | 7 +- app/src/main/res/layout/activity_compose.xml | 174 +++++++++++------- .../res/layout/view_compose_append_text.xml | 37 ++++ app/src/main/res/values/yuito.xml | 5 + 7 files changed, 254 insertions(+), 78 deletions(-) create mode 100644 app/src/main/java/com/keylesspalace/tusky/components/compose/view/ComposeAppendTextView.kt create mode 100644 app/src/main/res/layout/view_compose_append_text.xml create mode 100644 app/src/main/res/values/yuito.xml diff --git a/app/schemas/com.keylesspalace.tusky.db.AppDatabase/51.json b/app/schemas/com.keylesspalace.tusky.db.AppDatabase/51.json index 32c5eeee9..48a58392b 100644 --- a/app/schemas/com.keylesspalace.tusky.db.AppDatabase/51.json +++ b/app/schemas/com.keylesspalace.tusky.db.AppDatabase/51.json @@ -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')" ] } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt index 2f77423b9..fe68a88df 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt @@ -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() diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ComposeAppendTextView.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ComposeAppendTextView.kt new file mode 100644 index 000000000..9b10c3e69 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ComposeAppendTextView.kt @@ -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) + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt b/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt index cdde765f7..9baa1a8ee 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt @@ -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 diff --git a/app/src/main/res/layout/activity_compose.xml b/app/src/main/res/layout/activity_compose.xml index 367d88baa..9268ce621 100644 --- a/app/src/main/res/layout/activity_compose.xml +++ b/app/src/main/res/layout/activity_compose.xml @@ -278,6 +278,20 @@ app:behavior_peekHeight="0dp" app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior" /> + + - - - - - - - - - - - - - + android:layout_height="match_parent" + android:layout_weight="1"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/yuito.xml b/app/src/main/res/values/yuito.xml new file mode 100644 index 000000000..b2af0c37f --- /dev/null +++ b/app/src/main/res/values/yuito.xml @@ -0,0 +1,5 @@ + + + Append Text + Append fixed text at the end of every posts +