From ca196ffc73e2f5158448b50a0e4490e9ddfe4e40 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 12 Apr 2020 20:13:09 +0300 Subject: [PATCH] compose: implement BBcode and HTML sending --- .../23.json | 753 ++++++++++++++++++ .../tusky/SavedTootActivity.java | 2 +- .../components/compose/ComposeActivity.kt | 124 ++- .../components/compose/ComposeViewModel.kt | 9 +- .../keylesspalace/tusky/db/AccountEntity.kt | 3 +- .../keylesspalace/tusky/db/AppDatabase.java | 12 +- .../keylesspalace/tusky/db/TootEntity.java | 17 +- .../com/keylesspalace/tusky/di/AppModule.kt | 3 +- .../keylesspalace/tusky/di/NetworkModule.kt | 2 +- .../preference/AccountPreferencesFragment.kt | 36 + .../receiver/SendStatusBroadcastReceiver.kt | 2 +- .../tusky/service/SendTootService.kt | 9 +- .../tusky/util/SaveTootHelper.java | 4 +- app/src/main/res/drawable/ic_bbcode_24dp.xml | 18 + app/src/main/res/drawable/ic_html_24dp.xml | 18 + app/src/main/res/layout/activity_compose.xml | 2 +- app/src/main/res/values/husky.xml | 4 +- .../main/res/values/husky_donottranslate.xml | 13 + app/src/main/res/xml/account_preferences.xml | 8 + 19 files changed, 1003 insertions(+), 36 deletions(-) create mode 100644 app/schemas/com.keylesspalace.tusky.db.AppDatabase/23.json create mode 100644 app/src/main/res/drawable/ic_bbcode_24dp.xml create mode 100644 app/src/main/res/drawable/ic_html_24dp.xml create mode 100644 app/src/main/res/values/husky_donottranslate.xml diff --git a/app/schemas/com.keylesspalace.tusky.db.AppDatabase/23.json b/app/schemas/com.keylesspalace.tusky.db.AppDatabase/23.json new file mode 100644 index 00000000..771f1775 --- /dev/null +++ b/app/schemas/com.keylesspalace.tusky.db.AppDatabase/23.json @@ -0,0 +1,753 @@ +{ + "formatVersion": 1, + "database": { + "version": 23, + "identityHash": "0cb482507cdcf5628ae028242c3b74bb", + "entities": [ + { + "tableName": "TootEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `text` TEXT, `urls` TEXT, `descriptions` TEXT, `contentWarning` TEXT, `inReplyToId` TEXT, `inReplyToText` TEXT, `inReplyToUsername` TEXT, `visibility` INTEGER, `poll` TEXT, `formattingSyntax` TEXT NOT NULL, `markdownMode` INTEGER)", + "fields": [ + { + "fieldPath": "uid", + "columnName": "uid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "urls", + "columnName": "urls", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "descriptions", + "columnName": "descriptions", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "contentWarning", + "columnName": "contentWarning", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inReplyToId", + "columnName": "inReplyToId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inReplyToText", + "columnName": "inReplyToText", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inReplyToUsername", + "columnName": "inReplyToUsername", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "visibility", + "columnName": "visibility", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "poll", + "columnName": "poll", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "formattingSyntax", + "columnName": "formattingSyntax", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "markdownMode", + "columnName": "markdownMode", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "uid" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "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, `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, `notificationsReblogged` INTEGER NOT NULL, `notificationsFavorited` INTEGER NOT NULL, `notificationsPolls` INTEGER NOT NULL, `notificationsEmojiReactions` INTEGER NOT NULL, `notificationSound` INTEGER NOT NULL, `notificationVibration` INTEGER NOT NULL, `notificationLight` INTEGER NOT NULL, `defaultPostPrivacy` INTEGER NOT NULL, `defaultMediaSensitivity` INTEGER NOT NULL, `alwaysShowSensitiveMedia` INTEGER NOT NULL, `alwaysOpenSpoiler` INTEGER NOT NULL, `mediaPreviewEnabled` INTEGER NOT NULL, `lastNotificationId` TEXT NOT NULL, `activeNotifications` TEXT NOT NULL, `emojis` TEXT NOT NULL, `tabPreferences` TEXT NOT NULL, `notificationsFilter` TEXT NOT NULL, `defaultFormattingSyntax` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "domain", + "columnName": "domain", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accessToken", + "columnName": "accessToken", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isActive", + "columnName": "isActive", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accountId", + "columnName": "accountId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "username", + "columnName": "username", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "displayName", + "columnName": "displayName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "profilePictureUrl", + "columnName": "profilePictureUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "notificationsEnabled", + "columnName": "notificationsEnabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsMentioned", + "columnName": "notificationsMentioned", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsFollowed", + "columnName": "notificationsFollowed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsReblogged", + "columnName": "notificationsReblogged", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsFavorited", + "columnName": "notificationsFavorited", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsPolls", + "columnName": "notificationsPolls", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsEmojiReactions", + "columnName": "notificationsEmojiReactions", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationSound", + "columnName": "notificationSound", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationVibration", + "columnName": "notificationVibration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationLight", + "columnName": "notificationLight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "defaultPostPrivacy", + "columnName": "defaultPostPrivacy", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "defaultMediaSensitivity", + "columnName": "defaultMediaSensitivity", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "alwaysShowSensitiveMedia", + "columnName": "alwaysShowSensitiveMedia", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "alwaysOpenSpoiler", + "columnName": "alwaysOpenSpoiler", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "mediaPreviewEnabled", + "columnName": "mediaPreviewEnabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastNotificationId", + "columnName": "lastNotificationId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "activeNotifications", + "columnName": "activeNotifications", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "emojis", + "columnName": "emojis", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "tabPreferences", + "columnName": "tabPreferences", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "notificationsFilter", + "columnName": "notificationsFilter", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "defaultFormattingSyntax", + "columnName": "defaultFormattingSyntax", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_AccountEntity_domain_accountId", + "unique": true, + "columnNames": [ + "domain", + "accountId" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_AccountEntity_domain_accountId` ON `${TABLE_NAME}` (`domain`, `accountId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "InstanceEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`instance` TEXT NOT NULL, `emojiList` TEXT, `maximumTootCharacters` INTEGER, `maxPollOptions` INTEGER, `maxPollOptionLength` INTEGER, `version` TEXT, PRIMARY KEY(`instance`))", + "fields": [ + { + "fieldPath": "instance", + "columnName": "instance", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "emojiList", + "columnName": "emojiList", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "maximumTootCharacters", + "columnName": "maximumTootCharacters", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "maxPollOptions", + "columnName": "maxPollOptions", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "maxPollOptionLength", + "columnName": "maxPollOptionLength", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "instance" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimelineStatusEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serverId` TEXT NOT NULL, `url` TEXT, `timelineUserId` INTEGER NOT NULL, `authorServerId` TEXT, `inReplyToId` TEXT, `inReplyToAccountId` TEXT, `content` TEXT, `createdAt` INTEGER NOT NULL, `emojis` TEXT, `reblogsCount` INTEGER NOT NULL, `favouritesCount` INTEGER NOT NULL, `reblogged` INTEGER NOT NULL, `bookmarked` INTEGER NOT NULL, `favourited` INTEGER NOT NULL, `sensitive` INTEGER NOT NULL, `spoilerText` TEXT, `visibility` INTEGER, `attachments` TEXT, `mentions` TEXT, `application` TEXT, `reblogServerId` TEXT, `reblogAccountId` TEXT, `poll` TEXT, PRIMARY KEY(`serverId`, `timelineUserId`), FOREIGN KEY(`authorServerId`, `timelineUserId`) REFERENCES `TimelineAccountEntity`(`serverId`, `timelineUserId`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "serverId", + "columnName": "serverId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "timelineUserId", + "columnName": "timelineUserId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "authorServerId", + "columnName": "authorServerId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inReplyToId", + "columnName": "inReplyToId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inReplyToAccountId", + "columnName": "inReplyToAccountId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "emojis", + "columnName": "emojis", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "reblogsCount", + "columnName": "reblogsCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "favouritesCount", + "columnName": "favouritesCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "reblogged", + "columnName": "reblogged", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "bookmarked", + "columnName": "bookmarked", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "favourited", + "columnName": "favourited", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sensitive", + "columnName": "sensitive", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "spoilerText", + "columnName": "spoilerText", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "visibility", + "columnName": "visibility", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "mentions", + "columnName": "mentions", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "application", + "columnName": "application", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "reblogServerId", + "columnName": "reblogServerId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "reblogAccountId", + "columnName": "reblogAccountId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "poll", + "columnName": "poll", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "serverId", + "timelineUserId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_TimelineStatusEntity_authorServerId_timelineUserId", + "unique": false, + "columnNames": [ + "authorServerId", + "timelineUserId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_TimelineStatusEntity_authorServerId_timelineUserId` ON `${TABLE_NAME}` (`authorServerId`, `timelineUserId`)" + } + ], + "foreignKeys": [ + { + "table": "TimelineAccountEntity", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "authorServerId", + "timelineUserId" + ], + "referencedColumns": [ + "serverId", + "timelineUserId" + ] + } + ] + }, + { + "tableName": "TimelineAccountEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serverId` TEXT NOT NULL, `timelineUserId` INTEGER NOT NULL, `localUsername` TEXT NOT NULL, `username` TEXT NOT NULL, `displayName` TEXT NOT NULL, `url` TEXT NOT NULL, `avatar` TEXT NOT NULL, `emojis` TEXT NOT NULL, `bot` INTEGER NOT NULL, PRIMARY KEY(`serverId`, `timelineUserId`))", + "fields": [ + { + "fieldPath": "serverId", + "columnName": "serverId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timelineUserId", + "columnName": "timelineUserId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "localUsername", + "columnName": "localUsername", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "username", + "columnName": "username", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "displayName", + "columnName": "displayName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatar", + "columnName": "avatar", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "emojis", + "columnName": "emojis", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "bot", + "columnName": "bot", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "serverId", + "timelineUserId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ConversationEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`accountId` INTEGER NOT NULL, `id` TEXT NOT NULL, `accounts` TEXT NOT NULL, `unread` INTEGER NOT NULL, `s_id` TEXT NOT NULL, `s_url` TEXT, `s_inReplyToId` TEXT, `s_inReplyToAccountId` TEXT, `s_account` TEXT NOT NULL, `s_content` TEXT NOT NULL, `s_createdAt` INTEGER NOT NULL, `s_emojis` TEXT NOT NULL, `s_favouritesCount` INTEGER NOT NULL, `s_favourited` INTEGER NOT NULL, `s_bookmarked` INTEGER NOT NULL, `s_sensitive` INTEGER NOT NULL, `s_spoilerText` TEXT NOT NULL, `s_attachments` TEXT NOT NULL, `s_mentions` TEXT NOT NULL, `s_showingHiddenContent` INTEGER NOT NULL, `s_expanded` INTEGER NOT NULL, `s_collapsible` INTEGER NOT NULL, `s_collapsed` INTEGER NOT NULL, `s_poll` TEXT, PRIMARY KEY(`id`, `accountId`))", + "fields": [ + { + "fieldPath": "accountId", + "columnName": "accountId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accounts", + "columnName": "accounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.id", + "columnName": "s_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.url", + "columnName": "s_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastStatus.inReplyToId", + "columnName": "s_inReplyToId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastStatus.inReplyToAccountId", + "columnName": "s_inReplyToAccountId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastStatus.account", + "columnName": "s_account", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.content", + "columnName": "s_content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.createdAt", + "columnName": "s_createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.emojis", + "columnName": "s_emojis", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.favouritesCount", + "columnName": "s_favouritesCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.favourited", + "columnName": "s_favourited", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.bookmarked", + "columnName": "s_bookmarked", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.sensitive", + "columnName": "s_sensitive", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.spoilerText", + "columnName": "s_spoilerText", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.attachments", + "columnName": "s_attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.mentions", + "columnName": "s_mentions", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.showingHiddenContent", + "columnName": "s_showingHiddenContent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.expanded", + "columnName": "s_expanded", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.collapsible", + "columnName": "s_collapsible", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.collapsed", + "columnName": "s_collapsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.poll", + "columnName": "s_poll", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id", + "accountId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "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, '0cb482507cdcf5628ae028242c3b74bb')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/SavedTootActivity.java b/app/src/main/java/com/keylesspalace/tusky/SavedTootActivity.java index 08bfb419..1119a819 100644 --- a/app/src/main/java/com/keylesspalace/tusky/SavedTootActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/SavedTootActivity.java @@ -179,7 +179,7 @@ public final class SavedTootActivity extends BaseActivity implements SavedTootAd /*scheduledAt*/null, /*sensitive*/null, /*poll*/null, - item.getMarkdownMode() + item.getFormattingSyntax() ); Intent intent = ComposeActivity.startIntent(this, composeOptions); startActivity(intent); 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 390d181e..492731d3 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 @@ -113,6 +113,7 @@ class ComposeActivity : BaseActivity(), private var composeOptions: ComposeOptions? = null private val viewModel: ComposeViewModel by viewModels { viewModelFactory } + private var suggestFormattingSyntax: String = "text/markdown" private var mediaCount = 0 @@ -148,6 +149,7 @@ class ComposeActivity : BaseActivity(), setupButtons() photoUploadUri = savedInstanceState?.getParcelable(PHOTO_UPLOAD_URI_KEY) + viewModel.formattingSyntax = activeAccount.defaultFormattingSyntax /* If the composer is started up as a reply to another post, override the "starting" state * based on what the intent from the reply request passes. */ @@ -159,9 +161,14 @@ class ComposeActivity : BaseActivity(), if (!tootText.isNullOrEmpty()) { composeEditField.setText(tootText) } - enableMarkdownMode(composeOptions?.markdownMode ?: false); } - + + if(viewModel.formattingSyntax.length == 0) { + suggestFormattingSyntax = "text/markdown" + } else { + suggestFormattingSyntax = viewModel.formattingSyntax + } + if (!TextUtils.isEmpty(composeOptions?.scheduledAt)) { composeScheduleView.setDateTime(composeOptions?.scheduledAt) } @@ -326,6 +333,8 @@ class ComposeActivity : BaseActivity(), enableButton(composeAddMediaButton, true, true) enablePollButton(viewModel.poll != null) } + + private var supportedFormattingSyntax = arrayListOf() private fun subscribeToUpdates(mediaAdapter: MediaPreviewAdapter) { withLifecycleContext { @@ -335,7 +344,32 @@ class ComposeActivity : BaseActivity(), composeScheduleButton.visible(instanceData.supportsScheduled) } viewModel.instanceMetadata.observe { instanceData -> - composeMarkdownButton.visible(instanceData.supportsMarkdown) + if(instanceData.supportsMarkdown) { + supportedFormattingSyntax.add("text/markdown") + } + + if(instanceData.supportsBBcode) { + supportedFormattingSyntax.add("text/bbcode") + } + + if(instanceData.supportsHTML) { + supportedFormattingSyntax.add("text/html") + } + + if(supportedFormattingSyntax.size != 0) { + composeFormattingSyntax.visible(true) + + val supportsPrefferedSyntax = supportedFormattingSyntax.contains(viewModel.formattingSyntax) + + if(!supportsPrefferedSyntax) { + viewModel.formattingSyntax = "" + + setIconForSyntax(supportedFormattingSyntax[0], false) + } else { + setIconForSyntax(viewModel.formattingSyntax, true) + } + } + if(instanceData.software.equals("pleroma")) { reenableAttachments() } @@ -406,7 +440,8 @@ class ComposeActivity : BaseActivity(), composeHideMediaButton.setOnClickListener { toggleHideMedia() } composeScheduleButton.setOnClickListener { onScheduleClick() } composeScheduleView.setResetOnClickListener { resetSchedule() } - composeMarkdownButton.setOnClickListener { toggleMarkdownMode() } + composeFormattingSyntax.setOnClickListener { toggleFormattingMode() } + composeFormattingSyntax.setOnLongClickListener { selectFormattingSyntax() } atButton.setOnClickListener { atButtonClicked() } hashButton.setOnClickListener { hashButtonClicked() } codeButton.setOnClickListener { codeButtonClicked() } @@ -474,19 +509,80 @@ class ComposeActivity : BaseActivity(), composeEditField.setSelection(start + text.length) } - private fun enableMarkdownMode(enable: Boolean) { - viewModel.markdownMode = enable + private fun enableFormattingSyntaxButton(syntax: String, enable: Boolean) { + val stringId = when(syntax) { + "text/html" -> R.string.action_html + "text/bbcode" -> R.string.action_bbcode + else -> R.string.action_markdown + } + + val actionStringId = if(enable) R.string.action_disable_formatting_syntax else R.string.action_enable_formatting_syntax + val tooltipText = getString(actionStringId).format(stringId) + + composeFormattingSyntax.tooltipText = tooltipText + composeFormattingSyntax.contentDescription = tooltipText - enableMarkdownWYSIWYGButtons(viewModel.markdownMode) + @ColorInt val color = ThemeUtils.getColor(this, if(enable) R.attr.colorPrimary else android.R.attr.textColorTertiary); + composeFormattingSyntax.drawable.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN); + } + + private fun setIconForSyntax(syntax: String, enable: Boolean) { + val drawableId = when(syntax) { + "text/html" -> R.drawable.ic_html_24dp + "text/bbcode" -> R.drawable.ic_bbcode_24dp + else -> R.drawable.ic_markdown + } - TransitionManager.beginDelayedTransition(composeMarkdownButton.parent as ViewGroup); + suggestFormattingSyntax = if(drawableId == R.drawable.ic_markdown) "text/markdown" else syntax + composeFormattingSyntax.setImageResource(drawableId) + enableFormattingSyntaxButton(syntax, enable) + } - @ColorInt val color = ThemeUtils.getColor(this, if(viewModel.markdownMode) R.attr.colorPrimary else android.R.attr.textColorTertiary); - composeMarkdownButton.drawable.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN); + private fun toggleFormattingMode() { + if(viewModel.formattingSyntax.equals(suggestFormattingSyntax)) { + viewModel.formattingSyntax = "" + enableFormattingSyntaxButton(suggestFormattingSyntax, false) + } else { + viewModel.formattingSyntax = suggestFormattingSyntax + enableFormattingSyntaxButton(suggestFormattingSyntax, true) + } } - private fun toggleMarkdownMode() { - enableMarkdownMode(!viewModel.markdownMode) + private fun selectFormattingSyntax() : Boolean { + val menu = PopupMenu(this, composeFormattingSyntax) + val plaintextId = 0 + val markdownId = 1 + val bbcodeId = 2 + val htmlId = 3 + menu.menu.add(0, plaintextId, 0, R.string.action_plaintext) + if(viewModel.instanceMetadata.value?.supportsMarkdown ?: false) + menu.menu.add(0, markdownId, 0, R.string.action_markdown) + + if(viewModel.instanceMetadata.value?.supportsBBcode ?: false) + menu.menu.add(0, bbcodeId, 0, R.string.action_bbcode) + + if(viewModel.instanceMetadata.value?.supportsHTML ?: false) + menu.menu.add(0, htmlId, 0, R.string.action_html) + + menu.setOnMenuItemClickListener { menuItem -> + val choose = when (menuItem.itemId) { + markdownId -> "text/markdown" + bbcodeId -> "text/bbcode" + htmlId -> "text/html" + else -> "" + } + if(choose.length == 0) { + // leave previous + setIconForSyntax(viewModel.formattingSyntax, false) + } else { + setIconForSyntax(choose, true) + } + viewModel.formattingSyntax = choose + true + } + menu.show() + + return true } private fun enableMarkdownWYSIWYGButtons(visible: Boolean) { @@ -622,7 +718,7 @@ class ComposeActivity : BaseActivity(), composeEmojiButton.isClickable = enable composeHideMediaButton.isClickable = enable composeScheduleButton.isClickable = enable - composeMarkdownButton.isClickable = enable + composeFormattingSyntax.isClickable = enable composeTootButton.isEnabled = enable } @@ -1118,7 +1214,7 @@ class ComposeActivity : BaseActivity(), var scheduledAt: String? = null, var sensitive: Boolean? = null, var poll: NewPoll? = null, - var markdownMode: Boolean? = null + var formattingSyntax: String? = null ) : Parcelable companion object { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt index 63d04ef1..166b6499 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt @@ -63,7 +63,7 @@ class ComposeViewModel private var startingVisibility: Status.Visibility = Status.Visibility.UNKNOWN private val instance: MutableLiveData = MutableLiveData(null) private val nodeinfo: MutableLiveData = MutableLiveData(null) - public var markdownMode: Boolean = false + public var formattingSyntax: String = "" public var hasNoAttachmentLimits = false val instanceParams: LiveData = instance.map { instance -> @@ -280,7 +280,7 @@ class ComposeViewModel replyingStatusAuthor, statusVisibility.value!!, poll.value, - markdownMode + formattingSyntax ) } @@ -318,7 +318,7 @@ class ComposeViewModel poll = poll.value, replyingStatusContent = null, replyingStatusAuthorUsername = null, - markdownMode = markdownMode, + formattingSyntax = formattingSyntax, savedJsonUrls = null, accountId = accountManager.activeAccount!!.id, savedTootUid = 0, @@ -487,6 +487,9 @@ class ComposeViewModel } replyingStatusContent = composeOptions?.replyingStatusContent replyingStatusAuthor = composeOptions?.replyingStatusAuthor + + if(composeOptions?.formattingSyntax != null) + formattingSyntax = composeOptions?.formattingSyntax ?: accountManager.activeAccount!!.defaultFormattingSyntax } fun updatePoll(newPoll: NewPoll) { 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 c0f906ed..de076505 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt @@ -55,7 +55,8 @@ data class AccountEntity(@field:PrimaryKey(autoGenerate = true) var id: Long, var activeNotifications: String = "[]", var emojis: List = emptyList(), var tabPreferences: List = defaultTabs(), - var notificationsFilter: String = "[]") { + var notificationsFilter: String = "[]", + var defaultFormattingSyntax: String = "") { val identifier: String get() = "$domain:$accountId" diff --git a/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java b/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java index e38b77f7..aef669e0 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java +++ b/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java @@ -30,7 +30,7 @@ import androidx.annotation.NonNull; @Database(entities = {TootEntity.class, AccountEntity.class, InstanceEntity.class, TimelineStatusEntity.class, TimelineAccountEntity.class, ConversationEntity.class - }, version = 22) + }, version = 23) public abstract class AppDatabase extends RoomDatabase { public abstract TootDao tootDao(); @@ -333,4 +333,14 @@ public abstract class AppDatabase extends RoomDatabase { database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `notificationsEmojiReactions` INTEGER NOT NULL DEFAULT 1"); } }; + + public static final Migration MIGRATION_22_23 = new Migration(22, 23) { + @Override + public void migrate(@NonNull SupportSQLiteDatabase database) { + // leave markdownMode unused, we don't need it anymore but don't recreate table + // database.execSQL("ALTER TABLE `TootEntity` DROP COLUMN `markdownMode`"); + database.execSQL("ALTER TABLE `TootEntity` ADD COLUMN `formattingSyntax` TEXT NOT NULL DEFAULT ''"); + database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `defaultFormattingSyntax` TEXT NOT NULL DEFAULT ''"); + } + }; } diff --git a/app/src/main/java/com/keylesspalace/tusky/db/TootEntity.java b/app/src/main/java/com/keylesspalace/tusky/db/TootEntity.java index 678b92e4..d40bcc75 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/TootEntity.java +++ b/app/src/main/java/com/keylesspalace/tusky/db/TootEntity.java @@ -19,7 +19,7 @@ import com.google.gson.Gson; import com.keylesspalace.tusky.entity.NewPoll; import com.keylesspalace.tusky.entity.Status; -import androidx.annotation.Nullable; +import androidx.annotation.*; import androidx.room.ColumnInfo; import androidx.room.Entity; import androidx.room.PrimaryKey; @@ -66,13 +66,18 @@ public class TootEntity { @ColumnInfo(name = "poll") private final NewPoll poll; + @NonNull + @ColumnInfo(name = "formattingSyntax") + private final String formattingSyntax; + + /* DEPRECATED */ @Nullable @ColumnInfo(name = "markdownMode") - private final Boolean markdownMode; + public Boolean markdownMode = false; public TootEntity(int uid, String text, String urls, String descriptions, String contentWarning, String inReplyToId, @Nullable String inReplyToText, @Nullable String inReplyToUsername, - Status.Visibility visibility, @Nullable NewPoll poll, @Nullable Boolean markdownMode) { + Status.Visibility visibility, @Nullable NewPoll poll, String formattingSyntax) { this.uid = uid; this.text = text; this.urls = urls; @@ -83,7 +88,7 @@ public class TootEntity { this.inReplyToUsername = inReplyToUsername; this.visibility = visibility; this.poll = poll; - this.markdownMode = markdownMode; + this.formattingSyntax = formattingSyntax; } public String getText() { @@ -129,6 +134,10 @@ public class TootEntity { return poll; } + public String getFormattingSyntax() { + return formattingSyntax; + } + @Nullable public Boolean getMarkdownMode() { return markdownMode; diff --git a/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt index f4d68e37..fb1cc9fa 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt @@ -79,7 +79,8 @@ class AppModule { AppDatabase.MIGRATION_11_12, AppDatabase.MIGRATION_12_13, AppDatabase.MIGRATION_10_13, AppDatabase.MIGRATION_13_14, AppDatabase.MIGRATION_14_15, AppDatabase.MIGRATION_15_16, AppDatabase.MIGRATION_16_17, AppDatabase.MIGRATION_17_18, AppDatabase.MIGRATION_18_19, - AppDatabase.MIGRATION_19_20, AppDatabase.MIGRATION_20_21, AppDatabase.MIGRATION_21_22) + AppDatabase.MIGRATION_19_20, AppDatabase.MIGRATION_20_21, AppDatabase.MIGRATION_21_22, + AppDatabase.MIGRATION_22_23) .build() } diff --git a/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt index 127b3fc0..23946163 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt @@ -59,7 +59,7 @@ class NetworkModule { .apply { addInterceptor(InstanceSwitchAuthInterceptor(accountManager)) if (BuildConfig.DEBUG) { - addInterceptor(HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BASIC }) + addInterceptor(HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.HEADERS }) } } .build() diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/preference/AccountPreferencesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/preference/AccountPreferencesFragment.kt index c155c995..c87d146f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/preference/AccountPreferencesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/preference/AccountPreferencesFragment.kt @@ -65,6 +65,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), private lateinit var defaultPostPrivacyPreference: ListPreference private lateinit var defaultMediaSensitivityPreference: SwitchPreferenceCompat + private lateinit var defaultFormattingSyntaxPreference: ListPreference private lateinit var alwaysShowSensitiveMediaPreference: SwitchPreferenceCompat private lateinit var alwaysOpenSpoilerPreference: SwitchPreferenceCompat private lateinit var mediaPreviewEnabledPreference: SwitchPreferenceCompat @@ -85,6 +86,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), mutedDomainsPreference = requirePreference("mutedDomainsPreference") defaultPostPrivacyPreference = requirePreference("defaultPostPrivacy") as ListPreference defaultMediaSensitivityPreference = requirePreference("defaultMediaSensitivity") as SwitchPreferenceCompat + defaultFormattingSyntaxPreference = requirePreference("defaultFormattingSyntax") as ListPreference mediaPreviewEnabledPreference = requirePreference("mediaPreviewEnabled") as SwitchPreferenceCompat alwaysShowSensitiveMediaPreference = requirePreference("alwaysShowSensitiveMedia") as SwitchPreferenceCompat alwaysOpenSpoilerPreference = requirePreference("alwaysOpenSpoiler") as SwitchPreferenceCompat @@ -110,6 +112,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), defaultPostPrivacyPreference.onPreferenceChangeListener = this defaultMediaSensitivityPreference.onPreferenceChangeListener = this + defaultFormattingSyntaxPreference.onPreferenceChangeListener = this mediaPreviewEnabledPreference.onPreferenceChangeListener = this alwaysShowSensitiveMediaPreference.onPreferenceChangeListener = this alwaysOpenSpoilerPreference.onPreferenceChangeListener = this @@ -122,6 +125,14 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), defaultPostPrivacyPreference.value = it.defaultPostPrivacy.serverString() defaultPostPrivacyPreference.icon = getIconForVisibility(it.defaultPostPrivacy) + + defaultFormattingSyntaxPreference.value = when(it.defaultFormattingSyntax) { + "text/markdown" -> "Markdown" + "text/bbcode" -> "BBCode" + "text/html" -> "HTML" + else -> "Plaintext" + } + defaultFormattingSyntaxPreference.icon = getIconForSyntax(it.defaultFormattingSyntax) defaultMediaSensitivityPreference.isChecked = it.defaultMediaSensitivity defaultMediaSensitivityPreference.icon = getIconForSensitivity(it.defaultMediaSensitivity) @@ -143,6 +154,19 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), preference.icon = getIconForSensitivity(newValue as Boolean) syncWithServer(sensitive = newValue) } + defaultFormattingSyntaxPreference -> { + val syntax = when(newValue) { + "Markdown" -> "text/markdown" + "BBCode" -> "text/bbcode" + "HTML" -> "text/html" + else -> "" + } + preference.icon = getIconForSyntax(syntax) + accountManager.activeAccount?.let { + it.defaultFormattingSyntax = syntax + accountManager.saveAccount(it) + } + } mediaPreviewEnabledPreference -> { accountManager.activeAccount?.let { it.mediaPreviewEnabled = newValue as Boolean @@ -287,8 +311,20 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), return getTintedIcon(drawableId) } + + private fun getIconForSyntax(syntax: String): Drawable? { + val drawableId = when(syntax) { + "text/html" -> R.drawable.ic_html_24dp + "text/bbcode" -> R.drawable.ic_bbcode_24dp + "text/markdown" -> R.drawable.ic_markdown + else -> 0 + } + + return getTintedIcon(drawableId) + } private fun getTintedIcon(iconId: Int): Drawable? { + if(iconId == 0) return null return ThemeUtils.getTintedDrawable(requireContext(), iconId, R.attr.iconColor) } diff --git a/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt b/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt index a8b3000b..7b234942 100644 --- a/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt +++ b/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt @@ -102,7 +102,7 @@ class SendStatusBroadcastReceiver : BroadcastReceiver() { null, null, null, - null, + "", account.id, 0, randomAlphanumericString(16), diff --git a/app/src/main/java/com/keylesspalace/tusky/service/SendTootService.kt b/app/src/main/java/com/keylesspalace/tusky/service/SendTootService.kt index e2bb1aad..95cd907e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/service/SendTootService.kt +++ b/app/src/main/java/com/keylesspalace/tusky/service/SendTootService.kt @@ -130,8 +130,8 @@ class SendTootService : Service(), Injectable { } tootToSend.retries++ - - val contentType : String? = if (tootToSend.markdownMode != null && tootToSend.markdownMode == true) "text/markdown" else null + + val contentType : String? = if(tootToSend.formattingSyntax.length == 0) null else tootToSend.formattingSyntax val newStatus = NewStatus( tootToSend.text, @@ -152,7 +152,6 @@ class SendTootService : Service(), Injectable { newStatus ) - sendCalls[tootId] = sendCall val callback = object : Callback { @@ -259,7 +258,7 @@ class SendTootService : Service(), Injectable { toot.replyingStatusAuthorUsername, Status.Visibility.byString(toot.visibility), toot.poll, - toot.markdownMode) + toot.formattingSyntax) } private fun cancelSendingIntent(tootId: Int): PendingIntent { @@ -328,7 +327,7 @@ data class TootToSend( val replyingStatusContent: String?, val replyingStatusAuthorUsername: String?, val savedJsonUrls: List?, - val markdownMode: Boolean?, + val formattingSyntax: String, val accountId: Long, val savedTootUid: Int, val idempotencyKey: String, diff --git a/app/src/main/java/com/keylesspalace/tusky/util/SaveTootHelper.java b/app/src/main/java/com/keylesspalace/tusky/util/SaveTootHelper.java index 9d619a86..96c53685 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/SaveTootHelper.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/SaveTootHelper.java @@ -57,7 +57,7 @@ public final class SaveTootHelper { @Nullable String replyingStatusAuthorUsername, @NonNull Status.Visibility statusVisibility, @Nullable NewPoll poll, - @Nullable Boolean markdownMode) { + @NonNull String formattingSyntax) { if (TextUtils.isEmpty(content) && mediaUris.isEmpty() && poll == null) { return false; @@ -89,7 +89,7 @@ public final class SaveTootHelper { replyingStatusContent, replyingStatusAuthorUsername, statusVisibility, - poll, markdownMode); + poll, formattingSyntax); new AsyncTask() { @Override diff --git a/app/src/main/res/drawable/ic_bbcode_24dp.xml b/app/src/main/res/drawable/ic_bbcode_24dp.xml new file mode 100644 index 00000000..d068c028 --- /dev/null +++ b/app/src/main/res/drawable/ic_bbcode_24dp.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_html_24dp.xml b/app/src/main/res/drawable/ic_html_24dp.xml new file mode 100644 index 00000000..38969562 --- /dev/null +++ b/app/src/main/res/drawable/ic_html_24dp.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/app/src/main/res/layout/activity_compose.xml b/app/src/main/res/layout/activity_compose.xml index 622c684e..dc3ad8ca 100644 --- a/app/src/main/res/layout/activity_compose.xml +++ b/app/src/main/res/layout/activity_compose.xml @@ -375,7 +375,7 @@ app:srcCompat="@drawable/ic_access_time" /> Reply to - Markdown Mute conversation Unmute conversation React Remove reaction Who reacted + Enable %s + Disable %s %s reacted by @@ -22,6 +23,7 @@ Emoji Reactions Notifications about new emoji reactions + Default formatting syntax(if supported by instance) my posts are reacted with emojis Hide muted users diff --git a/app/src/main/res/values/husky_donottranslate.xml b/app/src/main/res/values/husky_donottranslate.xml new file mode 100644 index 00000000..06566569 --- /dev/null +++ b/app/src/main/res/values/husky_donottranslate.xml @@ -0,0 +1,13 @@ + + Plaintext + Markdown + BBCode + HTML + + + @string/action_plaintext + @string/action_markdown + @string/action_bbcode + @string/action_html + + diff --git a/app/src/main/res/xml/account_preferences.xml b/app/src/main/res/xml/account_preferences.xml index 5b316c72..a2535c5c 100644 --- a/app/src/main/res/xml/account_preferences.xml +++ b/app/src/main/res/xml/account_preferences.xml @@ -31,6 +31,14 @@ android:key="defaultPostPrivacy" android:summary="%s" android:title="@string/pref_default_post_privacy" /> + +