diff --git a/app/schemas/com.keylesspalace.tusky.db.AppDatabase/66.json b/app/schemas/com.keylesspalace.tusky.db.AppDatabase/66.json
new file mode 100644
index 0000000000..1e5389a917
--- /dev/null
+++ b/app/schemas/com.keylesspalace.tusky.db.AppDatabase/66.json
@@ -0,0 +1,1345 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 66,
+ "identityHash": "379378624adb80f474a2801fc454d783",
+ "entities": [
+ {
+ "tableName": "DraftEntity",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `accountId` INTEGER NOT NULL, `inReplyToId` TEXT, `content` TEXT, `contentWarning` TEXT, `sensitive` INTEGER NOT NULL, `visibility` INTEGER NOT NULL, `attachments` TEXT NOT NULL, `poll` TEXT, `failedToSend` INTEGER NOT NULL, `failedToSendNew` INTEGER NOT NULL, `scheduledAt` TEXT, `language` TEXT, `statusId` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "accountId",
+ "columnName": "accountId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "inReplyToId",
+ "columnName": "inReplyToId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "contentWarning",
+ "columnName": "contentWarning",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sensitive",
+ "columnName": "sensitive",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "visibility",
+ "columnName": "visibility",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "attachments",
+ "columnName": "attachments",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "poll",
+ "columnName": "poll",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "failedToSend",
+ "columnName": "failedToSend",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "failedToSendNew",
+ "columnName": "failedToSendNew",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "scheduledAt",
+ "columnName": "scheduledAt",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "language",
+ "columnName": "language",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "statusId",
+ "columnName": "statusId",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "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, `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, `defaultReplyPrivacy` INTEGER NOT NULL, `defaultMediaSensitivity` INTEGER NOT NULL, `defaultPostLanguage` TEXT NOT NULL, `alwaysShowSensitiveMedia` INTEGER NOT NULL, `alwaysOpenSpoiler` INTEGER NOT NULL, `filterBots` INTEGER NOT NULL DEFAULT 0, `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, `locked` INTEGER NOT NULL DEFAULT 0, `hasDirectMessageBadge` INTEGER NOT NULL DEFAULT 0, `isShowHomeBoosts` INTEGER NOT NULL, `isShowHomeReplies` INTEGER NOT NULL, `isShowHomeSelfBoosts` INTEGER 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": "clientId",
+ "columnName": "clientId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "clientSecret",
+ "columnName": "clientSecret",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "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": "notificationsFollowRequested",
+ "columnName": "notificationsFollowRequested",
+ "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": "notificationsSubscriptions",
+ "columnName": "notificationsSubscriptions",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notificationsSignUps",
+ "columnName": "notificationsSignUps",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notificationsUpdates",
+ "columnName": "notificationsUpdates",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notificationsReports",
+ "columnName": "notificationsReports",
+ "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": "defaultReplyPrivacy",
+ "columnName": "defaultReplyPrivacy",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "defaultMediaSensitivity",
+ "columnName": "defaultMediaSensitivity",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "defaultPostLanguage",
+ "columnName": "defaultPostLanguage",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "alwaysShowSensitiveMedia",
+ "columnName": "alwaysShowSensitiveMedia",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "alwaysOpenSpoiler",
+ "columnName": "alwaysOpenSpoiler",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "filterBots",
+ "columnName": "filterBots",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ },
+ {
+ "fieldPath": "mediaPreviewEnabled",
+ "columnName": "mediaPreviewEnabled",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastNotificationId",
+ "columnName": "lastNotificationId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notificationMarkerId",
+ "columnName": "notificationMarkerId",
+ "affinity": "TEXT",
+ "notNull": true,
+ "defaultValue": "'0'"
+ },
+ {
+ "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": "oauthScopes",
+ "columnName": "oauthScopes",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "unifiedPushUrl",
+ "columnName": "unifiedPushUrl",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "pushPubKey",
+ "columnName": "pushPubKey",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "pushPrivKey",
+ "columnName": "pushPrivKey",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "pushAuth",
+ "columnName": "pushAuth",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "pushServerKey",
+ "columnName": "pushServerKey",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastVisibleHomeTimelineStatusId",
+ "columnName": "lastVisibleHomeTimelineStatusId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "locked",
+ "columnName": "locked",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ },
+ {
+ "fieldPath": "hasDirectMessageBadge",
+ "columnName": "hasDirectMessageBadge",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ },
+ {
+ "fieldPath": "isShowHomeBoosts",
+ "columnName": "isShowHomeBoosts",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isShowHomeReplies",
+ "columnName": "isShowHomeReplies",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isShowHomeSelfBoosts",
+ "columnName": "isShowHomeSelfBoosts",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_AccountEntity_domain_accountId",
+ "unique": true,
+ "columnNames": [
+ "domain",
+ "accountId"
+ ],
+ "orders": [],
+ "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, `minPollDuration` INTEGER, `maxPollDuration` INTEGER, `charactersReservedPerUrl` INTEGER, `version` TEXT, `videoSizeLimit` INTEGER, `imageSizeLimit` INTEGER, `imageMatrixLimit` INTEGER, `maxMediaAttachments` INTEGER, `maxFields` INTEGER, `maxFieldNameLength` INTEGER, `maxFieldValueLength` INTEGER, `translationEnabled` INTEGER, `filterV2Supported` INTEGER NOT NULL DEFAULT false, 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": "minPollDuration",
+ "columnName": "minPollDuration",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "maxPollDuration",
+ "columnName": "maxPollDuration",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "charactersReservedPerUrl",
+ "columnName": "charactersReservedPerUrl",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "version",
+ "columnName": "version",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "videoSizeLimit",
+ "columnName": "videoSizeLimit",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "imageSizeLimit",
+ "columnName": "imageSizeLimit",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "imageMatrixLimit",
+ "columnName": "imageMatrixLimit",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "maxMediaAttachments",
+ "columnName": "maxMediaAttachments",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "maxFields",
+ "columnName": "maxFields",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "maxFieldNameLength",
+ "columnName": "maxFieldNameLength",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "maxFieldValueLength",
+ "columnName": "maxFieldValueLength",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "translationEnabled",
+ "columnName": "translationEnabled",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "filterV2Supported",
+ "columnName": "filterV2Supported",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "false"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "instance"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "TimelineStatusEntity",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serverId` TEXT NOT NULL, `url` TEXT, `tuskyAccountId` INTEGER NOT NULL, `authorServerId` TEXT NOT NULL, `inReplyToId` TEXT, `inReplyToAccountId` TEXT, `content` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `editedAt` INTEGER, `emojis` TEXT NOT NULL, `reblogsCount` INTEGER NOT NULL, `favouritesCount` INTEGER NOT NULL, `repliesCount` INTEGER NOT NULL, `reblogged` INTEGER NOT NULL, `bookmarked` INTEGER NOT NULL, `favourited` INTEGER NOT NULL, `sensitive` INTEGER NOT NULL, `spoilerText` TEXT NOT NULL, `visibility` INTEGER NOT NULL, `attachments` TEXT NOT NULL, `mentions` TEXT NOT NULL, `tags` TEXT NOT NULL, `application` TEXT, `poll` TEXT, `muted` INTEGER NOT NULL, `expanded` INTEGER NOT NULL, `contentCollapsed` INTEGER NOT NULL, `contentShowing` INTEGER NOT NULL, `pinned` INTEGER NOT NULL, `card` TEXT, `language` TEXT, `filtered` TEXT NOT NULL, PRIMARY KEY(`serverId`, `tuskyAccountId`), FOREIGN KEY(`authorServerId`, `tuskyAccountId`) REFERENCES `TimelineAccountEntity`(`serverId`, `tuskyAccountId`) 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": "tuskyAccountId",
+ "columnName": "tuskyAccountId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "authorServerId",
+ "columnName": "authorServerId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "inReplyToId",
+ "columnName": "inReplyToId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "inReplyToAccountId",
+ "columnName": "inReplyToAccountId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "createdAt",
+ "columnName": "createdAt",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "editedAt",
+ "columnName": "editedAt",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "emojis",
+ "columnName": "emojis",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "reblogsCount",
+ "columnName": "reblogsCount",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "favouritesCount",
+ "columnName": "favouritesCount",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "repliesCount",
+ "columnName": "repliesCount",
+ "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": true
+ },
+ {
+ "fieldPath": "visibility",
+ "columnName": "visibility",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "attachments",
+ "columnName": "attachments",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "mentions",
+ "columnName": "mentions",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "tags",
+ "columnName": "tags",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "application",
+ "columnName": "application",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "poll",
+ "columnName": "poll",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "muted",
+ "columnName": "muted",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "expanded",
+ "columnName": "expanded",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "contentCollapsed",
+ "columnName": "contentCollapsed",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "contentShowing",
+ "columnName": "contentShowing",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "pinned",
+ "columnName": "pinned",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "card",
+ "columnName": "card",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "language",
+ "columnName": "language",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "filtered",
+ "columnName": "filtered",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "serverId",
+ "tuskyAccountId"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_TimelineStatusEntity_authorServerId_tuskyAccountId",
+ "unique": false,
+ "columnNames": [
+ "authorServerId",
+ "tuskyAccountId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_TimelineStatusEntity_authorServerId_tuskyAccountId` ON `${TABLE_NAME}` (`authorServerId`, `tuskyAccountId`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "TimelineAccountEntity",
+ "onDelete": "NO ACTION",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "authorServerId",
+ "tuskyAccountId"
+ ],
+ "referencedColumns": [
+ "serverId",
+ "tuskyAccountId"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "TimelineAccountEntity",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serverId` TEXT NOT NULL, `tuskyAccountId` 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`, `tuskyAccountId`))",
+ "fields": [
+ {
+ "fieldPath": "serverId",
+ "columnName": "serverId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "tuskyAccountId",
+ "columnName": "tuskyAccountId",
+ "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": {
+ "autoGenerate": false,
+ "columnNames": [
+ "serverId",
+ "tuskyAccountId"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "ConversationEntity",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`accountId` INTEGER NOT NULL, `id` TEXT NOT NULL, `order` INTEGER 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_editedAt` INTEGER, `s_emojis` TEXT NOT NULL, `s_favouritesCount` INTEGER NOT NULL, `s_repliesCount` 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_tags` TEXT, `s_showingHiddenContent` INTEGER NOT NULL, `s_expanded` INTEGER NOT NULL, `s_collapsed` INTEGER NOT NULL, `s_muted` INTEGER NOT NULL, `s_poll` TEXT, `s_language` TEXT, PRIMARY KEY(`id`, `accountId`))",
+ "fields": [
+ {
+ "fieldPath": "accountId",
+ "columnName": "accountId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "order",
+ "columnName": "order",
+ "affinity": "INTEGER",
+ "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.editedAt",
+ "columnName": "s_editedAt",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastStatus.emojis",
+ "columnName": "s_emojis",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.favouritesCount",
+ "columnName": "s_favouritesCount",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.repliesCount",
+ "columnName": "s_repliesCount",
+ "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.tags",
+ "columnName": "s_tags",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastStatus.showingHiddenContent",
+ "columnName": "s_showingHiddenContent",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.expanded",
+ "columnName": "s_expanded",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.collapsed",
+ "columnName": "s_collapsed",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.muted",
+ "columnName": "s_muted",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.poll",
+ "columnName": "s_poll",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastStatus.language",
+ "columnName": "s_language",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id",
+ "accountId"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "NotificationEntity",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tuskyAccountId` INTEGER NOT NULL, `type` TEXT, `id` TEXT NOT NULL, `accountId` TEXT, `statusId` TEXT, `reportId` TEXT, `loading` INTEGER NOT NULL, PRIMARY KEY(`id`, `tuskyAccountId`), FOREIGN KEY(`accountId`, `tuskyAccountId`) REFERENCES `TimelineAccountEntity`(`serverId`, `tuskyAccountId`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`statusId`, `tuskyAccountId`) REFERENCES `TimelineStatusEntity`(`serverId`, `tuskyAccountId`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`reportId`, `tuskyAccountId`) REFERENCES `NotificationReportEntity`(`serverId`, `tuskyAccountId`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
+ "fields": [
+ {
+ "fieldPath": "tuskyAccountId",
+ "columnName": "tuskyAccountId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "accountId",
+ "columnName": "accountId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "statusId",
+ "columnName": "statusId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "reportId",
+ "columnName": "reportId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "loading",
+ "columnName": "loading",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id",
+ "tuskyAccountId"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_NotificationEntity_accountId_tuskyAccountId",
+ "unique": false,
+ "columnNames": [
+ "accountId",
+ "tuskyAccountId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_NotificationEntity_accountId_tuskyAccountId` ON `${TABLE_NAME}` (`accountId`, `tuskyAccountId`)"
+ },
+ {
+ "name": "index_NotificationEntity_statusId_tuskyAccountId",
+ "unique": false,
+ "columnNames": [
+ "statusId",
+ "tuskyAccountId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_NotificationEntity_statusId_tuskyAccountId` ON `${TABLE_NAME}` (`statusId`, `tuskyAccountId`)"
+ },
+ {
+ "name": "index_NotificationEntity_reportId_tuskyAccountId",
+ "unique": false,
+ "columnNames": [
+ "reportId",
+ "tuskyAccountId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_NotificationEntity_reportId_tuskyAccountId` ON `${TABLE_NAME}` (`reportId`, `tuskyAccountId`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "TimelineAccountEntity",
+ "onDelete": "NO ACTION",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "accountId",
+ "tuskyAccountId"
+ ],
+ "referencedColumns": [
+ "serverId",
+ "tuskyAccountId"
+ ]
+ },
+ {
+ "table": "TimelineStatusEntity",
+ "onDelete": "NO ACTION",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "statusId",
+ "tuskyAccountId"
+ ],
+ "referencedColumns": [
+ "serverId",
+ "tuskyAccountId"
+ ]
+ },
+ {
+ "table": "NotificationReportEntity",
+ "onDelete": "NO ACTION",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "reportId",
+ "tuskyAccountId"
+ ],
+ "referencedColumns": [
+ "serverId",
+ "tuskyAccountId"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "NotificationReportEntity",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tuskyAccountId` INTEGER NOT NULL, `serverId` TEXT NOT NULL, `category` TEXT NOT NULL, `statusIds` TEXT, `createdAt` INTEGER NOT NULL, `targetAccountId` TEXT, PRIMARY KEY(`serverId`, `tuskyAccountId`), FOREIGN KEY(`targetAccountId`, `tuskyAccountId`) REFERENCES `TimelineAccountEntity`(`serverId`, `tuskyAccountId`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
+ "fields": [
+ {
+ "fieldPath": "tuskyAccountId",
+ "columnName": "tuskyAccountId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "serverId",
+ "columnName": "serverId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "category",
+ "columnName": "category",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "statusIds",
+ "columnName": "statusIds",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "createdAt",
+ "columnName": "createdAt",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "targetAccountId",
+ "columnName": "targetAccountId",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "serverId",
+ "tuskyAccountId"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_NotificationReportEntity_targetAccountId_tuskyAccountId",
+ "unique": false,
+ "columnNames": [
+ "targetAccountId",
+ "tuskyAccountId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_NotificationReportEntity_targetAccountId_tuskyAccountId` ON `${TABLE_NAME}` (`targetAccountId`, `tuskyAccountId`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "TimelineAccountEntity",
+ "onDelete": "NO ACTION",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "targetAccountId",
+ "tuskyAccountId"
+ ],
+ "referencedColumns": [
+ "serverId",
+ "tuskyAccountId"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "HomeTimelineEntity",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tuskyAccountId` INTEGER NOT NULL, `id` TEXT NOT NULL, `statusId` TEXT, `reblogAccountId` TEXT, `loading` INTEGER NOT NULL, PRIMARY KEY(`id`, `tuskyAccountId`), FOREIGN KEY(`statusId`, `tuskyAccountId`) REFERENCES `TimelineStatusEntity`(`serverId`, `tuskyAccountId`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`reblogAccountId`, `tuskyAccountId`) REFERENCES `TimelineAccountEntity`(`serverId`, `tuskyAccountId`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
+ "fields": [
+ {
+ "fieldPath": "tuskyAccountId",
+ "columnName": "tuskyAccountId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "statusId",
+ "columnName": "statusId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "reblogAccountId",
+ "columnName": "reblogAccountId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "loading",
+ "columnName": "loading",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "id",
+ "tuskyAccountId"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_HomeTimelineEntity_statusId_tuskyAccountId",
+ "unique": false,
+ "columnNames": [
+ "statusId",
+ "tuskyAccountId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_HomeTimelineEntity_statusId_tuskyAccountId` ON `${TABLE_NAME}` (`statusId`, `tuskyAccountId`)"
+ },
+ {
+ "name": "index_HomeTimelineEntity_reblogAccountId_tuskyAccountId",
+ "unique": false,
+ "columnNames": [
+ "reblogAccountId",
+ "tuskyAccountId"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_HomeTimelineEntity_reblogAccountId_tuskyAccountId` ON `${TABLE_NAME}` (`reblogAccountId`, `tuskyAccountId`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "TimelineStatusEntity",
+ "onDelete": "NO ACTION",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "statusId",
+ "tuskyAccountId"
+ ],
+ "referencedColumns": [
+ "serverId",
+ "tuskyAccountId"
+ ]
+ },
+ {
+ "table": "TimelineAccountEntity",
+ "onDelete": "NO ACTION",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "reblogAccountId",
+ "tuskyAccountId"
+ ],
+ "referencedColumns": [
+ "serverId",
+ "tuskyAccountId"
+ ]
+ }
+ ]
+ }
+ ],
+ "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, '379378624adb80f474a2801fc454d783')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/AccountPreferencesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/AccountPreferencesFragment.kt
index aab700ce79..62897326c6 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/preference/AccountPreferencesFragment.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/AccountPreferencesFragment.kt
@@ -284,6 +284,13 @@ class AccountPreferencesFragment : PreferenceFragmentCompat() {
isSingleLineTitle = false
preferenceDataStore = accountPreferenceDataStore
}
+
+ switchPreference {
+ key = PrefKeys.FILTER_BOTS
+ setTitle(R.string.pref_title_filter_bots)
+ isSingleLineTitle = false
+ preferenceDataStore = accountPreferenceDataStore
+ }
}
preferenceCategory(R.string.pref_title_per_timeline_preferences) {
preference {
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 81597e00ce..5736bb02a0 100644
--- a/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java
+++ b/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java
@@ -62,7 +62,7 @@
},
// Note: Starting with version 54, database versions in Tusky are always even.
// This is to reserve odd version numbers for use by forks.
- version = 64,
+ version = 66,
autoMigrations = {
@AutoMigration(from = 48, to = 49),
@AutoMigration(from = 49, to = 50, spec = AppDatabase.MIGRATION_49_50.class),
@@ -70,7 +70,8 @@
@AutoMigration(from = 51, to = 52),
@AutoMigration(from = 53, to = 54), // hasDirectMessageBadge in AccountEntity
@AutoMigration(from = 56, to = 58), // translationEnabled in InstanceEntity/InstanceInfoEntity
- @AutoMigration(from = 62, to = 64) // filterV2Available in InstanceEntity
+ @AutoMigration(from = 62, to = 64), // filterV2Available in InstanceEntity
+ @AutoMigration(from = 64, to = 66), // filterBots in AccountEntity
}
)
public abstract class AppDatabase extends RoomDatabase {
diff --git a/app/src/main/java/com/keylesspalace/tusky/db/entity/AccountEntity.kt b/app/src/main/java/com/keylesspalace/tusky/db/entity/AccountEntity.kt
index a774788124..2566ae7f55 100644
--- a/app/src/main/java/com/keylesspalace/tusky/db/entity/AccountEntity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/db/entity/AccountEntity.kt
@@ -71,6 +71,10 @@ data class AccountEntity(
/** True if content behind a content warning is shown by default */
var alwaysOpenSpoiler: Boolean = false,
+ /** Filter timeline posts by bots */
+ @ColumnInfo(defaultValue = "0")
+ var filterBots: Boolean = false,
+
/**
* True if the "Download media previews" preference is true. This implies
* that media previews are shown as well as downloaded.
diff --git a/app/src/main/java/com/keylesspalace/tusky/network/FilterModel.kt b/app/src/main/java/com/keylesspalace/tusky/network/FilterModel.kt
index 3552125891..f852025843 100644
--- a/app/src/main/java/com/keylesspalace/tusky/network/FilterModel.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/network/FilterModel.kt
@@ -4,6 +4,7 @@ import android.util.Log
import at.connyduck.calladapter.networkresult.fold
import at.connyduck.calladapter.networkresult.getOrElse
import com.keylesspalace.tusky.components.instanceinfo.InstanceInfoRepository
+import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.entity.Filter
import com.keylesspalace.tusky.entity.FilterV1
import com.keylesspalace.tusky.entity.Status
@@ -21,10 +22,12 @@ import javax.inject.Inject
*/
class FilterModel @Inject constructor(
private val instanceInfoRepo: InstanceInfoRepository,
- private val api: MastodonApi
+ private val api: MastodonApi,
+ private val accountManager: AccountManager,
) {
private var pattern: Pattern? = null
private var v1 = false
+ private var filterBots: Boolean = false
private lateinit var kind: Filter.Kind
/**
@@ -33,6 +36,11 @@ class FilterModel @Inject constructor(
*/
suspend fun init(kind: Filter.Kind): Boolean {
this.kind = kind
+ filterBots = when (kind) {
+ Filter.Kind.HOME,
+ Filter.Kind.PUBLIC -> accountManager.activeAccount?.filterBots ?: false
+ else -> false
+ }
if (instanceInfoRepo.isFilterV2Supported()) {
// nothing to do - Instance supports V2 so posts are filtered by the server
@@ -67,6 +75,10 @@ class FilterModel @Inject constructor(
}
fun shouldFilterStatus(status: Status): Filter.Action {
+ if (filterBots && status.account.bot) {
+ return Filter.Action.HIDE
+ }
+
if (v1) {
// Patterns are expensive and thread-safe, matchers are neither.
val matcher = pattern?.matcher("") ?: return Filter.Action.NONE
diff --git a/app/src/main/java/com/keylesspalace/tusky/settings/AccountPreferenceDataStore.kt b/app/src/main/java/com/keylesspalace/tusky/settings/AccountPreferenceDataStore.kt
index d62731f010..841da583f3 100644
--- a/app/src/main/java/com/keylesspalace/tusky/settings/AccountPreferenceDataStore.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/settings/AccountPreferenceDataStore.kt
@@ -25,6 +25,7 @@ class AccountPreferenceDataStore @Inject constructor(
PrefKeys.TAB_FILTER_HOME_BOOSTS -> account.isShowHomeBoosts
PrefKeys.TAB_FILTER_HOME_REPLIES -> account.isShowHomeReplies
PrefKeys.TAB_SHOW_HOME_SELF_BOOSTS -> account.isShowHomeSelfBoosts
+ PrefKeys.FILTER_BOTS -> account.filterBots
else -> defValue
}
}
@@ -37,6 +38,7 @@ class AccountPreferenceDataStore @Inject constructor(
PrefKeys.TAB_FILTER_HOME_BOOSTS -> account.isShowHomeBoosts = value
PrefKeys.TAB_FILTER_HOME_REPLIES -> account.isShowHomeReplies = value
PrefKeys.TAB_SHOW_HOME_SELF_BOOSTS -> account.isShowHomeSelfBoosts = value
+ PrefKeys.FILTER_BOTS -> account.filterBots = value
}
accountManager.saveAccount(account)
diff --git a/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt b/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt
index 4861f59786..e73e8c5667 100644
--- a/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt
@@ -91,6 +91,7 @@ object PrefKeys {
const val MEDIA_PREVIEW_ENABLED = "mediaPreviewEnabled"
const val ALWAYS_SHOW_SENSITIVE_MEDIA = "alwaysShowSensitiveMedia"
const val ALWAYS_OPEN_SPOILER = "alwaysOpenSpoiler"
+ const val FILTER_BOTS = "filterBots"
const val NOTIFICATIONS_ENABLED = "notificationsEnabled"
const val NOTIFICATION_ALERT_LIGHT = "notificationAlertLight"
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index cdec38c392..f21a5c971c 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -326,6 +326,7 @@
HTTP proxy server
HTTP proxy port
Port should be between %1$d and %2$d
+ Hide posts by bot accounts
Disabled
<not set>
<invalid>
diff --git a/app/src/test/java/com/keylesspalace/tusky/FilterV1Test.kt b/app/src/test/java/com/keylesspalace/tusky/FilterV1Test.kt
index c7e9222c88..0fcff2e2f7 100644
--- a/app/src/test/java/com/keylesspalace/tusky/FilterV1Test.kt
+++ b/app/src/test/java/com/keylesspalace/tusky/FilterV1Test.kt
@@ -119,7 +119,7 @@ class FilterV1Test {
onBlocking { isFilterV2Supported() } doReturn false
}
- filterModel = FilterModel(instanceInfoRepo, api)
+ filterModel = FilterModel(instanceInfoRepo, api, mock())
runBlocking {
filterModel.init(Filter.Kind.HOME)
}
diff --git a/app/src/test/java/com/keylesspalace/tusky/components/viewthread/ViewThreadViewModelTest.kt b/app/src/test/java/com/keylesspalace/tusky/components/viewthread/ViewThreadViewModelTest.kt
index 14b872a409..5287256417 100644
--- a/app/src/test/java/com/keylesspalace/tusky/components/viewthread/ViewThreadViewModelTest.kt
+++ b/app/src/test/java/com/keylesspalace/tusky/components/viewthread/ViewThreadViewModelTest.kt
@@ -86,7 +86,7 @@ class ViewThreadViewModelTest {
onBlocking { isFilterV2Supported() } doReturn false
}
eventHub = EventHub()
- val filterModel = FilterModel(instanceInfoRepo, api)
+ val filterModel = FilterModel(instanceInfoRepo, api, mock())
val timelineCases = TimelineCases(api, eventHub)
val accountManager: AccountManager = mock {
on { activeAccount } doReturn AccountEntity(