diff --git a/app/schemas/com.keylesspalace.tusky.db.AppDatabase/47.json b/app/schemas/com.keylesspalace.tusky.db.AppDatabase/47.json new file mode 100644 index 000000000..ba5202639 --- /dev/null +++ b/app/schemas/com.keylesspalace.tusky.db.AppDatabase/47.json @@ -0,0 +1,995 @@ +{ + "formatVersion": 1, + "database": { + "version": 47, + "identityHash": "308b3faf2255729075a85abab23a1c9e", + "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": { + "columnNames": [ + "id" + ], + "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, `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, `activeNotifications` TEXT NOT NULL, `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)", + "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": "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": "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": "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 + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "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, 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 + } + ], + "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, `editedAt` INTEGER, `emojis` TEXT, `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, `mentions` TEXT, `tags` TEXT, `application` TEXT, `reblogServerId` TEXT, `reblogAccountId` TEXT, `poll` TEXT, `muted` INTEGER, `expanded` INTEGER NOT NULL, `contentCollapsed` INTEGER NOT NULL, `contentShowing` INTEGER NOT NULL, `pinned` INTEGER NOT NULL, `card` TEXT, `language` TEXT, `quote` 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": "editedAt", + "columnName": "editedAt", + "affinity": "INTEGER", + "notNull": false + }, + { + "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": "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": false + }, + { + "fieldPath": "mentions", + "columnName": "mentions", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "tags", + "columnName": "tags", + "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 + }, + { + "fieldPath": "muted", + "columnName": "muted", + "affinity": "INTEGER", + "notNull": false + }, + { + "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": "quote", + "columnName": "quote", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "serverId", + "timelineUserId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_TimelineStatusEntity_authorServerId_timelineUserId", + "unique": false, + "columnNames": [ + "authorServerId", + "timelineUserId" + ], + "orders": [], + "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, `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": { + "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, '308b3faf2255729075a85abab23a1c9e')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 77f81aba4..932ba219d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -39,7 +39,18 @@ + android:windowSoftInputMode="adjustResize" + android:exported="true"> + + + + + + + + , poll: NewPoll?, failedToSend: Boolean, + failedToSendAlert: Boolean, scheduledAt: String?, language: String?, statusId: String?, @@ -123,6 +124,7 @@ class DraftHelper @Inject constructor( attachments = attachments, poll = poll, failedToSend = failedToSend, + failedToSendNew = failedToSendAlert, scheduledAt = scheduledAt, language = language, statusId = statusId, diff --git a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsActivity.kt index 7cdcd2597..6d9a2aa16 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsActivity.kt @@ -33,6 +33,7 @@ import com.keylesspalace.tusky.R import com.keylesspalace.tusky.components.compose.ComposeActivity import com.keylesspalace.tusky.databinding.ActivityDraftsBinding import com.keylesspalace.tusky.db.DraftEntity +import com.keylesspalace.tusky.db.DraftsAlert import com.keylesspalace.tusky.di.ViewModelFactory import com.keylesspalace.tusky.util.parseAsMastodonHtml import com.keylesspalace.tusky.util.visible @@ -46,6 +47,9 @@ class DraftsActivity : BaseActivity(), DraftActionListener { @Inject lateinit var viewModelFactory: ViewModelFactory + @Inject + lateinit var draftsAlert: DraftsAlert + private val viewModel: DraftsViewModel by viewModels { viewModelFactory } private lateinit var binding: ActivityDraftsBinding @@ -83,6 +87,9 @@ class DraftsActivity : BaseActivity(), DraftActionListener { adapter.addLoadStateListener { binding.draftsErrorMessageView.visible(adapter.itemCount == 0) } + + // If a failed post is saved to drafts while this activity is up, do nothing; the user is already in the drafts view. + draftsAlert.observeInContext(this, false) } override fun onOpenDraft(draft: DraftEntity) { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/login/LoginActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/login/LoginActivity.kt index fd93a01ce..c5ad6e79c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/login/LoginActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/login/LoginActivity.kt @@ -21,6 +21,7 @@ import android.content.SharedPreferences import android.os.Bundle import android.text.method.LinkMovementMethod import android.util.Log +import android.view.Menu import android.view.View import android.widget.TextView import androidx.appcompat.app.AlertDialog @@ -37,6 +38,7 @@ import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.entity.AccessToken import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.util.getNonNullString +import com.keylesspalace.tusky.util.openLinkInCustomTab import com.keylesspalace.tusky.util.viewBinding import kotlinx.coroutines.launch import okhttp3.HttpUrl @@ -64,24 +66,8 @@ class LoginActivity : BaseActivity(), Injectable { is LoginResult.Ok -> lifecycleScope.launch { fetchOauthToken(result.code) } - is LoginResult.Err -> { - // Authorization failed. Put the error response where the user can read it and they - // can try again. - setLoading(false) - // Use error returned by the server or fall back to the generic message - binding.domainTextInputLayout.error = - result.errorMessage.ifBlank { getString(R.string.error_authorization_denied) } - Log.e( - TAG, - "%s %s".format( - getString(R.string.error_authorization_denied), - result.errorMessage - ) - ) - } - is LoginResult.Cancel -> { - setLoading(false) - } + is LoginResult.Err -> displayError(result.errorMessage) + is LoginResult.Cancel -> setLoading(false) } } @@ -114,7 +100,7 @@ class LoginActivity : BaseActivity(), Injectable { getString(R.string.preferences_file_key), Context.MODE_PRIVATE ) - binding.loginButton.setOnClickListener { onButtonClick() } + binding.loginButton.setOnClickListener { onLoginClick(true) } binding.whatsAnInstanceTextView.setOnClickListener { val dialog = AlertDialog.Builder(this) @@ -125,13 +111,9 @@ class LoginActivity : BaseActivity(), Injectable { textView?.movementMethod = LinkMovementMethod.getInstance() } - if (isAdditionalLogin() || isAccountMigration()) { - setSupportActionBar(binding.toolbar) - supportActionBar?.setDisplayHomeAsUpEnabled(true) - supportActionBar?.setDisplayShowTitleEnabled(false) - } else { - binding.toolbar.visibility = View.GONE - } + setSupportActionBar(binding.toolbar) + supportActionBar?.setDisplayHomeAsUpEnabled(isAdditionalLogin() || isAccountMigration()) + supportActionBar?.setDisplayShowTitleEnabled(false) } override fun requiresLogin(): Boolean { @@ -145,12 +127,23 @@ class LoginActivity : BaseActivity(), Injectable { } } + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menu?.add(R.string.action_browser_login)?.apply { + setOnMenuItemClickListener { + onLoginClick(false) + true + } + } + + return super.onCreateOptionsMenu(menu) + } + /** * Obtain the oauth client credentials for this app. This is only necessary the first time the * app is run on a given server instance. So, after the first authentication, they are * saved in SharedPreferences and every subsequent run they are simply fetched from there. */ - private fun onButtonClick() { + private fun onLoginClick(openInWebView: Boolean) { binding.loginButton.isEnabled = false binding.domainTextInputLayout.error = null @@ -183,7 +176,7 @@ class LoginActivity : BaseActivity(), Injectable { .putString(CLIENT_SECRET, credentials.clientSecret) .apply() - redirectUserToAuthorizeAndLogin(domain, credentials.clientId) + redirectUserToAuthorizeAndLogin(domain, credentials.clientId, openInWebView) }, { e -> binding.loginButton.isEnabled = true @@ -197,10 +190,10 @@ class LoginActivity : BaseActivity(), Injectable { } } - private fun redirectUserToAuthorizeAndLogin(domain: String, clientId: String) { + private fun redirectUserToAuthorizeAndLogin(domain: String, clientId: String, openInWebView: Boolean) { // To authorize this app and log in it's necessary to redirect to the domain given, // login there, and the server will redirect back to the app with its response. - val url = HttpUrl.Builder() + val uri = HttpUrl.Builder() .scheme("https") .host(domain) .addPathSegments(MastodonApi.ENDPOINT_AUTHORIZE) @@ -209,13 +202,59 @@ class LoginActivity : BaseActivity(), Injectable { .addQueryParameter("response_type", "code") .addQueryParameter("scope", OAUTH_SCOPES) .build() - doWebViewAuth.launch(LoginData(domain, url.toString().toUri(), oauthRedirectUri.toUri())) + .toString() + .toUri() + + if (openInWebView) { + doWebViewAuth.launch(LoginData(domain, uri, oauthRedirectUri.toUri())) + } else { + openLinkInCustomTab(uri, this) + } } override fun onStart() { super.onStart() - // first show or user cancelled login + + /* Check if we are resuming during authorization by seeing if the intent contains the + * redirect that was given to the server. If so, its response is here! */ + val uri = intent.data + + if (uri?.toString()?.startsWith(oauthRedirectUri) == true) { + // This should either have returned an authorization code or an error. + val code = uri.getQueryParameter("code") + val error = uri.getQueryParameter("error") + + /* restore variables from SharedPreferences */ + val domain = preferences.getNonNullString(DOMAIN, "") + val clientId = preferences.getNonNullString(CLIENT_ID, "") + val clientSecret = preferences.getNonNullString(CLIENT_SECRET, "") + + if (code != null && domain.isNotEmpty() && clientId.isNotEmpty() && clientSecret.isNotEmpty()) { + lifecycleScope.launch { + fetchOauthToken(code) + } + } else { + displayError(error) + } + } else { + // first show or user cancelled login + setLoading(false) + } + } + + private fun displayError(error: String?) { + // Authorization failed. Put the error response where the user can read it and they + // can try again. setLoading(false) + + binding.domainTextInputLayout.error = if (error == null) { + // This case means a junk response was received somehow. + getString(R.string.error_authorization_unknown) + } else { + // Use error returned by the server or fall back to the generic message + Log.e(TAG, "%s %s".format(getString(R.string.error_authorization_denied), error)) + error.ifBlank { getString(R.string.error_authorization_denied) } + } } private suspend fun fetchOauthToken(code: String) { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/notifications/PushNotificationHelper.kt b/app/src/main/java/com/keylesspalace/tusky/components/notifications/PushNotificationHelper.kt index 0d804dd97..cf1dd4384 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/notifications/PushNotificationHelper.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/notifications/PushNotificationHelper.kt @@ -36,7 +36,6 @@ import com.keylesspalace.tusky.util.CryptoUtil import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.unifiedpush.android.connector.UnifiedPush -import retrofit2.HttpException private const val TAG = "PushNotificationHelper" @@ -210,10 +209,8 @@ suspend fun updateUnifiedPushSubscription(context: Context, api: MastodonApi, ac suspend fun unregisterUnifiedPushEndpoint(api: MastodonApi, accountManager: AccountManager, account: AccountEntity) { withContext(Dispatchers.IO) { api.unsubscribePushNotifications("Bearer ${account.accessToken}", account.domain) - .onFailure { - Log.d(TAG, "Error unregistering push endpoint for account " + account.id) - Log.d(TAG, Log.getStackTraceString(it)) - Log.d(TAG, (it as HttpException).response().toString()) + .onFailure { throwable -> + Log.w(TAG, "Error unregistering push endpoint for account " + account.id, throwable) } .onSuccess { Log.d(TAG, "UnifiedPush unregistration succeeded for account " + account.id) 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 2b4ed61f7..ceeeba2c7 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java +++ b/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java @@ -31,7 +31,7 @@ import java.io.File; */ @Database(entities = { DraftEntity.class, AccountEntity.class, InstanceEntity.class, TimelineStatusEntity.class, TimelineAccountEntity.class, ConversationEntity.class - }, version = 46) + }, version = 47) public abstract class AppDatabase extends RoomDatabase { public abstract AccountDao accountDao(); @@ -640,4 +640,11 @@ public abstract class AppDatabase extends RoomDatabase { database.execSQL("ALTER TABLE `DraftEntity` ADD COLUMN `statusId` TEXT"); } }; + + public static final Migration MIGRATION_46_47 = new Migration(46, 47) { + @Override + public void migrate(@NonNull SupportSQLiteDatabase database) { + database.execSQL("ALTER TABLE `DraftEntity` ADD COLUMN `failedToSendNew` INTEGER NOT NULL DEFAULT 0"); + } + }; } diff --git a/app/src/main/java/com/keylesspalace/tusky/db/DraftDao.kt b/app/src/main/java/com/keylesspalace/tusky/db/DraftDao.kt index 8029dd236..7b1f62b8c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/DraftDao.kt +++ b/app/src/main/java/com/keylesspalace/tusky/db/DraftDao.kt @@ -15,6 +15,7 @@ package com.keylesspalace.tusky.db +import androidx.lifecycle.LiveData import androidx.paging.PagingSource import androidx.room.Dao import androidx.room.Insert @@ -30,6 +31,12 @@ interface DraftDao { @Query("SELECT * FROM DraftEntity WHERE accountId = :accountId ORDER BY id ASC") fun draftsPagingSource(accountId: Long): PagingSource + @Query("SELECT COUNT(*) FROM DraftEntity WHERE accountId = :accountId AND failedToSendNew = 1") + fun draftsNeedUserAlert(accountId: Long): LiveData + + @Query("UPDATE DraftEntity SET failedToSendNew = 0 WHERE accountId = :accountId AND failedToSendNew = 1") + suspend fun draftsClearNeedUserAlert(accountId: Long) + @Query("SELECT * FROM DraftEntity WHERE accountId = :accountId") suspend fun loadDrafts(accountId: Long): List diff --git a/app/src/main/java/com/keylesspalace/tusky/db/DraftEntity.kt b/app/src/main/java/com/keylesspalace/tusky/db/DraftEntity.kt index d5f9edc9b..0b38385ae 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/DraftEntity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/db/DraftEntity.kt @@ -40,6 +40,7 @@ data class DraftEntity( val attachments: List, val poll: NewPoll?, val failedToSend: Boolean, + val failedToSendNew: Boolean, val scheduledAt: String?, val language: String?, val statusId: String?, diff --git a/app/src/main/java/com/keylesspalace/tusky/db/DraftsAlert.kt b/app/src/main/java/com/keylesspalace/tusky/db/DraftsAlert.kt new file mode 100644 index 000000000..917305d19 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/db/DraftsAlert.kt @@ -0,0 +1,99 @@ +/* Copyright 2023 Andi McClure + * + * This file is a part of Tusky. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Tusky; if not, + * see . */ + +package com.keylesspalace.tusky.db + +import android.content.Context +import android.content.DialogInterface +import android.util.Log +import androidx.appcompat.app.AlertDialog +import androidx.lifecycle.LifecycleCoroutineScope +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import com.keylesspalace.tusky.R +import com.keylesspalace.tusky.components.drafts.DraftsActivity +import kotlinx.coroutines.launch +import javax.inject.Inject +import javax.inject.Singleton + +/** + * This class manages an alert popup when a post has failed and been saved to drafts. + * It must be separately registered in each lifetime in which it is to appear, + * and it only appears if the post failure belongs to the current user. + */ + +private const val TAG = "DraftsAlert" + +@Singleton +class DraftsAlert @Inject constructor(db: AppDatabase) { + // For tracking when a media upload fails in the service + private val draftDao: DraftDao = db.draftDao() + + @Inject + lateinit var accountManager: AccountManager + + public fun observeInContext(context: T, showAlert: Boolean) where T : Context, T : LifecycleOwner { + accountManager.activeAccount?.let { activeAccount -> + val coroutineScope = context.lifecycleScope + + // Assume a single MainActivity, AccountActivity or DraftsActivity never sees more then one user id in its lifetime. + val activeAccountId = activeAccount.id + + // This LiveData will be automatically disposed when the activity is destroyed. + val draftsNeedUserAlert = draftDao.draftsNeedUserAlert(activeAccountId) + + // observe ensures that this gets called at the most appropriate moment wrt the context lifecycle— + // at init, at next onResume, or immediately if the context is resumed already. + if (showAlert) { + draftsNeedUserAlert.observe(context) { count -> + Log.d(TAG, "User id $activeAccountId changed: Notification-worthy draft count $count") + if (count > 0) { + AlertDialog.Builder(context) + .setTitle(R.string.action_post_failed) + .setMessage( + context.getResources().getQuantityString(R.plurals.action_post_failed_detail, count) + ) + .setPositiveButton(R.string.action_post_failed_show_drafts) { _: DialogInterface?, _: Int -> + clearDraftsAlert(coroutineScope, activeAccountId) // User looked at drafts + + val intent = DraftsActivity.newIntent(context) + context.startActivity(intent) + } + .setNegativeButton(R.string.action_post_failed_do_nothing) { _: DialogInterface?, _: Int -> + clearDraftsAlert(coroutineScope, activeAccountId) // User doesn't care + } + .show() + } + } + } else { + draftsNeedUserAlert.observe(context) { _ -> + Log.d(TAG, "User id $activeAccountId: Clean out notification-worthy drafts") + clearDraftsAlert(coroutineScope, activeAccountId) + } + } + } ?: run { + Log.w(TAG, "Attempted to observe drafts, but there is no active account") + } + } + + /** + * Clear drafts alert for specified user + */ + fun clearDraftsAlert(coroutineScope: LifecycleCoroutineScope, id: Long) { + coroutineScope.launch { + draftDao.draftsClearNeedUserAlert(id) + } + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/db/TimelineDao.kt b/app/src/main/java/com/keylesspalace/tusky/db/TimelineDao.kt index cb38752f7..c00e3b51d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/TimelineDao.kt +++ b/app/src/main/java/com/keylesspalace/tusky/db/TimelineDao.kt @@ -73,7 +73,8 @@ rb.emojis as 'rb_emojis', rb.bot as 'rb_bot' FROM TimelineStatusEntity s LEFT JOIN TimelineAccountEntity a ON (s.timelineUserId = a.timelineUserId AND s.authorServerId = a.serverId) LEFT JOIN TimelineAccountEntity rb ON (s.timelineUserId = rb.timelineUserId AND s.reblogAccountId = rb.serverId) -WHERE s.serverId = :statusId OR s.reblogServerId = :statusId""" +WHERE (s.serverId = :statusId OR s.reblogServerId = :statusId) +AND s.authorServerId IS NOT NULL""" ) abstract suspend fun getStatus(statusId: String): TimelineStatusWithAccount? 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 91d355fa1..85ec3a4be 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt @@ -73,7 +73,7 @@ class AppModule { AppDatabase.MIGRATION_35_36, AppDatabase.MIGRATION_36_37, AppDatabase.MIGRATION_37_38, AppDatabase.MIGRATION_38_39, AppDatabase.MIGRATION_39_40, AppDatabase.MIGRATION_40_41, AppDatabase.MIGRATION_41_42, AppDatabase.MIGRATION_42_43, AppDatabase.MIGRATION_43_44, - AppDatabase.MIGRATION_44_45, AppDatabase.MIGRATION_45_46, + AppDatabase.MIGRATION_44_45, AppDatabase.MIGRATION_45_46, AppDatabase.MIGRATION_46_47 ) .build() } diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewVideoFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewVideoFragment.kt index 566aa6472..b3c0246de 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewVideoFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewVideoFragment.kt @@ -98,6 +98,9 @@ class ViewVideoFragment : ViewMediaFragment() { binding.mediaDescription.visible(showingDescription) binding.mediaDescription.movementMethod = ScrollingMovementMethod() + // Ensure the description is visible over the video + binding.mediaDescription.elevation = binding.videoView.elevation + 1 + binding.videoView.transitionName = url binding.videoView.setVideoPath(url) mediaController = object : MediaController(mediaActivity) { diff --git a/app/src/main/java/com/keylesspalace/tusky/service/SendStatusService.kt b/app/src/main/java/com/keylesspalace/tusky/service/SendStatusService.kt index 6851f9c66..62433a823 100644 --- a/app/src/main/java/com/keylesspalace/tusky/service/SendStatusService.kt +++ b/app/src/main/java/com/keylesspalace/tusky/service/SendStatusService.kt @@ -266,7 +266,7 @@ class SendStatusService : Service(), Injectable { mediaUploader.cancelUploadScope(*failedStatus.media.map { it.localId }.toIntArray()) - saveStatusToDrafts(failedStatus) + saveStatusToDrafts(failedStatus, failedToSendAlert = true) val notification = buildDraftNotification( R.string.send_post_notification_error_title, @@ -289,7 +289,7 @@ class SendStatusService : Service(), Injectable { val sendJob = sendJobs.remove(statusId) sendJob?.cancel() - saveStatusToDrafts(statusToCancel) + saveStatusToDrafts(statusToCancel, failedToSendAlert = false) val notification = buildDraftNotification( R.string.send_post_notification_cancel_title, @@ -306,7 +306,7 @@ class SendStatusService : Service(), Injectable { } } - private suspend fun saveStatusToDrafts(status: StatusToSend) { + private suspend fun saveStatusToDrafts(status: StatusToSend, failedToSendAlert: Boolean) { draftHelper.saveDraft( draftId = status.draftId, accountId = status.accountId, @@ -320,6 +320,7 @@ class SendStatusService : Service(), Injectable { mediaFocus = status.media.map { it.focus }, poll = status.poll, failedToSend = true, + failedToSendAlert = failedToSendAlert, scheduledAt = status.scheduledAt, language = status.language, statusId = status.statusId, diff --git a/app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.kt b/app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.kt index 398359eaf..647e9bf36 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.kt @@ -252,7 +252,7 @@ private fun openLinkInBrowser(uri: Uri?, context: Context) { * @param uri the uri to open * @param context context */ -private fun openLinkInCustomTab(uri: Uri, context: Context) { +fun openLinkInCustomTab(uri: Uri, context: Context) { val toolbarColor = MaterialColors.getColor(context, R.attr.colorSurface, Color.BLACK) val navigationbarColor = MaterialColors.getColor(context, android.R.attr.navigationBarColor, Color.BLACK) val navigationbarDividerColor = MaterialColors.getColor(context, R.attr.dividerColor, Color.BLACK) diff --git a/app/src/main/res/values-cy/strings.xml b/app/src/main/res/values-cy/strings.xml index eacf2d148..f10347385 100644 --- a/app/src/main/res/values-cy/strings.xml +++ b/app/src/main/res/values-cy/strings.xml @@ -3,11 +3,11 @@ Bu gwall. Ni all hwn fod yn wag. Rhoddwyd parth annilys - Methu awdurdodi gyda\'r gweinydd hwnnw. + Methu awdurdodi gyda\'r gweinydd hwnnw. Os bydd hyn yn parhau, ceisiwch Mewngofnodi yn Porwr o\'r ddewislen. Methu dod o hyd i borwr gwe i\'w ddefnyddio. - Bu gwall awdurdodi anhysbys. - Gwrthodwyd awdurdodi. - Methu cael tocyn mewngofnodi. + Bu gwall awdurdodi anhysbys. Os bydd hyn yn parhau, ceisiwch Mewngofnodi yn Porwr o\'r ddewislen. + Gwrthodwyd awdurdodi. Os ydych chi\'n siŵr dy fod di wedi gyflenwi\'r manylion cywir, ceisiwch Mewngofnodi yn Porwr o\'r ddewislen. + Methu cael tocyn mewngofnodi. Os bydd hyn yn parhau, ceisiwch Mewngofnodi yn Porwr o\'r ddewislen. Mae\'ch neges yn rhy hir! Ni allwch lwytho\'r math hwnnw o ffeil. Nid oedd modd agor y ffeil honno. @@ -52,7 +52,7 @@ Ffefryn Mwy Creu - Mewngofnodi â Mastodon + Mewngofnodi â Thusky Allgofnodi Ydych chi\'n siŵr eich bod am allgofnodi o\'r cyfrif %1$s? Dilyn @@ -487,7 +487,7 @@ Adroddodd %s %s %s · %d post wedi\'u hatodi mae yna adroddiad newydd - Torri rheolau + Toriad rheol Sbam Wedi methu pinio Wedi methu dadbinio @@ -657,4 +657,13 @@ Rhannu URL cyfrif i… Rhannu enw denyddiwr cyfrif i… Enw defnyddiwr wedi\'i gopïo + Hynaf yn gyntaf + Diweddaraf yn gyntaf + Trefn darllen + Analluogwyd + <heb ei osod> + <annilys> + Mewngofnodi â phorwr + Yn gweithio yn y rhan mwyaf o achosion. Nid oes unrhyw ddata yn cael ei ollwng i apiau eraill. + Gall gefnogi dulliau dilysu ychwanegol, ond mae angen porwr a gefnogir. diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index ca71032bf..c915346de 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -3,11 +3,11 @@ Errorea gertatu da. Eremu hau ezin da hutsik egon. Domeinu baliogabea sartu da - Akatsa saioa hasterakoan. + Akatsa instantzia horrekin autentikatzerakoan. Akatsak badarrai, menutik, nabigatzailean saioa hasteko aukerarekin saiatu. Ez da web nabigatzailerik aurkitu. - Identifikatu gabeko baimentza akatsa gertatu da. - Akatsa baimentzerakoan. - Akatsa login identifikatzailea lortzerakoan. + Identifikatu gabeko baimen-akatsa gertatu da. Akatsak badarrai, menutik, nabigatzailean saioa hasteko aukerarekin saiatu. + Baimena ukatu da. Ziur bazaude zuk sartutako egiaztagiriak zuzenak direla, menutik, nabigatzailean saioa hasteko aukerarekin saiatu. + Akatsa saio-hasieraren identifikatzailea eskuratzerakoan. Akatsak badarrai, menutik, nabigatzailean saioa hasteko aukerarekin saiatu. Tut luzeegia! Ez da fitxategi mota hau onartzen. Ezin izan da fitxategi hau ireki. @@ -32,18 +32,18 @@ Profila editatu Zirriborroak Lizentziak - %s(e)k bultzatu du - Kontuz edukiarekin - Ezkutuko multimedia + %s-(e)k bultzatu du + Eduki hunkigarria + Ezkutuko media Sakatu ikusteko - Gehiago erakutsi + Gehiago erakutsi Gutxiago erakutsi Zabaldu - Bildu + Itxi Edukirik ez. Arrastatu behera birkargatzeko! %s(e)k zure tuta bultzatu du %s(e)k zure tuta gogoko du - %s(e)k jarraitu zaitu + %s-(e)k jarraitu zaitu \@%s salatu Informazio gehigarria? Erantzun azkarra @@ -97,7 +97,7 @@ Emoji teklatua %1$s jaisten Lotura kopiatu - Tutaren URLa partekatu… + Tutaren URL partekatu… Tuta partekatu… Partekatu media hona… Bidalia! @@ -127,9 +127,9 @@ Mediaren igoera bukatzen Igotzen… Jaitsi - Jarraipen-eskakizunari uko egin\? + Jarraipenaren eskakizunari uko egin\? Kontu hau jarraitzeari utzi\? - Tuta ezabatu\? + Tut hau ezabatu\? Publikoa: Istorio publikoetan erakutsi Ezkutukoa: Ez erakutsi istorio publikoetan Pribatua: Jarraitzaileentzat soilik ikusgai @@ -140,7 +140,7 @@ Soinuarekin jakinarazi Bibrazioarekin jakinarazi Led-arekin jakinarazi - Jakinarazi noiz + Honen arabera jakinarazi Aipatzen naute Jarraitzen didate Bultzatzen naute @@ -154,8 +154,8 @@ Automatikoa Nabigatzailea Chromeko fitxak erabili - Tut egiteko botoia ezkutatu beherantz joaterakoan - Denbora-lerro filtroak + Tut berria idazteko botoia ezkutatu beherantz joan einean + Denbora-lerroaren iragaztea Fitxak Bultzadak erakutsi Erakutsi erantzunak @@ -172,10 +172,10 @@ Publiko Zerrendagabetuta Jarraitzaileak soilik - Status testuaren tamaina - Oso txikia + Tutaren testuaren tamaina + Txikiena Txikia - Erdikoa + Ertaina Handia Handiena Aipamen berriak @@ -186,7 +186,7 @@ Bultzatutako tuten jakinarazpenak Gogokoak Zure tutak gogoko bezala ezartzerakoan jakinarazpenak - %s(e)k aipatu zaitu + %s-(e)k aipatu zaitu %1$s, %2$s, %3$s eta beste %4$d %1$s, %2$s eta %3$s %1$s eta %2$s @@ -211,10 +211,10 @@ Akatsen berri-emateak eta hobekuntza-eskariak: \n https://github.com/accelforce/Yuito/issues Yuitoren profila - Partekatu tutaren edukia - Partekatu tutaren lotura + Tutaren edukia partekatu + Tutaren esteka partekatu Irudiak - Bideoak + Bideoa Eskaera bidalita %du-an @@ -244,18 +244,18 @@ Jarraitzaileak eskuz onartu beharko dituzu Zirriborroa gorde? Tuta bidaltzen… - Errorea tuta bidaltzerakoan + Errorea tuta bidaltzean Tuta bidaltzen - Bidalketa ezeztatua - Tutaren kopia zirriborroetan sartu da + Bidalketa bertan behera utzita + Tutaren kopia bat zure zirriborroetan gorde da Idatzi %s instantziak ez ditu emoji pertsonalizatuak eskaintzen Emojien estiloa Sistema Lehenago jaitsi beharko dituzu Bilatzen… - Tut guztiak ezkutatu/zabaldu - Ireki + Tut guztiak zabaldu/itxi + Tuta ireki Berrabiaraztea beharrezkoa da Aplikazioa berrabiarazi beharko duzu aldaketa ezartzeko Beranduago @@ -281,7 +281,7 @@ Sareko errore bat sortu da! Zure konexioa ziurta ezazu berriro, mesedez! Mezu Zuzenak Fitxak - Lotuta + Finkatua Ezkutuko domeinuak Programatutako tutak \@%s @@ -310,7 +310,7 @@ Media jaisten %s ez dago ezkutatua Tut hau ezabatu eta zirriborro berria egin\? - Ziur al zaude %s ezabatu nahi duzula\? Domeinu horretatik datorren edukia ez duzu denbora-lerro publikoetan edo jakinarazpenetan ikusiko. Domeinu horretan dituzun jarraitzaileak ezabatuko dira. + Ziur al zaude %s-(r)en eduki guztia ezabatu nahi duzula\? Domeinu horretatik datorren edukia ez duzu denbora-lerro publikoetan edo jakinarazpenetan ikusiko. Domeinu horretan dituzun jarraitzaileak ezabatuko dira. Domeinu osoa ezkutatu Galdeketak bukatu dira Iragazkiak @@ -367,12 +367,12 @@ gehienezko %1$d fitxa iritsita Media: %s - Edukiaren abisua: %s + Edukiarekiko abisua: %s Deskribapenik ez - Birblogeatuta - Gogotuta - Publiko - Zerrendagabetuta + Partekatua + Gogokoa + Publikoa + Zerrendatu gabea Jarraitzaileak Zuzena Inkestatu aukerekin: %1$s, %2$s, %3$s, %4$s; %5$s @@ -414,7 +414,7 @@ Iruzkin gehigarriak %s(r)i birbidali Txostena huts egin du - Egoeren eskuratzea huts egin du + Akatsa tutak eskuratzean Txostena zure zerbitzariaren moderatzaileari bidaliko zaio. Jarraian, kontu honen zergatia salatzen duzun azalpena eman dezakezu: Kontua beste zerbitzari batekoa da. Bidali txostenaren kopia anonimatua hara ere\? Kontuak @@ -438,7 +438,7 @@ Laster-markak Ireki bultzadaren egilea Denbora lerro publikoak - Laster-markatuta + Laster-marketara gehitua Aukeratu zerrenda Zerrenda Ez duzu zirriborrorik. @@ -448,8 +448,8 @@ Jarraitzeko eskaereri buruzko jakinarazpenak \@%s isildu\? \@%s blokeatu\? - Mututu elkarrizketa - %s(e)k zu jarraitzeko eskatu dizu + Elkarrizketa mututu + %s-(e)k zu jarraitzeko eskatu zaitu Traolak Jakinarazpenak ezkutatu Desmututu %s @@ -461,13 +461,13 @@ Minutu %d faltan %d minutu faltan - Gehitu traola - Azpia - Goia - Nabigatze posizio nagusia - Erakutsi gradiente koloretsua ezkutuko mediarentzako - jarraipen-eskaera - Desmututu elkarrizketa + Traola gehitu + Beheran + Goian + Nabigazio nagusiaren posizioa + Gradiente koloretsuak erakutsi ezkutuko mediaren lekuan + jarraitzeko eskaera jasotzean + Elkarrizketa desmututu Desmututu %s Jakinarazpenak berrikusi Erakutsi baieztapen elkarrizketa-koadroa gogokoenetara gehitu aurretik @@ -514,4 +514,13 @@ \nPush-jakinarazpenek ez dute eraginik izango, baina jakinarazpenen hobespenak eskuz berrikus ditzakezu. Mezuetan estatistika kuantitatiboak ezkutatu Laster-marka kendu + Ezin izan da irudia editatu. + Bideo eta audio fitxategiek ezin dute %s MBeko tamaina baino handiagoa izan. + Akatsa #%s mututzerakoan + Errorea #%s desmututzerakoan + Instantzia honek traolak jarraitzeko funtzioarekin bateragarritasuna ez dauka. + Akatsa #%s jarraitzerakoan + Akatsa #%s jarraitzerakoan + Ezin izan da saio-hasierako orria kargatu. + Akatsa kontuaren xehetasunak kargatzerakoan diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 64eb01c46..8bc0572f1 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -3,11 +3,11 @@ エラーが発生しました。 本文なしでは投稿できません。 無効なドメインです - そのインスタンスでの認証に失敗しました。 + そのインスタンスでの認証に失敗しました。失敗が続く場合、メニューからブラウザでのログインを試してください。 ウェブブラウザが見つかりませんでした。 - 不明な承認エラーが発生しました。 - 承認が拒否されました。 - ログイントークンの取得に失敗しました。 + 不明な承認エラーが発生しました。失敗が続く場合、メニューからブラウザでのログインを試してください。 + 認証が拒否されました。正しい認証情報を入力したことが確かな場合、メニューからブラウザでのログインを試してください。 + ログイントークンの取得に失敗しました。もし失敗が続く場合、メニューからブラウザでのログインを試してください。 投稿文が長すぎます! その形式のファイルはアップロードできません。 ファイルを開けませんでした。 @@ -58,7 +58,7 @@ 引用 その他 投稿する - Mastodonでログイン + TUsky でログイン ログアウト アカウント %1$s からログアウトしてもよろしいですか? フォローする @@ -592,4 +592,37 @@ アカウントのユーザー名を共有… ユーザー名がコピーされました 簡易投稿欄を表示 + スレッドの読み込み中 + 新しい順 + 読む順番 + 古い順 + サムネイル画像で常に表示される中心点を設定するには、円をタップまたはドラッグして中してくだだい。 + 通知のミュート + %1$s に参加 + %1$s 編集 %2$s + %1$s の投稿 %2$s + 投稿 %s の検索エラー + 中心点の設定に失敗しました + アカウントがロックされていなかったとしても、%1$s のスタッフは以下のアカウントのフォローリクエストを確認した方がいいと判断しました。 + 中心点の設定 + 保存していない変更があります。 + サーバーからステータスの元情報を取得できませんでした。 + 無効 + <設定なし> + <無効> + やり取りした投稿が編集された時 + やり取りした投稿が編集されたときの通知 + ポートは %d から %d の間でなければなりません + アップロードに失敗しました + アップロードに失敗した投稿は下書きに保存されました。 +\n +\nサーバーと接続できなかったか、投稿が拒否されました。 + 下書きを表示 + 閉じる + ブラウザでログイン + ほとんどの場合に動作します。他のアプリにはデータが漏洩しません。 + 追加の認証方法がサポートされる可能性がありますが、対応ブラウザが必要です。 + アップロードに失敗した投稿は下書きに保存されました。 +\n +\nサーバーと接続できなかったか、投稿が拒否されました。 diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml index cd6a6437d..7889e894a 100644 --- a/app/src/main/res/values-lv/strings.xml +++ b/app/src/main/res/values-lv/strings.xml @@ -440,4 +440,62 @@ Slēpt profilu kvantitatīvo statistiku Slēpt ierakstu kvantitatīvo statistiku Neizdevās ielādēt atbildes informāciju + Dzēst un sākt no jauna + Aizvākt + Vai atcelt sekošanas pieprasījumu\? + Vai dzēst šo ierakstu un sākt no jauna\? + Darbību nodrošina Tusky + Meklēt personas, kurām seko + Sūtot ziņu, radās kļūda + %1$s ir pārcēlies uz: + Piespraušana neizdevās + Atspraušana neizdevās + Dalīties ar ieraksta saturu + Pieskaries vai velc apli, lai izvēlētos fokusa punktu, kas vienmēr būs redzams sīktēlos. + Izplest/sakļaut visus ierakstus + Mastodon standarta emocijzīmju komplekts + Google aktuālais emocijzīmju komplekts + Paziņojumi par moderēšanas ziņojumiem + %s pieminēja tevi + %1$s un %2$s + %1$s, %2$s un %3$s + Dalīties ar saiti uz ierakstu + Neizdevās pievienot parakstu + Neizdevās iestatīt fokusa punktu + Izmantot absolūto laiku + %1$s + %1$s un %2$s + Satura brīdinājums: %s + Projekta vietne: +\n https://tusky.app + Ielādē pavedienu + pārtraukta sekošana #%s + %s atcelta slēpšana + Nevarēja izveidot sarakstu + Lasīšanas secība + Filtrējamā frāze + Atspējots + <nav iestatīts> + <nederīgs> + %s (%s) + Pievienot jaunu Mastodon kontu + Animēt pielāgotās emocijzīmes + Jaunu dalībnieku reģistrācija + Kad pievienoti vairāki konti + Nepieciešama lietotnes restartēšana + Pastiprināt sākotnējai auditorijai + Pastiprināts + Atbildot @%s + Prasa manuāli apstiprināt sekotājus + Pastiprināja + + %s pastiprinājumi + %s pastiprinājums + %s pastiprinājumi + + + sasniegts maksimālais ciļņu skaits %1$d + sasniegts maksimālais ciļņu skaits %1$d + sasniegts maksimālais ciļņu skaits %1$d + \ No newline at end of file diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 1329c22fb..ff28b4714 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -60,7 +60,7 @@ Favoriet verwijderen Meer Bericht schrijven - Aanmelden + Aanmelden met Mastodon Afmelden Ben je er zeker van dat je het account %1$s wil afmelden? Volgen @@ -328,14 +328,10 @@ Geen omschrijving Geboost Als favoriet gemarkeerd - Openbaar - - Minder openbaar - - Volgers - - Direct - + Openbaar + Minder openbaar + Volgers + Direct Media downloaden Media aan het downloaden Filters @@ -561,4 +557,39 @@ Fout tijdens het volgen van #%s Fout tijdens het ontvolgen van #%s Dit ingeplande bericht verwijderen\? + Leesvolgorde + Oudste eerst + Nieuwste eerst + Account toevoegen aan de lijst is mislukt + Toevoegen of verwijderen van lijst + Kan niet vastmaken + Uitgeschakeld + Account verwijderen van de lijst is mislukt + Er zijn niet opgeslagen wijzigingen. + Meldingen negeren + Bewerkingen + Standaardtaal van berichten + Rapporten + Bewerkt + %1$s bewerkte %2$s + %1$s maakte %2$s + Door in te loggen ben je het eens met de regels van %s. + Spam + Overig + Media moet een beschrijving hebben. + Kan niet losmaken + %s regels + %s (%s) + Regelovertreding + Je hebt geen lijsten. + nu + ALT + Ontvolg #%s\? + Fout bij negeren #%s + Ga door met bewerken + Er is een nieuw rapport + Nieuw rapport over %s + Gebruikersnaam gekopieerd + #%s ontvolgd + Gevolgde hashtags \ No newline at end of file diff --git a/app/src/main/res/values-oc/strings.xml b/app/src/main/res/values-oc/strings.xml index 4f7034e78..3a07e4915 100644 --- a/app/src/main/res/values-oc/strings.xml +++ b/app/src/main/res/values-oc/strings.xml @@ -601,4 +601,16 @@ Ignorar las modificacions Téner de modificar Avètz de modificacions pas salvadas. + Cargament del fil + Òrdre de lectura + Mai ancians en primièr + Mai recents primièr + Desactivat + <pas definit> + <invalid> + Partejar lo ligam al compte + Partejar lo nom d’utilizaire del compte + Partejar l’URL del compte amb… + Partejar lo nom d’utilizaire del compte amb… + Nom d’utilizaire copiat diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 18eff7575..b0f6d2682 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -3,9 +3,9 @@ Wystąpił błąd. To nie może pozostać puste. Wprowadzono nieprawidłową domenę - Nie udało się uwierzytelnić z tą instancją. + Nie udało się uwierzytelnić z tą instancją. Jeśli problem będzie się powtarzał, spróbuj zalogować się za pomocą przeglądarki z menu aplikacji. Nie znaleziono przeglądarki internetowej. - Wystąpił nieokreślony błąd podczas próby autoryzacji. + Wystąpił nieokreślony błąd podczas próby autoryzacji. Jeśli problem będzie się powtarzał, spróbuj zalogować się za pomocą przeglądarki z menu aplikacji. Odmówiono autoryzacji. Nie udało się uzyskać tokenu logowania. Zbyt długi wpis! @@ -54,7 +54,7 @@ Wyloguj się Czy na pewno chcesz wylogować się z konta %1$s? Obserwuj - Odobserwuj + Przestań śledzić Zablokuj Odblokuj Ukryj podbicia @@ -598,4 +598,29 @@ Czy chcesz zapisać jako szkic\? (Załączniki zostaną załadowane ponownie po przywróceniu szkicu.) Ta instancja nie wspiera obserwowania hashtagów. Obserwowane hashtagi + Ładowanie wątku + Logując się akceptujesz regulamin %s. + Najpierw najstarsze + Najpierw najnowsze + Nie masz żadnych list. + Wycisz powiadomienia + %1$s edytował %2$s + %1$s stworzył %2$s + Edycje + Masz niezapisane zmiany. + %s regulamin + Błąd wysyłania + Pokaż szkice + Odrzuć + ALT + Zaloguj się przez przeglądarkę + Kontynuuj edycję + Spam + Udostępnij link do konta… + Udostępnij nazwę użytkownika… + Skopiowano nazwę użytkownika + Edytowano + Inne + Edytowano %s + Usunąć ten zaplanowany wpis\? \ No newline at end of file diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 204e09717..396dd4ea0 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -20,7 +20,7 @@ Notificações Linha local Linha global - Mensagens Diretas + Mensagens diretas Editar abas Conversa Toots @@ -54,7 +54,7 @@ Desfavoritar Mais Compor - Entrar com Mastodon + Entrar com Tusky Sair Tem certeza de que deseja sair da conta %1$s\? Seguir @@ -518,4 +518,22 @@ 180 dias 14 dias 365 dias + Desabilitado + <não definido> + <inválido> + Sempre + A imagem não pôde ser editada. + Arquivos de vídeo e áudio não podem exceder %s MB de tamanho. + A mídia deve ter uma descrição. + Nunca + ALT + Erro ao silenciar #%s + Entrar com Navegador + Nome de usuário copiado + Hashtags seguidas + Detalhes + Erro ao seguir #%s + Erro ao deixar de seguir #%s + Não foi possível carregar a página de login. + Falha ao carregar detalhes da conta diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 4d12960bb..4e4e4a89a 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -36,7 +36,7 @@ Вийти Чернетки Вподобане - Увійти з Mastodon + Увійти з Tusky Зʼєднання… Немає результатів Пошук… @@ -139,10 +139,10 @@ Тред Вкладки Не вдалося відвантажити. - Не вдалося отримати токен входу. - Авторизацію відхилено. - Сталася помилка невпізнання авторизації. - Помилка автентифікації цього сервера. + Не вдалося отримати токен входу. Якщо проблема не зникає, спробуйте увійти через браузер з меню. + Авторизацію відхилено. Якщо ви впевнені, що вказали правильні облікові дані, спробуйте увійти через браузер з меню. + Сталася помилка невпізнання авторизації. Якщо проблема не зникає, спробуйте увійти через браузер з меню. + Помилка автентифікації цього сервера. Якщо проблема не зникає, спробуйте увійти через браузер з меню. Введено недійсний домен Показати поширення Показати поширення @@ -630,4 +630,16 @@ Вимкнено <не налаштовано> <недійсний> + Увійти через браузер + Працює в більшості випадків. Дані не витікають в інші застосунки. + Може підтримувати додаткові методи автентифікації, але для цього потрібен підтримуваний браузер. + Не вдалося вивантажити + Показати чернетки + Відхилити + Не вдалося вивантажити ваш допис і його було збережено в чернетках. +\n +\nАбо з не вдалося зв\'язатися з сервером, або він відхилив допис. + Не вдалося вивантажити ваш допис і його було збережено в чернетках. +\n +\nАбо з не вдалося зв\'язатися з сервером, або він відхилив допис. \ No newline at end of file diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index a696998e0..677b69026 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -93,7 +93,7 @@ Đăng lại URL tút với… Đăng lại tút với… Đang lưu vào thiết bị - Tải về + Tải xuống Đăng lại với tư cách … Mở với tư cách %s Chép URL @@ -455,7 +455,7 @@ Bỏ ẩn %s Ẩn tiêu đề tab Đã lưu! - Ghi chú + Ghi chú về người này Chưa có thông báo. Có gì mới\? Ẩn số liệu trên trang hồ sơ @@ -571,7 +571,7 @@ Hình ảnh phải có mô tả. Không xác định được trạng thái máy chủ. Cổng nên là khoảng giữa %d đến %d - + Hủy bỏ thay đổi Tiếp tục sửa Thay đổi chưa được lưu. @@ -591,4 +591,16 @@ Tắt <không đặt> <không hợp lệ> + Có thể hỗ trợ các phương thức xác thực bổ sung nhưng yêu cầu trình duyệt được hỗ trợ. + Đăng nhập bằng trình duyệt + Đăng nhập ổn định. Dữ liệu sẽ không bị lộ. + Tải lên thất bại + Tút của bạn không tải lên được và đã được lưu nháp. +\n +\nKhông thể liên lạc được với máy chủ hoặc nó đã từ chối tút. + Tút của bạn không tải lên được và đã được lưu nháp. +\n +\nKhông thể liên lạc được với máy chủ hoặc nó đã từ chối tút. + Xem bản nháp + Bỏ qua \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 51ef3bc77..2f75a2b72 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -4,11 +4,11 @@ 网络请求出错,请检查互联网连接并重试! 内容不能为空。 该域名无效 - 未能通过该实例的身份验证。 + 未能通过该实例的身份验证。如果这个问题持续,请从菜单处尝试“在浏览器中登录”。 找不到可用的浏览器。 - 发生不明授权错误。 - 授权被拒绝。 - 未能获取登录令牌。 + 发生不明授权错误。如果这个问题持续,请从菜单处尝试 “在浏览器中登录”。 + 授权被拒绝。如果你确定提供了正确的凭据,请从菜单处尝试“在浏览器中登录”。 + 未能获取登录令牌。如果这个问题持续,请从菜单处尝试 “在浏览器中登录”。 嘟文太长了! 无法上传此类型的文件。 此文件无法打开。 @@ -60,9 +60,9 @@ 取消喜欢 更多 发表嘟文 - 登录 Mastodon 帐号 + 用 Tusky 登录 注销 - 确定要退出帐号 %1$s 吗? + 确定要退出账号 %1$s 吗? 关注 取消关注 屏蔽 @@ -78,7 +78,7 @@ 关闭 个人资料 设置 - 帐户设置 + 账户设置 喜欢 被隐藏的用户 被屏蔽的用户 @@ -141,9 +141,9 @@ 标题 什么是实例? 正在连接… - 请输入你帐号所在的 Mastodon 站点的域名,比如 mastodon.social,icosahedron.website,social.tchncs.de,等等 。 + 请输入你账号所在的 Mastodon 站点的域名,比如 mastodon.social,icosahedron.website,social.tchncs.de,等等 。 \n -\n还没有 Mastodon 帐号?你也可以输入想注册的 Mastodon 站点的域名,然后在该服务器创建新的帐号并授权 Yuito 登入。 +\n还没有 Mastodon 账号?你也可以输入想注册的 Mastodon 站点的域名,然后在该服务器创建新的账号并授权 Tusky 登入。 \n \n在 Mastodon 里,你的账号信息储存在某一特定实例当中,但 Mastodon 可使跨站互动和站内互动一样简单。 \n @@ -242,7 +242,7 @@ 问题反馈:\n https://github.com/accelforce/Yuito/issues - Yuito 官方帐号 + Yuito 官方账号 分享嘟文内容 分享嘟文链接 图片 @@ -271,8 +271,8 @@ 移除 更新 需要过滤的文字 - 添加帐号 - 添加新的 Mastodon 帐号 + 添加账号 + 添加新的 Mastodon 账号 列表 列表 无法新建列表 @@ -293,7 +293,7 @@ 设置图片标题 移除 - 保护你的帐户(锁嘟) + 保护你的账户(锁嘟) 你需要手动审核所有关注请求 保存为草稿? 正在发送嘟文… @@ -425,8 +425,8 @@ 转发到 %s 举报失败 无法获取嘟文 - 该报告将发送给给您的服务器管理员。您可以在下面提供有关回报此帐户的原因的说明: - 该帐户来自其他服务器。向那里发送一份匿名的报告副本? + 该报告将发送给给您的服务器管理员。您可以在下面提供有关回报此账户的原因的说明: + 该账户来自其他服务器。向那里发送一份匿名的报告副本? 账户 搜索失败 显示通知过滤器 @@ -547,7 +547,7 @@ 取关 #%s 出错 %s (%s) (无更改) - 帖子语言 + 嘟文语言 %s (🔗 %s) 设置焦点失败 设置焦点 @@ -610,4 +610,16 @@ <无效> 从新到旧 从旧到新 + 用浏览器登录 + 多数情况下有效。没有数据泄露给其他应用。 + 可能支持额外的验证方法但需要受支持的浏览器。 + 你的嘟文上传失败,已被保存到草稿。 +\n +\n要么是无法联系服务器,要么是服务器拒绝了它。 + 显示草稿 + 取消 + 上传失败了 + 你的嘟文上传失败,已被保存到草稿。 +\n +\n要么是无法联系服务器,要么是服务器拒绝了它。 diff --git a/app/src/main/res/values-zh-rSG/strings.xml b/app/src/main/res/values-zh-rSG/strings.xml index 0e173e006..2d87640b4 100644 --- a/app/src/main/res/values-zh-rSG/strings.xml +++ b/app/src/main/res/values-zh-rSG/strings.xml @@ -60,9 +60,9 @@ 取消喜欢 更多 新嘟文 - 登录 Mastodon 帐号 + 登录 Mastodon 账号 退出登录 - 确定要退出帐号 %1$s 吗? + 确定要退出账号 %1$s 吗? 关注 取消关注 屏蔽 @@ -141,10 +141,11 @@ 标题 需要帮助? 正在连接… - 请输入你帐号所在的 Mastodon 站点的域名,比如 pawoo.net,acg.mn,wxw.moe,等等 。 - \n\n还没有 Mastodon 帐号?你也可以输入想注册的 Mastodon 站点的域名,然后在该服务器创建新的帐号并授权 Yuito 登入。 - \n\n在 Mastodon 里,跨站互动和站内互动一样简单。可以前往 https://joinmastodon.org 了解更多信息。 - + 请输入你账号所在的 Mastodon 站点的域名,比如 pawoo.net,acg.mn,wxw.moe,等等 。 +\n +\n还没有 Mastodon 账号?你也可以输入想注册的 Mastodon 站点的域名,然后在该服务器创建新的账号并授权 Tusky 登入。 +\n +\n在 Mastodon 里,跨站互动和站内互动一样简单。可以前往 https://joinmastodon.org 了解更多信息。 正在结束上传… 正在上传… 下载 @@ -239,7 +240,7 @@ 问题反馈:\n https://github.com/accelforce/Yuito/issues - Yuito 官方帐号 + Yuito 官方账号 分享嘟文内容 分享嘟文链接 照片 @@ -268,8 +269,8 @@ 移除 更新 需要过滤的文字 - 添加帐号 - 添加新的 Mastodon 帐号 + 添加账号 + 添加新的 Mastodon 账号 列表 列表 无法新建列表 diff --git a/app/src/main/res/values/plurals.xml b/app/src/main/res/values/plurals.xml new file mode 100644 index 000000000..c025d78a7 --- /dev/null +++ b/app/src/main/res/values/plurals.xml @@ -0,0 +1,6 @@ + + + @string/action_post_failed_detail + @string/action_post_failed_detail_plural + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 758a8119a..8a2b664cc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -4,11 +4,11 @@ A network error occurred! Please check your connection and try again! This cannot be empty. Invalid domain entered - Failed authenticating with that instance. + Failed authenticating with that instance. If this persists, try "Login in Browser" from the menu. Couldn\'t find a web browser to use. - An unidentified authorization error occurred. - Authorization was denied. - Failed getting a login token. + An unidentified authorization error occurred. If this persists, try "Login in Browser" from the menu. + Authorization was denied. If you\'re sure that you supplied the correct credentials, try "Login in Browser" from the menu. + Failed getting a login token. If this persists, try "Login in Browser" from the menu. Failed loading account details Could not load the login page. The post is too long! @@ -101,9 +101,15 @@ Remove bookmark More Compose - Log in with Mastodon + Login with Tusky + Login with Browser Log out Are you sure you want to log out of the account %1$s? + Upload failed + Your post failed to upload and has been saved to drafts.\n\nEither the server could not be contacted, or it rejected the post. + Your posts failed to upload and have been saved to drafts.\n\nEither the server could not be contacted, or it rejected the posts. + Show drafts + Dismiss Follow Unfollow Block @@ -593,6 +599,8 @@ Poll with choices: %1$s, %2$s, %3$s, %4$s; %5$s Post language + Works in most cases. No data is leaked to other apps. + May support additional authentication methods, but requires a supported browser. List name diff --git a/app/src/test/java/com/keylesspalace/tusky/MainActivityTest.kt b/app/src/test/java/com/keylesspalace/tusky/MainActivityTest.kt index 05da15075..97e2cf468 100644 --- a/app/src/test/java/com/keylesspalace/tusky/MainActivityTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/MainActivityTest.kt @@ -130,6 +130,7 @@ class MainActivityTest { val viewModel = QuickTootViewModel(mockAccountManager, mock()) activity.eventHub = EventHub() activity.accountManager = mockAccountManager + activity.draftsAlert = mock {} activity.mastodonApi = mock { onBlocking { accountVerifyCredentials() } doReturn NetworkResult.success(account) onBlocking { listAnnouncements(false) } doReturn NetworkResult.success(emptyList()) diff --git a/fastlane/metadata/android/en-US/changelogs/100.txt b/fastlane/metadata/android/en-US/changelogs/100.txt new file mode 100644 index 000000000..d2c450456 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/100.txt @@ -0,0 +1,7 @@ +Tusky 21.0 + +- Support for post editing +- New setting to control preferred reading direction +- Larger media previews and a new overlay to indicate media with description +- It is now possible to add accounts to lists from their profile +and much more \ No newline at end of file diff --git a/fastlane/metadata/android/eu/changelogs/87.txt b/fastlane/metadata/android/eu/changelogs/87.txt new file mode 100644 index 000000000..fbe0eeaa7 --- /dev/null +++ b/fastlane/metadata/android/eu/changelogs/87.txt @@ -0,0 +1,8 @@ +Tusky 16.0 bertsioa + +- Denbora-lerroaren karga-logika guztiz berridatzia izan da, azkarragoa eta mantentzeko errazagoa izateko, baita akats gutxiago eduki dezan ere. +- Orain, Tusky aplikazioak APNG eta animaziodun WebP formatudun emoji pertsonalizatuak anima ditzake. +- Akats-zuzenketa ugari +- Android 11rekin bateragarria +- Itzulpen berriak: Eskoziako gaelera, galiziera eta ukrainera +- Hobetutako itzulpenak diff --git a/fastlane/metadata/android/eu/changelogs/97.txt b/fastlane/metadata/android/eu/changelogs/97.txt new file mode 100644 index 000000000..ae439e0d8 --- /dev/null +++ b/fastlane/metadata/android/eu/changelogs/97.txt @@ -0,0 +1,9 @@ +Tusky 20.0 + +- Aplikaziorako ikono berria Dzuk-en eskutik https://dzuk.zone +- Orain traolak jarrai ditzakezu. Traolan klik egin eta ondoren, tresna-barrako ikonoa. +- Android 13rekin bateragarria +- Bidalketa bat egiterakoan, erabilitako hizkuntza aukera dezakezu +- Profiletako edukien fitxak orain eduki hunkigarria errespetatzen du eta arinago kargatzen da. +- Orain, irudiaren foku-puntua bidali aurretik zehazteko aukera duzu +- Tresna-barran aukera berri bat zure erabiltzaile-izen osoa erakusteko diff --git a/fastlane/metadata/android/uk/changelogs/100.txt b/fastlane/metadata/android/uk/changelogs/100.txt new file mode 100644 index 000000000..845c0a6ec --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/100.txt @@ -0,0 +1,7 @@ +Tusky 21.0 + +- Підтримка редагування дописів +- Нове налаштування для керування бажаним напрямком читання +- Більші прев'ю медіа та новий вигляд позначення медіа з описом +- З'явилася можливість додавати облікові записи до списків з їхнього профілю +та багато іншого diff --git a/fastlane/metadata/android/vi/changelogs/100.txt b/fastlane/metadata/android/vi/changelogs/100.txt new file mode 100644 index 000000000..2a099320e --- /dev/null +++ b/fastlane/metadata/android/vi/changelogs/100.txt @@ -0,0 +1,7 @@ +Tusky 21.0 + +- Hỗ trợ sửa tút +- Thêm cài đặt cách đọc +- Xem trước media và lớp phủ mới để biểu thị phương tiện có mô tả +- Cho phép thêm tài khoản vào danh sách hồ sơ +và còn nữa diff --git a/fastlane/metadata/android/zh-Hans/changelogs/100.txt b/fastlane/metadata/android/zh-Hans/changelogs/100.txt new file mode 100644 index 000000000..bdd784c93 --- /dev/null +++ b/fastlane/metadata/android/zh-Hans/changelogs/100.txt @@ -0,0 +1,7 @@ +Tusky 21.0 + +- 支持编辑嘟文 +- 控制偏首选阅读方向的新设置 +- 支持预览更大的媒体文件以及表明带描述媒体文件的新遮罩 +- 允许从账户的资料页将其添加到列表 +还有其他更多内容有待发现