From d21d045eda16a8daa4c61f6e9b6d4213a8f62532 Mon Sep 17 00:00:00 2001
From: kyori19
Date: Fri, 15 Apr 2022 02:39:30 +0900
Subject: [PATCH 001/104] Support new signup notifications (#2357)
---
.../32.json | 815 ++++++++++++++++++
.../tusky/adapter/NotificationsAdapter.java | 9 +-
.../notifications/NotificationHelper.java | 15 +-
.../NotificationPreferencesFragment.kt | 11 +
.../keylesspalace/tusky/db/AccountEntity.kt | 1 +
.../keylesspalace/tusky/db/AppDatabase.java | 9 +-
.../com/keylesspalace/tusky/di/AppModule.kt | 2 +-
.../tusky/entity/Notification.kt | 6 +-
.../tusky/fragment/NotificationsFragment.java | 10 +-
.../tusky/settings/SettingsConstants.kt | 1 +
app/src/main/res/values/strings.xml | 4 +
11 files changed, 869 insertions(+), 14 deletions(-)
create mode 100644 app/schemas/com.keylesspalace.tusky.db.AppDatabase/32.json
diff --git a/app/schemas/com.keylesspalace.tusky.db.AppDatabase/32.json b/app/schemas/com.keylesspalace.tusky.db.AppDatabase/32.json
new file mode 100644
index 00000000..97ad414e
--- /dev/null
+++ b/app/schemas/com.keylesspalace.tusky.db.AppDatabase/32.json
@@ -0,0 +1,815 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 32,
+ "identityHash": "c92343960c9d46d9cfd49f1873cce47d",
+ "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)",
+ "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
+ }
+ ],
+ "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, `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, `notificationSound` INTEGER NOT NULL, `notificationVibration` INTEGER NOT NULL, `notificationLight` INTEGER NOT NULL, `defaultPostPrivacy` INTEGER NOT NULL, `defaultMediaSensitivity` INTEGER NOT NULL, `alwaysShowSensitiveMedia` INTEGER NOT NULL, `alwaysOpenSpoiler` INTEGER NOT NULL, `mediaPreviewEnabled` INTEGER NOT NULL, `lastNotificationId` TEXT NOT NULL, `activeNotifications` TEXT NOT NULL, `emojis` TEXT NOT NULL, `tabPreferences` TEXT NOT NULL, `notificationsFilter` TEXT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "domain",
+ "columnName": "domain",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "accessToken",
+ "columnName": "accessToken",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isActive",
+ "columnName": "isActive",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "accountId",
+ "columnName": "accountId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "username",
+ "columnName": "username",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "displayName",
+ "columnName": "displayName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "profilePictureUrl",
+ "columnName": "profilePictureUrl",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notificationsEnabled",
+ "columnName": "notificationsEnabled",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notificationsMentioned",
+ "columnName": "notificationsMentioned",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notificationsFollowed",
+ "columnName": "notificationsFollowed",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "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": "notificationSound",
+ "columnName": "notificationSound",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notificationVibration",
+ "columnName": "notificationVibration",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notificationLight",
+ "columnName": "notificationLight",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "defaultPostPrivacy",
+ "columnName": "defaultPostPrivacy",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "defaultMediaSensitivity",
+ "columnName": "defaultMediaSensitivity",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "alwaysShowSensitiveMedia",
+ "columnName": "alwaysShowSensitiveMedia",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "alwaysOpenSpoiler",
+ "columnName": "alwaysOpenSpoiler",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "mediaPreviewEnabled",
+ "columnName": "mediaPreviewEnabled",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastNotificationId",
+ "columnName": "lastNotificationId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "activeNotifications",
+ "columnName": "activeNotifications",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "emojis",
+ "columnName": "emojis",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "tabPreferences",
+ "columnName": "tabPreferences",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notificationsFilter",
+ "columnName": "notificationsFilter",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "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, 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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "instance"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "TimelineStatusEntity",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serverId` TEXT NOT NULL, `url` TEXT, `timelineUserId` INTEGER NOT NULL, `authorServerId` TEXT, `inReplyToId` TEXT, `inReplyToAccountId` TEXT, `content` TEXT, `createdAt` INTEGER NOT NULL, `emojis` TEXT, `reblogsCount` INTEGER NOT NULL, `favouritesCount` INTEGER NOT NULL, `reblogged` INTEGER NOT NULL, `bookmarked` INTEGER NOT NULL, `favourited` INTEGER NOT NULL, `sensitive` INTEGER NOT NULL, `spoilerText` TEXT 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, PRIMARY KEY(`serverId`, `timelineUserId`), FOREIGN KEY(`authorServerId`, `timelineUserId`) REFERENCES `TimelineAccountEntity`(`serverId`, `timelineUserId`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
+ "fields": [
+ {
+ "fieldPath": "serverId",
+ "columnName": "serverId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "url",
+ "columnName": "url",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "timelineUserId",
+ "columnName": "timelineUserId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "authorServerId",
+ "columnName": "authorServerId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "inReplyToId",
+ "columnName": "inReplyToId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "inReplyToAccountId",
+ "columnName": "inReplyToAccountId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "createdAt",
+ "columnName": "createdAt",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "emojis",
+ "columnName": "emojis",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "reblogsCount",
+ "columnName": "reblogsCount",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "favouritesCount",
+ "columnName": "favouritesCount",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "reblogged",
+ "columnName": "reblogged",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "bookmarked",
+ "columnName": "bookmarked",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "favourited",
+ "columnName": "favourited",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "sensitive",
+ "columnName": "sensitive",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "spoilerText",
+ "columnName": "spoilerText",
+ "affinity": "TEXT",
+ "notNull": 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
+ }
+ ],
+ "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, `accounts` TEXT NOT NULL, `unread` INTEGER NOT NULL, `s_id` TEXT NOT NULL, `s_url` TEXT, `s_inReplyToId` TEXT, `s_inReplyToAccountId` TEXT, `s_account` TEXT NOT NULL, `s_content` TEXT NOT NULL, `s_createdAt` INTEGER NOT NULL, `s_emojis` TEXT NOT NULL, `s_favouritesCount` INTEGER NOT NULL, `s_favourited` INTEGER NOT NULL, `s_bookmarked` INTEGER NOT NULL, `s_sensitive` INTEGER NOT NULL, `s_spoilerText` TEXT NOT NULL, `s_attachments` TEXT NOT NULL, `s_mentions` TEXT NOT NULL, `s_tags` TEXT, `s_showingHiddenContent` INTEGER NOT NULL, `s_expanded` INTEGER NOT NULL, `s_collapsible` INTEGER NOT NULL, `s_collapsed` INTEGER NOT NULL, `s_muted` INTEGER NOT NULL, `s_poll` TEXT, PRIMARY KEY(`id`, `accountId`))",
+ "fields": [
+ {
+ "fieldPath": "accountId",
+ "columnName": "accountId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "accounts",
+ "columnName": "accounts",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "unread",
+ "columnName": "unread",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.id",
+ "columnName": "s_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.url",
+ "columnName": "s_url",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastStatus.inReplyToId",
+ "columnName": "s_inReplyToId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastStatus.inReplyToAccountId",
+ "columnName": "s_inReplyToAccountId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastStatus.account",
+ "columnName": "s_account",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.content",
+ "columnName": "s_content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.createdAt",
+ "columnName": "s_createdAt",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.emojis",
+ "columnName": "s_emojis",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.favouritesCount",
+ "columnName": "s_favouritesCount",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.favourited",
+ "columnName": "s_favourited",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.bookmarked",
+ "columnName": "s_bookmarked",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.sensitive",
+ "columnName": "s_sensitive",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.spoilerText",
+ "columnName": "s_spoilerText",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.attachments",
+ "columnName": "s_attachments",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.mentions",
+ "columnName": "s_mentions",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.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.collapsible",
+ "columnName": "s_collapsible",
+ "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
+ }
+ ],
+ "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, 'c92343960c9d46d9cfd49f1873cce47d')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
index 9cef6245..936f20a3 100644
--- a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
+++ b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
@@ -226,7 +226,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
case VIEW_TYPE_FOLLOW: {
if (payloadForHolder == null) {
FollowViewHolder holder = (FollowViewHolder) viewHolder;
- holder.setMessage(concreteNotificaton.getAccount());
+ holder.setMessage(concreteNotificaton.getAccount(), concreteNotificaton.getType() == Notification.Type.SIGN_UP);
holder.setupButtons(notificationActionListener, concreteNotificaton.getAccount().getId());
}
break;
@@ -283,7 +283,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
case REBLOG: {
return VIEW_TYPE_STATUS_NOTIFICATION;
}
- case FOLLOW: {
+ case FOLLOW:
+ case SIGN_UP: {
return VIEW_TYPE_FOLLOW;
}
case FOLLOW_REQUEST: {
@@ -335,10 +336,10 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
this.statusDisplayOptions = statusDisplayOptions;
}
- void setMessage(TimelineAccount account) {
+ void setMessage(TimelineAccount account, Boolean isSignUp) {
Context context = message.getContext();
- String format = context.getString(R.string.notification_follow_format);
+ String format = context.getString(isSignUp ? R.string.notification_sign_up_format : R.string.notification_follow_format);
String wrappedDisplayName = StringUtils.unicodeWrap(account.getName());
String wholeMessage = String.format(format, wrappedDisplayName);
CharSequence emojifiedMessage = CustomEmojiHelper.emojify(
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java
index 6b9afce1..63c17082 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java
+++ b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java
@@ -16,6 +16,8 @@
package com.keylesspalace.tusky.components.notifications;
+import static com.keylesspalace.tusky.viewdata.PollViewDataKt.buildDescription;
+
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
@@ -73,8 +75,6 @@ import java.util.concurrent.TimeUnit;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.schedulers.Schedulers;
-import static com.keylesspalace.tusky.viewdata.PollViewDataKt.buildDescription;
-
public class NotificationHelper {
private static int notificationId = 0;
@@ -116,6 +116,7 @@ public class NotificationHelper {
public static final String CHANNEL_FAVOURITE = "CHANNEL_FAVOURITE";
public static final String CHANNEL_POLL = "CHANNEL_POLL";
public static final String CHANNEL_SUBSCRIPTIONS = "CHANNEL_SUBSCRIPTIONS";
+ public static final String CHANNEL_SIGN_UP = "CHANNEL_SIGN_UP";
/**
* WorkManager Tag
@@ -392,6 +393,7 @@ public class NotificationHelper {
CHANNEL_FAVOURITE + account.getIdentifier(),
CHANNEL_POLL + account.getIdentifier(),
CHANNEL_SUBSCRIPTIONS + account.getIdentifier(),
+ CHANNEL_SIGN_UP + account.getIdentifier(),
};
int[] channelNames = {
R.string.notification_mention_name,
@@ -401,6 +403,7 @@ public class NotificationHelper {
R.string.notification_favourite_name,
R.string.notification_poll_name,
R.string.notification_subscription_name,
+ R.string.notification_sign_up_name,
};
int[] channelDescriptions = {
R.string.notification_mention_descriptions,
@@ -410,6 +413,7 @@ public class NotificationHelper {
R.string.notification_favourite_description,
R.string.notification_poll_description,
R.string.notification_subscription_description,
+ R.string.notification_sign_up_description,
};
List channels = new ArrayList<>(6);
@@ -560,6 +564,8 @@ public class NotificationHelper {
return account.getNotificationsFavorited();
case POLL:
return account.getNotificationsPolls();
+ case SIGN_UP:
+ return account.getNotificationsSignUps();
default:
return false;
}
@@ -582,6 +588,8 @@ public class NotificationHelper {
return CHANNEL_FAVOURITE + account.getIdentifier();
case POLL:
return CHANNEL_POLL + account.getIdentifier();
+ case SIGN_UP:
+ return CHANNEL_SIGN_UP + account.getIdentifier();
default:
return null;
}
@@ -663,6 +671,8 @@ public class NotificationHelper {
} else {
return context.getString(R.string.poll_ended_voted);
}
+ case SIGN_UP:
+ return String.format(context.getString(R.string.notification_sign_up_format), accountName);
}
return null;
}
@@ -671,6 +681,7 @@ public class NotificationHelper {
switch (notification.getType()) {
case FOLLOW:
case FOLLOW_REQUEST:
+ case SIGN_UP:
return "@" + notification.getAccount().getUsername();
case MENTION:
case FAVOURITE:
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/NotificationPreferencesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/NotificationPreferencesFragment.kt
index 4d8ba84f..82ee0a38 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/preference/NotificationPreferencesFragment.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/NotificationPreferencesFragment.kt
@@ -122,6 +122,17 @@ class NotificationPreferencesFragment : PreferenceFragmentCompat(), Injectable {
true
}
}
+
+ switchPreference {
+ setTitle(R.string.pref_title_notification_filter_sign_ups)
+ key = PrefKeys.NOTIFICATION_FILTER_SIGN_UPS
+ isIconSpaceReserved = false
+ isChecked = activeAccount.notificationsSignUps
+ setOnPreferenceChangeListener { _, newValue ->
+ updateAccount { it.notificationsSignUps = newValue as Boolean }
+ true
+ }
+ }
}
preferenceCategory(R.string.pref_title_notification_alerts) { category ->
diff --git a/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt b/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt
index 0c25cbbc..5da91e20 100644
--- a/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt
@@ -50,6 +50,7 @@ data class AccountEntity(
var notificationsFavorited: Boolean = true,
var notificationsPolls: Boolean = true,
var notificationsSubscriptions: Boolean = true,
+ var notificationsSignUps: Boolean = true,
var notificationSound: Boolean = true,
var notificationVibration: Boolean = true,
var notificationLight: Boolean = true,
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 159a6f52..2131300c 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 = 31)
+ }, version = 32)
public abstract class AppDatabase extends RoomDatabase {
public abstract AccountDao accountDao();
@@ -483,4 +483,11 @@ public abstract class AppDatabase extends RoomDatabase {
database.execSQL("DELETE FROM `TimelineStatusEntity`");
}
};
+
+ public static final Migration MIGRATION_31_32 = new Migration(31, 32) {
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `notificationsSignUps` INTEGER NOT NULL DEFAULT 1");
+ }
+ };
}
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 b0f28261..677f8167 100644
--- a/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt
@@ -62,7 +62,7 @@ class AppModule {
AppDatabase.MIGRATION_22_23, AppDatabase.MIGRATION_23_24, AppDatabase.MIGRATION_24_25,
AppDatabase.Migration25_26(appContext.getExternalFilesDir("Tusky")),
AppDatabase.MIGRATION_26_27, AppDatabase.MIGRATION_27_28, AppDatabase.MIGRATION_28_29,
- AppDatabase.MIGRATION_29_30, AppDatabase.MIGRATION_30_31
+ AppDatabase.MIGRATION_29_30, AppDatabase.MIGRATION_30_31, AppDatabase.MIGRATION_31_32,
)
.build()
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Notification.kt b/app/src/main/java/com/keylesspalace/tusky/entity/Notification.kt
index ae2d74a9..ddcf5e61 100644
--- a/app/src/main/java/com/keylesspalace/tusky/entity/Notification.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/entity/Notification.kt
@@ -37,7 +37,9 @@ data class Notification(
FOLLOW("follow"),
FOLLOW_REQUEST("follow_request"),
POLL("poll"),
- STATUS("status");
+ STATUS("status"),
+ SIGN_UP("admin.sign_up"),
+ ;
companion object {
@@ -49,7 +51,7 @@ data class Notification(
}
return UNKNOWN
}
- val asList = listOf(MENTION, REBLOG, FAVOURITE, FOLLOW, FOLLOW_REQUEST, POLL, STATUS)
+ val asList = listOf(MENTION, REBLOG, FAVOURITE, FOLLOW, FOLLOW_REQUEST, POLL, STATUS, SIGN_UP)
}
override fun toString(): String {
diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java
index 467ebc8c..f267f29e 100644
--- a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java
+++ b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java
@@ -15,6 +15,10 @@
package com.keylesspalace.tusky.fragment;
+import static com.keylesspalace.tusky.util.StringUtils.isLessThan;
+import static autodispose2.AutoDispose.autoDisposable;
+import static autodispose2.androidx.lifecycle.AndroidLifecycleScopeProvider.from;
+
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
@@ -111,10 +115,6 @@ import kotlin.Unit;
import kotlin.collections.CollectionsKt;
import kotlin.jvm.functions.Function1;
-import static autodispose2.AutoDispose.autoDisposable;
-import static autodispose2.androidx.lifecycle.AndroidLifecycleScopeProvider.from;
-import static com.keylesspalace.tusky.util.StringUtils.isLessThan;
-
public class NotificationsFragment extends SFragment implements
SwipeRefreshLayout.OnRefreshListener,
StatusActionListener,
@@ -707,6 +707,8 @@ public class NotificationsFragment extends SFragment implements
return getString(R.string.notification_poll_name);
case STATUS:
return getString(R.string.notification_subscription_name);
+ case SIGN_UP:
+ return getString(R.string.notification_sign_up_name);
default:
return "Unknown";
}
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 c59ba58b..c728e1f6 100644
--- a/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt
@@ -59,6 +59,7 @@ object PrefKeys {
const val NOTIFICATION_FILTER_FOLLOW_REQUESTS = "notificationFilterFollowRequests"
const val NOTIFICATIONS_FILTER_FOLLOWS = "notificationFilterFollows"
const val NOTIFICATION_FILTER_SUBSCRIPTIONS = "notificationFilterSubscriptions"
+ const val NOTIFICATION_FILTER_SIGN_UPS = "notificationFilterSignUps"
const val TAB_FILTER_HOME_REPLIES = "tabFilterHomeReplies"
const val TAB_FILTER_HOME_BOOSTS = "tabFilterHomeBoosts"
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e9daafe0..e6ce2338 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -62,6 +62,7 @@
%s favorited your post
%s followed you
%s requested to follow you
+ %s signed up
%s just posted
Report @%s
@@ -228,6 +229,7 @@
my posts are favorited
polls have ended
somebody I\'m subscribed to published a new post
+ somebody signed up
Appearance
App Theme
Timelines
@@ -295,6 +297,8 @@
Notifications about polls that have ended
New posts
Notifications when somebody you\'re subscribed to published a new post
+ Sign ups
+ Notifications about new users
%s mentioned you
%1$s, %2$s, %3$s and %4$d others
From 3e8c6a318a99744e4f7c480fc43c43130d1d1c75 Mon Sep 17 00:00:00 2001
From: Konrad Pozniak
Date: Thu, 14 Apr 2022 19:49:49 +0200
Subject: [PATCH 002/104] introduce KotlinResultCallAdapter for nice suspending
network calls (#2415)
* introduce KotlinResultCallAdapter for nice suspending network calls
* fix tests
---
app/build.gradle | 5 +-
.../com/keylesspalace/tusky/MainActivity.kt | 21 ++--
.../announcements/AnnouncementsViewModel.kt | 6 +-
.../components/compose/ComposeViewModel.kt | 8 +-
.../tusky/components/login/LoginActivity.kt | 95 +++++++++----------
.../scheduled/ScheduledStatusViewModel.kt | 15 +--
.../keylesspalace/tusky/di/NetworkModule.kt | 2 +
.../tusky/network/MastodonApi.kt | 16 ++--
.../tusky/viewmodel/EditProfileViewModel.kt | 87 ++++++++---------
.../tusky/BottomSheetActivityTest.kt | 10 +-
.../tusky/ComposeActivityTest.kt | 57 ++++++-----
.../com/keylesspalace/tusky/FilterTest.kt | 4 +-
.../CachedTimelineRemoteMediatorTest.kt | 6 +-
.../NetworkTimelinePagingSourceTest.kt | 4 +-
.../NetworkTimelineRemoteMediatorTest.kt | 11 +--
15 files changed, 168 insertions(+), 179 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index 645bcb05..f16f9675 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -137,6 +137,7 @@ dependencies {
implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion"
implementation "com.squareup.retrofit2:adapter-rxjava3:$retrofitVersion"
+ implementation "at.connyduck:kotlin-result-calladapter:1.0.0"
implementation "com.squareup.okhttp3:okhttp:$okhttpVersion"
implementation "com.squareup.okhttp3:logging-interceptor:$okhttpVersion"
@@ -176,8 +177,8 @@ dependencies {
testImplementation "androidx.test.ext:junit:1.1.3"
testImplementation "org.robolectric:robolectric:4.4"
- testImplementation "org.mockito:mockito-inline:3.6.28"
- testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
+ testImplementation "org.mockito:mockito-inline:4.4.0"
+ testImplementation "org.mockito.kotlin:mockito-kotlin:4.0.0"
androidTestImplementation "androidx.test.espresso:espresso-core:3.4.0"
androidTestImplementation "androidx.room:room-testing:$roomVersion"
diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt
index 5c7901c5..a934ff9a 100644
--- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt
@@ -682,18 +682,15 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
}
}
- private fun fetchUserInfo() {
- mastodonApi.accountVerifyCredentials()
- .observeOn(AndroidSchedulers.mainThread())
- .autoDispose(this, Lifecycle.Event.ON_DESTROY)
- .subscribe(
- { userInfo ->
- onFetchUserInfoSuccess(userInfo)
- },
- { throwable ->
- Log.e(TAG, "Failed to fetch user info. " + throwable.message)
- }
- )
+ private fun fetchUserInfo() = lifecycleScope.launch {
+ mastodonApi.accountVerifyCredentials().fold(
+ { userInfo ->
+ onFetchUserInfoSuccess(userInfo)
+ },
+ { throwable ->
+ Log.e(TAG, "Failed to fetch user info. " + throwable.message)
+ }
+ )
}
private fun onFetchUserInfoSuccess(me: Account) {
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsViewModel.kt
index d1ae0b9e..10dc303f 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsViewModel.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsViewModel.kt
@@ -35,6 +35,7 @@ import com.keylesspalace.tusky.util.Resource
import com.keylesspalace.tusky.util.RxAwareViewModel
import com.keylesspalace.tusky.util.Success
import io.reactivex.rxjava3.core.Single
+import kotlinx.coroutines.rx3.rxSingle
import javax.inject.Inject
class AnnouncementsViewModel @Inject constructor(
@@ -56,8 +57,9 @@ class AnnouncementsViewModel @Inject constructor(
appDatabase.instanceDao().loadMetadataForInstance(accountManager.activeAccount?.domain!!)
.map> { Either.Left(it) }
.onErrorResumeNext {
- mastodonApi.getInstance()
- .map { Either.Right(it) }
+ rxSingle {
+ mastodonApi.getInstance().getOrThrow()
+ }.map { Either.Right(it) }
}
) { emojis, either ->
either.asLeftOrNull()?.copy(emojiList = emojis)
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt
index 66dacfb4..08df6dc9 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt
@@ -48,6 +48,7 @@ import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.disposables.Disposable
import kotlinx.coroutines.launch
+import kotlinx.coroutines.rx3.rxSingle
import java.util.Locale
import javax.inject.Inject
@@ -105,7 +106,10 @@ class ComposeViewModel @Inject constructor(
init {
Single.zip(
- api.getCustomEmojis(), api.getInstance()
+ api.getCustomEmojis(),
+ rxSingle {
+ api.getInstance().getOrThrow()
+ }
) { emojis, instance ->
InstanceEntity(
instance = accountManager.activeAccount?.domain!!,
@@ -291,7 +295,7 @@ class ComposeViewModel @Inject constructor(
): LiveData {
val deletionObservable = if (isEditingScheduledToot) {
- api.deleteScheduledStatus(scheduledTootId.toString()).toObservable().map { }
+ rxSingle { api.deleteScheduledStatus(scheduledTootId.toString()) }.toObservable().map { }
} else {
Observable.just(Unit)
}.toLiveData()
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 cc2bd776..4df7abc1 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
@@ -33,7 +33,6 @@ import com.keylesspalace.tusky.MainActivity
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.ActivityLoginBinding
import com.keylesspalace.tusky.di.Injectable
-import com.keylesspalace.tusky.entity.AppCredentials
import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.util.getNonNullString
import com.keylesspalace.tusky.util.rickRoll
@@ -166,32 +165,33 @@ class LoginActivity : BaseActivity(), Injectable {
setLoading(true)
lifecycleScope.launch {
- val credentials: AppCredentials = try {
- mastodonApi.authenticateApp(
- domain, getString(R.string.app_name), oauthRedirectUri,
- OAUTH_SCOPES, getString(R.string.tusky_website)
- )
- } catch (e: Exception) {
- binding.loginButton.isEnabled = true
- binding.domainTextInputLayout.error =
- getString(R.string.error_failed_app_registration)
- setLoading(false)
- Log.e(TAG, Log.getStackTraceString(e))
- return@launch
- }
+ mastodonApi.authenticateApp(
+ domain, getString(R.string.app_name), oauthRedirectUri,
+ OAUTH_SCOPES, getString(R.string.tusky_website)
+ ).fold(
+ { credentials ->
+ // Before we open browser page we save the data.
+ // Even if we don't open other apps user may go to password manager or somewhere else
+ // and we will need to pick up the process where we left off.
+ // Alternatively we could pass it all as part of the intent and receive it back
+ // but it is a bit of a workaround.
+ preferences.edit()
+ .putString(DOMAIN, domain)
+ .putString(CLIENT_ID, credentials.clientId)
+ .putString(CLIENT_SECRET, credentials.clientSecret)
+ .apply()
- // Before we open browser page we save the data.
- // Even if we don't open other apps user may go to password manager or somewhere else
- // and we will need to pick up the process where we left off.
- // Alternatively we could pass it all as part of the intent and receive it back
- // but it is a bit of a workaround.
- preferences.edit()
- .putString(DOMAIN, domain)
- .putString(CLIENT_ID, credentials.clientId)
- .putString(CLIENT_SECRET, credentials.clientSecret)
- .apply()
-
- redirectUserToAuthorizeAndLogin(domain, credentials.clientId)
+ redirectUserToAuthorizeAndLogin(domain, credentials.clientId)
+ },
+ { e ->
+ binding.loginButton.isEnabled = true
+ binding.domainTextInputLayout.error =
+ getString(R.string.error_failed_app_registration)
+ setLoading(false)
+ Log.e(TAG, Log.getStackTraceString(e))
+ return@launch
+ }
+ )
}
}
@@ -224,29 +224,28 @@ class LoginActivity : BaseActivity(), Injectable {
setLoading(true)
- val accessToken = try {
- mastodonApi.fetchOAuthToken(
- domain, clientId, clientSecret, oauthRedirectUri, code,
- "authorization_code"
- )
- } catch (e: Exception) {
- setLoading(false)
- binding.domainTextInputLayout.error =
- getString(R.string.error_retrieving_oauth_token)
- Log.e(
- TAG,
- "%s %s".format(getString(R.string.error_retrieving_oauth_token), e.message),
- )
- return
- }
+ mastodonApi.fetchOAuthToken(
+ domain, clientId, clientSecret, oauthRedirectUri, code, "authorization_code"
+ ).fold(
+ { accessToken ->
+ accountManager.addAccount(accessToken.accessToken, domain)
- accountManager.addAccount(accessToken.accessToken, domain)
-
- val intent = Intent(this, MainActivity::class.java)
- intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
- startActivity(intent)
- finish()
- overridePendingTransition(R.anim.explode, R.anim.explode)
+ val intent = Intent(this, MainActivity::class.java)
+ intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+ startActivity(intent)
+ finish()
+ overridePendingTransition(R.anim.explode, R.anim.explode)
+ },
+ { e ->
+ setLoading(false)
+ binding.domainTextInputLayout.error =
+ getString(R.string.error_retrieving_oauth_token)
+ Log.e(
+ TAG,
+ "%s %s".format(getString(R.string.error_retrieving_oauth_token), e.message),
+ )
+ }
+ )
}
private fun setLoading(loadingState: Boolean) {
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledStatusViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledStatusViewModel.kt
index cd3e5ac0..766ed44a 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledStatusViewModel.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledStatusViewModel.kt
@@ -25,7 +25,6 @@ import com.keylesspalace.tusky.appstore.EventHub
import com.keylesspalace.tusky.entity.ScheduledStatus
import com.keylesspalace.tusky.network.MastodonApi
import kotlinx.coroutines.launch
-import kotlinx.coroutines.rx3.await
import javax.inject.Inject
class ScheduledStatusViewModel @Inject constructor(
@@ -43,12 +42,14 @@ class ScheduledStatusViewModel @Inject constructor(
fun deleteScheduledStatus(status: ScheduledStatus) {
viewModelScope.launch {
- try {
- mastodonApi.deleteScheduledStatus(status.id).await()
- pagingSourceFactory.remove(status)
- } catch (throwable: Throwable) {
- Log.w("ScheduledTootViewModel", "Error deleting scheduled status", throwable)
- }
+ mastodonApi.deleteScheduledStatus(status.id).fold(
+ {
+ pagingSourceFactory.remove(status)
+ },
+ { throwable ->
+ Log.w("ScheduledTootViewModel", "Error deleting scheduled status", throwable)
+ }
+ )
}
}
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt
index 7bda6ef7..d927c299 100644
--- a/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt
@@ -19,6 +19,7 @@ import android.content.Context
import android.content.SharedPreferences
import android.os.Build
import android.text.Spanned
+import at.connyduck.calladapter.kotlinresult.KotlinResultCallAdapterFactory
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.keylesspalace.tusky.BuildConfig
@@ -111,6 +112,7 @@ class NetworkModule {
.client(httpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
+ .addCallAdapterFactory(KotlinResultCallAdapterFactory.create())
.build()
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt
index 28d83eca..111cad56 100644
--- a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt
@@ -80,7 +80,7 @@ interface MastodonApi {
fun getCustomEmojis(): Single>
@GET("api/v1/instance")
- fun getInstance(): Single
+ suspend fun getInstance(): Result
@GET("api/v1/filters")
fun getFilters(): Single>
@@ -249,12 +249,12 @@ interface MastodonApi {
): Single>
@DELETE("api/v1/scheduled_statuses/{id}")
- fun deleteScheduledStatus(
+ suspend fun deleteScheduledStatus(
@Path("id") scheduledStatusId: String
- ): Single
+ ): Result
@GET("api/v1/accounts/verify_credentials")
- fun accountVerifyCredentials(): Single
+ suspend fun accountVerifyCredentials(): Result
@FormUrlEncoded
@PATCH("api/v1/accounts/update_credentials")
@@ -265,7 +265,7 @@ interface MastodonApi {
@Multipart
@PATCH("api/v1/accounts/update_credentials")
- fun accountUpdateCredentials(
+ suspend fun accountUpdateCredentials(
@Part(value = "display_name") displayName: RequestBody?,
@Part(value = "note") note: RequestBody?,
@Part(value = "locked") locked: RequestBody?,
@@ -279,7 +279,7 @@ interface MastodonApi {
@Part(value = "fields_attributes[2][value]") fieldValue2: RequestBody?,
@Part(value = "fields_attributes[3][name]") fieldName3: RequestBody?,
@Part(value = "fields_attributes[3][value]") fieldValue3: RequestBody?
- ): Call
+ ): Result
@GET("api/v1/accounts/search")
fun searchAccounts(
@@ -447,7 +447,7 @@ interface MastodonApi {
@Field("redirect_uris") redirectUris: String,
@Field("scopes") scopes: String,
@Field("website") website: String
- ): AppCredentials
+ ): Result
@FormUrlEncoded
@POST("oauth/token")
@@ -458,7 +458,7 @@ interface MastodonApi {
@Field("redirect_uri") redirectUri: String,
@Field("code") code: String,
@Field("grant_type") grantType: String
- ): AccessToken
+ ): Result
@FormUrlEncoded
@POST("api/v1/lists")
diff --git a/app/src/main/java/com/keylesspalace/tusky/viewmodel/EditProfileViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/viewmodel/EditProfileViewModel.kt
index f3539f8d..17aa38c7 100644
--- a/app/src/main/java/com/keylesspalace/tusky/viewmodel/EditProfileViewModel.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/viewmodel/EditProfileViewModel.kt
@@ -20,6 +20,7 @@ import android.net.Uri
import androidx.core.net.toUri
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
import com.keylesspalace.tusky.appstore.EventHub
import com.keylesspalace.tusky.appstore.ProfileEditedEvent
import com.keylesspalace.tusky.entity.Account
@@ -31,8 +32,7 @@ import com.keylesspalace.tusky.util.Loading
import com.keylesspalace.tusky.util.Resource
import com.keylesspalace.tusky.util.Success
import com.keylesspalace.tusky.util.randomAlphanumericString
-import io.reactivex.rxjava3.disposables.CompositeDisposable
-import io.reactivex.rxjava3.kotlin.addTo
+import kotlinx.coroutines.launch
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody
@@ -40,9 +40,7 @@ import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONException
import org.json.JSONObject
-import retrofit2.Call
-import retrofit2.Callback
-import retrofit2.Response
+import retrofit2.HttpException
import java.io.File
import javax.inject.Inject
@@ -63,24 +61,20 @@ class EditProfileViewModel @Inject constructor(
private var oldProfileData: Account? = null
- private val disposables = CompositeDisposable()
-
- fun obtainProfile() {
+ fun obtainProfile() = viewModelScope.launch {
if (profileData.value == null || profileData.value is Error) {
profileData.postValue(Loading())
- mastodonApi.accountVerifyCredentials()
- .subscribe(
- { profile ->
- oldProfileData = profile
- profileData.postValue(Success(profile))
- },
- {
- profileData.postValue(Error())
- }
- )
- .addTo(disposables)
+ mastodonApi.accountVerifyCredentials().fold(
+ { profile ->
+ oldProfileData = profile
+ profileData.postValue(Success(profile))
+ },
+ {
+ profileData.postValue(Error())
+ }
+ )
}
}
@@ -151,34 +145,34 @@ class EditProfileViewModel @Inject constructor(
return
}
- mastodonApi.accountUpdateCredentials(
- displayName, note, locked, avatar, header,
- field1?.first, field1?.second, field2?.first, field2?.second, field3?.first, field3?.second, field4?.first, field4?.second
- ).enqueue(object : Callback {
- override fun onResponse(call: Call, response: Response) {
- val newProfileData = response.body()
- if (!response.isSuccessful || newProfileData == null) {
- val errorResponse = response.errorBody()?.string()
- val errorMsg = if (!errorResponse.isNullOrBlank()) {
- try {
- JSONObject(errorResponse).optString("error", null)
- } catch (e: JSONException) {
+ viewModelScope.launch {
+ mastodonApi.accountUpdateCredentials(
+ displayName, note, locked, avatar, header,
+ field1?.first, field1?.second, field2?.first, field2?.second, field3?.first, field3?.second, field4?.first, field4?.second
+ ).fold(
+ { newProfileData ->
+ saveData.postValue(Success())
+ eventHub.dispatch(ProfileEditedEvent(newProfileData))
+ },
+ { throwable ->
+ if (throwable is HttpException) {
+ val errorResponse = throwable.response()?.errorBody()?.string()
+ val errorMsg = if (!errorResponse.isNullOrBlank()) {
+ try {
+ JSONObject(errorResponse).optString("error", "")
+ } catch (e: JSONException) {
+ null
+ }
+ } else {
null
}
+ saveData.postValue(Error(errorMessage = errorMsg))
} else {
- null
+ saveData.postValue(Error())
}
- saveData.postValue(Error(errorMessage = errorMsg))
- return
}
- saveData.postValue(Success())
- eventHub.dispatch(ProfileEditedEvent(newProfileData))
- }
-
- override fun onFailure(call: Call, t: Throwable) {
- saveData.postValue(Error())
- }
- })
+ )
+ }
}
// cache activity state for rotation change
@@ -208,15 +202,11 @@ class EditProfileViewModel @Inject constructor(
return File(application.cacheDir, filename)
}
- override fun onCleared() {
- disposables.dispose()
- }
-
- fun obtainInstance() {
+ fun obtainInstance() = viewModelScope.launch {
if (instanceData.value == null || instanceData.value is Error) {
instanceData.postValue(Loading())
- mastodonApi.getInstance().subscribe(
+ mastodonApi.getInstance().fold(
{ instance ->
instanceData.postValue(Success(instance))
},
@@ -224,7 +214,6 @@ class EditProfileViewModel @Inject constructor(
instanceData.postValue(Error())
}
)
- .addTo(disposables)
}
}
}
diff --git a/app/src/test/java/com/keylesspalace/tusky/BottomSheetActivityTest.kt b/app/src/test/java/com/keylesspalace/tusky/BottomSheetActivityTest.kt
index ef6d2632..ff208823 100644
--- a/app/src/test/java/com/keylesspalace/tusky/BottomSheetActivityTest.kt
+++ b/app/src/test/java/com/keylesspalace/tusky/BottomSheetActivityTest.kt
@@ -16,15 +16,11 @@
package com.keylesspalace.tusky
import android.text.SpannedString
-import android.widget.LinearLayout
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
-import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.keylesspalace.tusky.entity.SearchResult
import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.entity.TimelineAccount
import com.keylesspalace.tusky.network.MastodonApi
-import com.nhaarman.mockitokotlin2.doReturn
-import com.nhaarman.mockitokotlin2.mock
import io.reactivex.rxjava3.android.plugins.RxAndroidPlugins
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.plugins.RxJavaPlugins
@@ -39,8 +35,8 @@ import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.Mockito.eq
-import org.mockito.Mockito.mock
-import java.util.ArrayList
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
import java.util.Date
import java.util.concurrent.TimeUnit
@@ -306,7 +302,7 @@ class BottomSheetActivityTest {
init {
mastodonApi = api
@Suppress("UNCHECKED_CAST")
- bottomSheet = mock(BottomSheetBehavior::class.java) as BottomSheetBehavior
+ bottomSheet = mock()
}
override fun openLink(url: String) {
diff --git a/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt b/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt
index e7b3a1a9..dc4a412f 100644
--- a/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt
+++ b/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt
@@ -24,8 +24,6 @@ import com.keylesspalace.tusky.components.compose.ComposeActivity
import com.keylesspalace.tusky.components.compose.ComposeViewModel
import com.keylesspalace.tusky.components.compose.DEFAULT_CHARACTER_LIMIT
import com.keylesspalace.tusky.components.compose.DEFAULT_MAXIMUM_URL_LENGTH
-import com.keylesspalace.tusky.components.compose.MediaUploader
-import com.keylesspalace.tusky.components.drafts.DraftHelper
import com.keylesspalace.tusky.db.AccountEntity
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.db.AppDatabase
@@ -37,18 +35,16 @@ import com.keylesspalace.tusky.entity.Instance
import com.keylesspalace.tusky.entity.InstanceConfiguration
import com.keylesspalace.tusky.entity.StatusConfiguration
import com.keylesspalace.tusky.network.MastodonApi
-import com.keylesspalace.tusky.service.ServiceClient
-import com.nhaarman.mockitokotlin2.any
import io.reactivex.rxjava3.core.Single
-import io.reactivex.rxjava3.core.SingleObserver
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito.`when`
-import org.mockito.Mockito.mock
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
import org.robolectric.Robolectric
import org.robolectric.Shadows.shadowOf
import org.robolectric.annotation.Config
@@ -94,44 +90,47 @@ class ComposeActivityTest {
val controller = Robolectric.buildActivity(ComposeActivity::class.java)
activity = controller.get()
- accountManagerMock = mock(AccountManager::class.java)
- `when`(accountManagerMock.activeAccount).thenReturn(account)
+ accountManagerMock = mock {
+ on { activeAccount } doReturn account
+ }
- apiMock = mock(MastodonApi::class.java)
- `when`(apiMock.getCustomEmojis()).thenReturn(Single.just(emptyList()))
- `when`(apiMock.getInstance()).thenReturn(object : Single() {
- override fun subscribeActual(observer: SingleObserver) {
- val instance = instanceResponseCallback?.invoke()
+ apiMock = mock {
+ on { getCustomEmojis() } doReturn Single.just(emptyList())
+ onBlocking { getInstance() } doReturn instanceResponseCallback?.invoke().let { instance ->
if (instance == null) {
- observer.onError(Throwable())
+ Result.failure(Throwable())
} else {
- observer.onSuccess(instance)
+ Result.success(instance)
}
}
- })
+ }
- val instanceDaoMock = mock(InstanceDao::class.java)
- `when`(instanceDaoMock.loadMetadataForInstance(any())).thenReturn(
- Single.just(InstanceEntity(instanceDomain, emptyList(), null, null, null, null, null, null, null))
- )
+ val instanceDaoMock: InstanceDao = mock {
+ on { loadMetadataForInstance(any()) } doReturn
+ Single.just(InstanceEntity(instanceDomain, emptyList(), null, null, null, null, null, null, null))
+ on { loadMetadataForInstance(any()) } doReturn
+ Single.just(InstanceEntity(instanceDomain, emptyList(), null, null, null, null, null, null, null))
+ }
- val dbMock = mock(AppDatabase::class.java)
- `when`(dbMock.instanceDao()).thenReturn(instanceDaoMock)
+ val dbMock: AppDatabase = mock {
+ on { instanceDao() } doReturn instanceDaoMock
+ }
val viewModel = ComposeViewModel(
apiMock,
accountManagerMock,
- mock(MediaUploader::class.java),
- mock(ServiceClient::class.java),
- mock(DraftHelper::class.java),
+ mock(),
+ mock(),
+ mock(),
dbMock
)
activity.intent = Intent(activity, ComposeActivity::class.java).apply {
putExtra(ComposeActivity.COMPOSE_OPTIONS_EXTRA, composeOptions)
}
- val viewModelFactoryMock = mock(ViewModelFactory::class.java)
- `when`(viewModelFactoryMock.create(ComposeViewModel::class.java)).thenReturn(viewModel)
+ val viewModelFactoryMock: ViewModelFactory = mock {
+ on { create(ComposeViewModel::class.java) } doReturn viewModel
+ }
activity.accountManager = accountManagerMock
activity.viewModelFactory = viewModelFactoryMock
@@ -490,7 +489,7 @@ class ComposeActivityTest {
)
}
- fun getCustomInstanceConfiguration(maximumStatusCharacters: Int? = null, charactersReservedPerUrl: Int? = null): InstanceConfiguration {
+ private fun getCustomInstanceConfiguration(maximumStatusCharacters: Int? = null, charactersReservedPerUrl: Int? = null): InstanceConfiguration {
return InstanceConfiguration(
statuses = StatusConfiguration(
maxCharacters = maximumStatusCharacters,
diff --git a/app/src/test/java/com/keylesspalace/tusky/FilterTest.kt b/app/src/test/java/com/keylesspalace/tusky/FilterTest.kt
index 03fff5ee..d5063943 100644
--- a/app/src/test/java/com/keylesspalace/tusky/FilterTest.kt
+++ b/app/src/test/java/com/keylesspalace/tusky/FilterTest.kt
@@ -8,12 +8,12 @@ import com.keylesspalace.tusky.entity.Poll
import com.keylesspalace.tusky.entity.PollOption
import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.network.FilterModel
-import com.nhaarman.mockitokotlin2.mock
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
import org.robolectric.annotation.Config
import java.util.ArrayList
import java.util.Date
@@ -22,7 +22,7 @@ import java.util.Date
@RunWith(AndroidJUnit4::class)
class FilterTest {
- lateinit var filterModel: FilterModel
+ private lateinit var filterModel: FilterModel
@Before
fun setup() {
diff --git a/app/src/test/java/com/keylesspalace/tusky/components/timeline/CachedTimelineRemoteMediatorTest.kt b/app/src/test/java/com/keylesspalace/tusky/components/timeline/CachedTimelineRemoteMediatorTest.kt
index 462b0a4a..2778f8c2 100644
--- a/app/src/test/java/com/keylesspalace/tusky/components/timeline/CachedTimelineRemoteMediatorTest.kt
+++ b/app/src/test/java/com/keylesspalace/tusky/components/timeline/CachedTimelineRemoteMediatorTest.kt
@@ -17,9 +17,6 @@ import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.db.AppDatabase
import com.keylesspalace.tusky.db.Converters
import com.keylesspalace.tusky.db.TimelineStatusWithAccount
-import com.nhaarman.mockitokotlin2.anyOrNull
-import com.nhaarman.mockitokotlin2.doReturn
-import com.nhaarman.mockitokotlin2.mock
import io.reactivex.rxjava3.core.Single
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
@@ -31,6 +28,9 @@ import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
import org.robolectric.Shadows.shadowOf
import org.robolectric.annotation.Config
import retrofit2.HttpException
diff --git a/app/src/test/java/com/keylesspalace/tusky/components/timeline/NetworkTimelinePagingSourceTest.kt b/app/src/test/java/com/keylesspalace/tusky/components/timeline/NetworkTimelinePagingSourceTest.kt
index 2e67c6fe..60dda419 100644
--- a/app/src/test/java/com/keylesspalace/tusky/components/timeline/NetworkTimelinePagingSourceTest.kt
+++ b/app/src/test/java/com/keylesspalace/tusky/components/timeline/NetworkTimelinePagingSourceTest.kt
@@ -3,11 +3,11 @@ package com.keylesspalace.tusky.components.timeline
import androidx.paging.PagingSource
import com.keylesspalace.tusky.components.timeline.viewmodel.NetworkTimelinePagingSource
import com.keylesspalace.tusky.components.timeline.viewmodel.NetworkTimelineViewModel
-import com.nhaarman.mockitokotlin2.doReturn
-import com.nhaarman.mockitokotlin2.mock
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Test
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
class NetworkTimelinePagingSourceTest {
diff --git a/app/src/test/java/com/keylesspalace/tusky/components/timeline/NetworkTimelineRemoteMediatorTest.kt b/app/src/test/java/com/keylesspalace/tusky/components/timeline/NetworkTimelineRemoteMediatorTest.kt
index 74d0fe25..eabf744c 100644
--- a/app/src/test/java/com/keylesspalace/tusky/components/timeline/NetworkTimelineRemoteMediatorTest.kt
+++ b/app/src/test/java/com/keylesspalace/tusky/components/timeline/NetworkTimelineRemoteMediatorTest.kt
@@ -12,11 +12,6 @@ import com.keylesspalace.tusky.components.timeline.viewmodel.NetworkTimelineView
import com.keylesspalace.tusky.db.AccountEntity
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.viewdata.StatusViewData
-import com.nhaarman.mockitokotlin2.anyOrNull
-import com.nhaarman.mockitokotlin2.doReturn
-import com.nhaarman.mockitokotlin2.doThrow
-import com.nhaarman.mockitokotlin2.mock
-import com.nhaarman.mockitokotlin2.verify
import kotlinx.coroutines.runBlocking
import okhttp3.Headers
import okhttp3.ResponseBody.Companion.toResponseBody
@@ -24,6 +19,11 @@ import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.doThrow
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
import org.robolectric.annotation.Config
import retrofit2.HttpException
import retrofit2.Response
@@ -331,7 +331,6 @@ class NetworkTimelineRemoteMediatorTest {
mockStatusViewData("2"),
mockStatusViewData("1"),
)
-
verify(timelineViewModel).nextKey = "0"
assertTrue(result is RemoteMediator.MediatorResult.Success)
assertEquals(false, (result as RemoteMediator.MediatorResult.Success).endOfPaginationReached)
From ad077cf09293ded4db4e9a00e73b6d04a9a20b01 Mon Sep 17 00:00:00 2001
From: Levi Bard
Date: Thu, 14 Apr 2022 19:58:08 +0200
Subject: [PATCH 003/104] Don't show preview cards on statuses with polls.
(#2430)
Fixes #2427
---
.../keylesspalace/tusky/adapter/StatusBaseViewHolder.java | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java
index 1239ea71..dbca518a 100644
--- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java
+++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java
@@ -1043,9 +1043,11 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
StatusDisplayOptions statusDisplayOptions,
final StatusActionListener listener
) {
- final Card card = status.getActionable().getCard();
+ final Status actionable = status.getActionable();
+ final Card card = actionable.getCard();
if (cardViewMode != CardViewMode.NONE &&
- status.getActionable().getAttachments().size() == 0 &&
+ actionable.getAttachments().size() == 0 &&
+ actionable.getPoll() == null &&
card != null &&
!TextUtils.isEmpty(card.getUrl()) &&
(!status.isCollapsible() || !status.isCollapsed())) {
@@ -1067,7 +1069,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
// Statuses from other activitypub sources can be marked sensitive even if there's no media,
// so let's blur the preview in that case
// If media previews are disabled, show placeholder for cards as well
- if (statusDisplayOptions.mediaPreviewEnabled() && !status.getActionable().getSensitive() && !TextUtils.isEmpty(card.getImage())) {
+ if (statusDisplayOptions.mediaPreviewEnabled() && !actionable.getSensitive() && !TextUtils.isEmpty(card.getImage())) {
int topLeftRadius = 0;
int topRightRadius = 0;
From 7aa328b3dced477946ef00dee9e4eef38da18905 Mon Sep 17 00:00:00 2001
From: Konrad Pozniak
Date: Fri, 15 Apr 2022 10:50:28 +0200
Subject: [PATCH 004/104] fix login on Android API level <24 (#2432)
---
.../components/login/LoginWebViewActivity.kt | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/login/LoginWebViewActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/login/LoginWebViewActivity.kt
index 01f6c3b0..827b5620 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/login/LoginWebViewActivity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/login/LoginWebViewActivity.kt
@@ -16,6 +16,7 @@ import android.webkit.WebStorage
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.activity.result.contract.ActivityResultContract
+import androidx.core.net.toUri
import com.keylesspalace.tusky.BaseActivity
import com.keylesspalace.tusky.BuildConfig
import com.keylesspalace.tusky.databinding.LoginWebviewBinding
@@ -103,8 +104,8 @@ class LoginWebViewActivity : BaseActivity(), Injectable {
webView.webViewClient = object : WebViewClient() {
override fun onReceivedError(
- view: WebView?,
- request: WebResourceRequest?,
+ view: WebView,
+ request: WebResourceRequest,
error: WebResourceError
) {
Log.d("LoginWeb", "Failed to load ${data.url}: $error")
@@ -115,7 +116,17 @@ class LoginWebViewActivity : BaseActivity(), Injectable {
view: WebView,
request: WebResourceRequest
): Boolean {
- val url = request.url
+ return shouldOverrideUrlLoading(request.url)
+ }
+
+ /* overriding this deprecated method is necessary for it to work on api levels < 24 */
+ @Suppress("OVERRIDE_DEPRECATION")
+ override fun shouldOverrideUrlLoading(view: WebView?, urlString: String?): Boolean {
+ val url = urlString?.toUri() ?: return false
+ return shouldOverrideUrlLoading(url)
+ }
+
+ fun shouldOverrideUrlLoading(url: Uri): Boolean {
return if (url.scheme == oauthUrl.scheme && url.host == oauthUrl.host) {
val error = url.getQueryParameter("error")
if (error != null) {
@@ -130,6 +141,7 @@ class LoginWebViewActivity : BaseActivity(), Injectable {
}
}
}
+
webView.setBackgroundColor(Color.TRANSPARENT)
if (savedInstanceState == null) {
From ffbc4b64037901a6d257cfbdc6bd09cbfc64ba88 Mon Sep 17 00:00:00 2001
From: Konrad Pozniak
Date: Fri, 15 Apr 2022 11:00:36 +0200
Subject: [PATCH 005/104] upgrade Kotlin Result CallAdapter to v1.0.1 to fix
crash (#2433)
---
app/build.gradle | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/build.gradle b/app/build.gradle
index f16f9675..3c5cb162 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -137,7 +137,7 @@ dependencies {
implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion"
implementation "com.squareup.retrofit2:adapter-rxjava3:$retrofitVersion"
- implementation "at.connyduck:kotlin-result-calladapter:1.0.0"
+ implementation "at.connyduck:kotlin-result-calladapter:1.0.1"
implementation "com.squareup.okhttp3:okhttp:$okhttpVersion"
implementation "com.squareup.okhttp3:logging-interceptor:$okhttpVersion"
From 3e849244f9d1c77eee4614193832f9b263961ab0 Mon Sep 17 00:00:00 2001
From: Konrad Pozniak
Date: Fri, 15 Apr 2022 13:20:27 +0200
Subject: [PATCH 006/104] move Html parsing to ViewData (#2414)
* move Html parsing to ViewData
* refactor reports to use viewdata
* cleanup code
* refactor conversations
* fix getEditableText
* rename StatusParsingHelper
* fix tests
* commit db schema file
* add file header
* rename helper function to parseAsMastodonHtml
* order imports correctly
* move mapping off main thread to default dispatcher
* fix ktlint
---
.../33.json | 809 ++++++++++++++++++
.../components/account/AccountActivity.kt | 24 +-
.../components/account/AccountFieldAdapter.kt | 3 +-
.../conversation/ConversationAdapter.kt | 12 +-
.../conversation/ConversationEntity.kt | 133 +--
.../conversation/ConversationViewData.kt | 87 ++
.../conversation/ConversationViewHolder.java | 20 +-
.../conversation/ConversationsFragment.kt | 30 +-
.../conversation/ConversationsViewModel.kt | 79 +-
.../components/report/ReportViewModel.kt | 12 +-
.../report/adapter/StatusViewHolder.kt | 58 +-
.../report/adapter/StatusesAdapter.kt | 12 +-
.../timeline/TimelineTypeMappers.kt | 16 +-
.../viewmodel/CachedTimelineViewModel.kt | 11 +-
.../viewmodel/NetworkTimelineViewModel.kt | 6 +-
.../keylesspalace/tusky/db/AppDatabase.java | 39 +-
.../tusky/db/ConversationsDao.kt | 5 +-
.../com/keylesspalace/tusky/db/Converters.kt | 21 -
.../com/keylesspalace/tusky/di/AppModule.kt | 1 +
.../keylesspalace/tusky/di/NetworkModule.kt | 9 +-
.../com/keylesspalace/tusky/entity/Account.kt | 55 +-
.../tusky/entity/Announcement.kt | 3 +-
.../com/keylesspalace/tusky/entity/Card.kt | 9 +-
.../com/keylesspalace/tusky/entity/Status.kt | 74 +-
.../tusky/json/SpannedTypeAdapter.kt | 54 --
.../tusky/util/StatusParsingHelper.kt | 62 ++
.../keylesspalace/tusky/util/ViewDataUtils.kt | 3 -
.../tusky/viewdata/StatusViewData.kt | 53 +-
.../tusky/BottomSheetActivityTest.kt | 3 +-
.../tusky/ComposeActivityTest.kt | 3 +-
.../com/keylesspalace/tusky/FilterTest.kt | 3 +-
.../tusky/StatusComparisonTest.kt | 14 +-
.../NetworkTimelinePagingSourceTest.kt | 5 +
.../tusky/components/timeline/StatusMocker.kt | 4 +-
34 files changed, 1232 insertions(+), 500 deletions(-)
create mode 100644 app/schemas/com.keylesspalace.tusky.db.AppDatabase/33.json
create mode 100644 app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewData.kt
delete mode 100644 app/src/main/java/com/keylesspalace/tusky/json/SpannedTypeAdapter.kt
create mode 100644 app/src/main/java/com/keylesspalace/tusky/util/StatusParsingHelper.kt
diff --git a/app/schemas/com.keylesspalace.tusky.db.AppDatabase/33.json b/app/schemas/com.keylesspalace.tusky.db.AppDatabase/33.json
new file mode 100644
index 00000000..e6d8ec7d
--- /dev/null
+++ b/app/schemas/com.keylesspalace.tusky.db.AppDatabase/33.json
@@ -0,0 +1,809 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 33,
+ "identityHash": "920a0e0c9a600bd236f6bf959b469c18",
+ "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)",
+ "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
+ }
+ ],
+ "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, `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, `notificationSound` INTEGER NOT NULL, `notificationVibration` INTEGER NOT NULL, `notificationLight` INTEGER NOT NULL, `defaultPostPrivacy` INTEGER NOT NULL, `defaultMediaSensitivity` INTEGER NOT NULL, `alwaysShowSensitiveMedia` INTEGER NOT NULL, `alwaysOpenSpoiler` INTEGER NOT NULL, `mediaPreviewEnabled` INTEGER NOT NULL, `lastNotificationId` TEXT NOT NULL, `activeNotifications` TEXT NOT NULL, `emojis` TEXT NOT NULL, `tabPreferences` TEXT NOT NULL, `notificationsFilter` TEXT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "domain",
+ "columnName": "domain",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "accessToken",
+ "columnName": "accessToken",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isActive",
+ "columnName": "isActive",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "accountId",
+ "columnName": "accountId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "username",
+ "columnName": "username",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "displayName",
+ "columnName": "displayName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "profilePictureUrl",
+ "columnName": "profilePictureUrl",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notificationsEnabled",
+ "columnName": "notificationsEnabled",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notificationsMentioned",
+ "columnName": "notificationsMentioned",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notificationsFollowed",
+ "columnName": "notificationsFollowed",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "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": "notificationSound",
+ "columnName": "notificationSound",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notificationVibration",
+ "columnName": "notificationVibration",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notificationLight",
+ "columnName": "notificationLight",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "defaultPostPrivacy",
+ "columnName": "defaultPostPrivacy",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "defaultMediaSensitivity",
+ "columnName": "defaultMediaSensitivity",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "alwaysShowSensitiveMedia",
+ "columnName": "alwaysShowSensitiveMedia",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "alwaysOpenSpoiler",
+ "columnName": "alwaysOpenSpoiler",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "mediaPreviewEnabled",
+ "columnName": "mediaPreviewEnabled",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastNotificationId",
+ "columnName": "lastNotificationId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "activeNotifications",
+ "columnName": "activeNotifications",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "emojis",
+ "columnName": "emojis",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "tabPreferences",
+ "columnName": "tabPreferences",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notificationsFilter",
+ "columnName": "notificationsFilter",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "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, 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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "instance"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "TimelineStatusEntity",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serverId` TEXT NOT NULL, `url` TEXT, `timelineUserId` INTEGER NOT NULL, `authorServerId` TEXT, `inReplyToId` TEXT, `inReplyToAccountId` TEXT, `content` TEXT, `createdAt` INTEGER NOT NULL, `emojis` TEXT, `reblogsCount` INTEGER NOT NULL, `favouritesCount` INTEGER NOT NULL, `reblogged` INTEGER NOT NULL, `bookmarked` INTEGER NOT NULL, `favourited` INTEGER NOT NULL, `sensitive` INTEGER NOT NULL, `spoilerText` TEXT 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, PRIMARY KEY(`serverId`, `timelineUserId`), FOREIGN KEY(`authorServerId`, `timelineUserId`) REFERENCES `TimelineAccountEntity`(`serverId`, `timelineUserId`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
+ "fields": [
+ {
+ "fieldPath": "serverId",
+ "columnName": "serverId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "url",
+ "columnName": "url",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "timelineUserId",
+ "columnName": "timelineUserId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "authorServerId",
+ "columnName": "authorServerId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "inReplyToId",
+ "columnName": "inReplyToId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "inReplyToAccountId",
+ "columnName": "inReplyToAccountId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "createdAt",
+ "columnName": "createdAt",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "emojis",
+ "columnName": "emojis",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "reblogsCount",
+ "columnName": "reblogsCount",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "favouritesCount",
+ "columnName": "favouritesCount",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "reblogged",
+ "columnName": "reblogged",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "bookmarked",
+ "columnName": "bookmarked",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "favourited",
+ "columnName": "favourited",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "sensitive",
+ "columnName": "sensitive",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "spoilerText",
+ "columnName": "spoilerText",
+ "affinity": "TEXT",
+ "notNull": 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
+ }
+ ],
+ "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, `accounts` TEXT NOT NULL, `unread` INTEGER NOT NULL, `s_id` TEXT NOT NULL, `s_url` TEXT, `s_inReplyToId` TEXT, `s_inReplyToAccountId` TEXT, `s_account` TEXT NOT NULL, `s_content` TEXT NOT NULL, `s_createdAt` INTEGER NOT NULL, `s_emojis` TEXT NOT NULL, `s_favouritesCount` INTEGER NOT NULL, `s_favourited` INTEGER NOT NULL, `s_bookmarked` INTEGER NOT NULL, `s_sensitive` INTEGER NOT NULL, `s_spoilerText` TEXT NOT NULL, `s_attachments` TEXT NOT NULL, `s_mentions` TEXT NOT NULL, `s_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, PRIMARY KEY(`id`, `accountId`))",
+ "fields": [
+ {
+ "fieldPath": "accountId",
+ "columnName": "accountId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "accounts",
+ "columnName": "accounts",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "unread",
+ "columnName": "unread",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.id",
+ "columnName": "s_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.url",
+ "columnName": "s_url",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastStatus.inReplyToId",
+ "columnName": "s_inReplyToId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastStatus.inReplyToAccountId",
+ "columnName": "s_inReplyToAccountId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastStatus.account",
+ "columnName": "s_account",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.content",
+ "columnName": "s_content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.createdAt",
+ "columnName": "s_createdAt",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.emojis",
+ "columnName": "s_emojis",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.favouritesCount",
+ "columnName": "s_favouritesCount",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.favourited",
+ "columnName": "s_favourited",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.bookmarked",
+ "columnName": "s_bookmarked",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.sensitive",
+ "columnName": "s_sensitive",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.spoilerText",
+ "columnName": "s_spoilerText",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.attachments",
+ "columnName": "s_attachments",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.mentions",
+ "columnName": "s_mentions",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.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
+ }
+ ],
+ "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, '920a0e0c9a600bd236f6bf959b469c18')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt
index c218e111..114a6cd0 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt
@@ -78,7 +78,7 @@ import com.keylesspalace.tusky.util.emojify
import com.keylesspalace.tusky.util.getDomain
import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.loadAvatar
-import com.keylesspalace.tusky.util.openLink
+import com.keylesspalace.tusky.util.parseAsMastodonHtml
import com.keylesspalace.tusky.util.setClickableText
import com.keylesspalace.tusky.util.show
import com.keylesspalace.tusky.util.viewBinding
@@ -375,12 +375,11 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
}
}
viewModel.accountFieldData.observe(
- this,
- {
- accountFieldAdapter.fields = it
- accountFieldAdapter.notifyDataSetChanged()
- }
- )
+ this
+ ) {
+ accountFieldAdapter.fields = it
+ accountFieldAdapter.notifyDataSetChanged()
+ }
viewModel.noteSaved.observe(this) {
binding.saveNoteInfo.visible(it, View.INVISIBLE)
}
@@ -395,11 +394,10 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
adapter.refreshContent()
}
viewModel.isRefreshing.observe(
- this,
- { isRefreshing ->
- binding.swipeToRefreshLayout.isRefreshing = isRefreshing == true
- }
- )
+ this
+ ) { isRefreshing ->
+ binding.swipeToRefreshLayout.isRefreshing = isRefreshing == true
+ }
binding.swipeToRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
}
@@ -410,7 +408,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
binding.accountUsernameTextView.text = usernameFormatted
binding.accountDisplayNameTextView.text = account.name.emojify(account.emojis, binding.accountDisplayNameTextView, animateEmojis)
- val emojifiedNote = account.note.emojify(account.emojis, binding.accountNoteTextView, animateEmojis)
+ val emojifiedNote = account.note.parseAsMastodonHtml().emojify(account.emojis, binding.accountNoteTextView, animateEmojis)
setClickableText(binding.accountNoteTextView, emojifiedNote, emptyList(), null, this)
// accountFieldAdapter.fields = account.fields ?: emptyList()
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/account/AccountFieldAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/account/AccountFieldAdapter.kt
index 093dbcfb..d51bb145 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/account/AccountFieldAdapter.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/account/AccountFieldAdapter.kt
@@ -29,6 +29,7 @@ import com.keylesspalace.tusky.util.BindingHolder
import com.keylesspalace.tusky.util.Either
import com.keylesspalace.tusky.util.createClickableText
import com.keylesspalace.tusky.util.emojify
+import com.keylesspalace.tusky.util.parseAsMastodonHtml
import com.keylesspalace.tusky.util.setClickableText
class AccountFieldAdapter(
@@ -65,7 +66,7 @@ class AccountFieldAdapter(
val emojifiedName = field.name.emojify(emojis, nameTextView, animateEmojis)
nameTextView.text = emojifiedName
- val emojifiedValue = field.value.emojify(emojis, valueTextView, animateEmojis)
+ val emojifiedValue = field.value.parseAsMastodonHtml().emojify(emojis, valueTextView, animateEmojis)
setClickableText(valueTextView, emojifiedValue, emptyList(), null, linkListener)
if (field.verifiedAt != null) {
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationAdapter.kt
index 89c1ad0f..0c946514 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationAdapter.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationAdapter.kt
@@ -26,7 +26,7 @@ import com.keylesspalace.tusky.util.StatusDisplayOptions
class ConversationAdapter(
private val statusDisplayOptions: StatusDisplayOptions,
private val listener: StatusActionListener
-) : PagingDataAdapter(CONVERSATION_COMPARATOR) {
+) : PagingDataAdapter(CONVERSATION_COMPARATOR) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ConversationViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_conversation, parent, false)
@@ -37,17 +37,13 @@ class ConversationAdapter(
holder.setupWithConversation(getItem(position))
}
- fun item(position: Int): ConversationEntity? {
- return getItem(position)
- }
-
companion object {
- val CONVERSATION_COMPARATOR = object : DiffUtil.ItemCallback() {
- override fun areItemsTheSame(oldItem: ConversationEntity, newItem: ConversationEntity): Boolean {
+ val CONVERSATION_COMPARATOR = object : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(oldItem: ConversationViewData, newItem: ConversationViewData): Boolean {
return oldItem.id == newItem.id
}
- override fun areContentsTheSame(oldItem: ConversationEntity, newItem: ConversationEntity): Boolean {
+ override fun areContentsTheSame(oldItem: ConversationViewData, newItem: ConversationViewData): Boolean {
return oldItem == newItem
}
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationEntity.kt b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationEntity.kt
index 88c9dbad..f585b4ea 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationEntity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationEntity.kt
@@ -15,7 +15,6 @@
package com.keylesspalace.tusky.components.conversation
-import android.text.Spanned
import androidx.room.Embedded
import androidx.room.Entity
import androidx.room.TypeConverters
@@ -27,7 +26,7 @@ import com.keylesspalace.tusky.entity.HashTag
import com.keylesspalace.tusky.entity.Poll
import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.entity.TimelineAccount
-import com.keylesspalace.tusky.util.shouldTrimStatus
+import com.keylesspalace.tusky.viewdata.StatusViewData
import java.util.Date
@Entity(primaryKeys = ["id", "accountId"])
@@ -38,7 +37,16 @@ data class ConversationEntity(
val accounts: List,
val unread: Boolean,
@Embedded(prefix = "s_") val lastStatus: ConversationStatusEntity
-)
+) {
+ fun toViewData(): ConversationViewData {
+ return ConversationViewData(
+ id = id,
+ accounts = accounts,
+ unread = unread,
+ lastStatus = lastStatus.toViewData()
+ )
+ }
+}
data class ConversationAccountEntity(
val id: String,
@@ -67,7 +75,7 @@ data class ConversationStatusEntity(
val inReplyToId: String?,
val inReplyToAccountId: String?,
val account: ConversationAccountEntity,
- val content: Spanned,
+ val content: String,
val createdAt: Date,
val emojis: List,
val favouritesCount: Int,
@@ -80,95 +88,43 @@ data class ConversationStatusEntity(
val tags: List?,
val showingHiddenContent: Boolean,
val expanded: Boolean,
- val collapsible: Boolean,
val collapsed: Boolean,
val muted: Boolean,
val poll: Poll?
) {
- /** its necessary to override this because Spanned.equals does not work as expected */
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (javaClass != other?.javaClass) return false
- other as ConversationStatusEntity
-
- if (id != other.id) return false
- if (url != other.url) return false
- if (inReplyToId != other.inReplyToId) return false
- if (inReplyToAccountId != other.inReplyToAccountId) return false
- if (account != other.account) return false
- if (content.toString() != other.content.toString()) return false
- if (createdAt != other.createdAt) return false
- if (emojis != other.emojis) return false
- if (favouritesCount != other.favouritesCount) return false
- if (favourited != other.favourited) return false
- if (sensitive != other.sensitive) return false
- if (spoilerText != other.spoilerText) return false
- if (attachments != other.attachments) return false
- if (mentions != other.mentions) return false
- if (tags != other.tags) return false
- if (showingHiddenContent != other.showingHiddenContent) return false
- if (expanded != other.expanded) return false
- if (collapsible != other.collapsible) return false
- if (collapsed != other.collapsed) return false
- if (muted != other.muted) return false
- if (poll != other.poll) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = id.hashCode()
- result = 31 * result + (url?.hashCode() ?: 0)
- result = 31 * result + (inReplyToId?.hashCode() ?: 0)
- result = 31 * result + (inReplyToAccountId?.hashCode() ?: 0)
- result = 31 * result + account.hashCode()
- result = 31 * result + content.toString().hashCode()
- result = 31 * result + createdAt.hashCode()
- result = 31 * result + emojis.hashCode()
- result = 31 * result + favouritesCount
- result = 31 * result + favourited.hashCode()
- result = 31 * result + sensitive.hashCode()
- result = 31 * result + spoilerText.hashCode()
- result = 31 * result + attachments.hashCode()
- result = 31 * result + mentions.hashCode()
- result = 31 * result + tags.hashCode()
- result = 31 * result + showingHiddenContent.hashCode()
- result = 31 * result + expanded.hashCode()
- result = 31 * result + collapsible.hashCode()
- result = 31 * result + collapsed.hashCode()
- result = 31 * result + muted.hashCode()
- result = 31 * result + poll.hashCode()
- return result
- }
-
- fun toStatus(): Status {
- return Status(
- id = id,
- url = url,
- account = account.toAccount(),
- inReplyToId = inReplyToId,
- inReplyToAccountId = inReplyToAccountId,
- content = content,
- reblog = null,
- createdAt = createdAt,
- emojis = emojis,
- reblogsCount = 0,
- favouritesCount = favouritesCount,
- reblogged = false,
- favourited = favourited,
- bookmarked = bookmarked,
- sensitive = sensitive,
- spoilerText = spoilerText,
- visibility = Status.Visibility.DIRECT,
- attachments = attachments,
- mentions = mentions,
- tags = tags,
- application = null,
- pinned = false,
- muted = muted,
- poll = poll,
- card = null
+ fun toViewData(): StatusViewData.Concrete {
+ return StatusViewData.Concrete(
+ status = Status(
+ id = id,
+ url = url,
+ account = account.toAccount(),
+ inReplyToId = inReplyToId,
+ inReplyToAccountId = inReplyToAccountId,
+ content = content,
+ reblog = null,
+ createdAt = createdAt,
+ emojis = emojis,
+ reblogsCount = 0,
+ favouritesCount = favouritesCount,
+ reblogged = false,
+ favourited = favourited,
+ bookmarked = bookmarked,
+ sensitive = sensitive,
+ spoilerText = spoilerText,
+ visibility = Status.Visibility.DIRECT,
+ attachments = attachments,
+ mentions = mentions,
+ tags = tags,
+ application = null,
+ pinned = false,
+ muted = muted,
+ poll = poll,
+ card = null
+ ),
+ isExpanded = expanded,
+ isShowingContent = showingHiddenContent,
+ isCollapsed = collapsed
)
}
}
@@ -202,7 +158,6 @@ fun Status.toEntity() =
tags = tags,
showingHiddenContent = false,
expanded = false,
- collapsible = shouldTrimStatus(content),
collapsed = true,
muted = muted ?: false,
poll = poll
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewData.kt b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewData.kt
new file mode 100644
index 00000000..470675d1
--- /dev/null
+++ b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewData.kt
@@ -0,0 +1,87 @@
+/* Copyright 2022 Tusky Contributors
+ *
+ * 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.components.conversation
+
+import com.keylesspalace.tusky.entity.Poll
+import com.keylesspalace.tusky.viewdata.StatusViewData
+
+data class ConversationViewData(
+ val id: String,
+ val accounts: List,
+ val unread: Boolean,
+ val lastStatus: StatusViewData.Concrete
+) {
+ fun toEntity(
+ accountId: Long,
+ favourited: Boolean = lastStatus.status.favourited,
+ bookmarked: Boolean = lastStatus.status.bookmarked,
+ muted: Boolean = lastStatus.status.muted ?: false,
+ poll: Poll? = lastStatus.status.poll,
+ expanded: Boolean = lastStatus.isExpanded,
+ collapsed: Boolean = lastStatus.isCollapsed,
+ showingHiddenContent: Boolean = lastStatus.isShowingContent
+ ): ConversationEntity {
+ return ConversationEntity(
+ accountId = accountId,
+ id = id,
+ accounts = accounts,
+ unread = unread,
+ lastStatus = lastStatus.toConversationStatusEntity(
+ favourited = favourited,
+ bookmarked = bookmarked,
+ muted = muted,
+ poll = poll,
+ expanded = expanded,
+ collapsed = collapsed,
+ showingHiddenContent = showingHiddenContent
+ )
+ )
+ }
+}
+
+fun StatusViewData.Concrete.toConversationStatusEntity(
+ favourited: Boolean = status.favourited,
+ bookmarked: Boolean = status.bookmarked,
+ muted: Boolean = status.muted ?: false,
+ poll: Poll? = status.poll,
+ expanded: Boolean = isExpanded,
+ collapsed: Boolean = isCollapsed,
+ showingHiddenContent: Boolean = isShowingContent
+): ConversationStatusEntity {
+ return ConversationStatusEntity(
+ id = id,
+ url = status.url,
+ inReplyToId = status.inReplyToId,
+ inReplyToAccountId = status.inReplyToAccountId,
+ account = status.account.toEntity(),
+ content = status.content,
+ createdAt = status.createdAt,
+ emojis = status.emojis,
+ favouritesCount = status.favouritesCount,
+ favourited = favourited,
+ bookmarked = bookmarked,
+ sensitive = status.sensitive,
+ spoilerText = status.spoilerText,
+ attachments = status.attachments,
+ mentions = status.mentions,
+ tags = status.tags,
+ showingHiddenContent = showingHiddenContent,
+ expanded = expanded,
+ collapsed = collapsed,
+ muted = muted,
+ poll = poll
+ )
+}
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java
index 436ba84e..ffb88a94 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java
+++ b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java
@@ -28,11 +28,14 @@ import androidx.recyclerview.widget.RecyclerView;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.adapter.StatusBaseViewHolder;
import com.keylesspalace.tusky.entity.Attachment;
+import com.keylesspalace.tusky.entity.Status;
+import com.keylesspalace.tusky.entity.TimelineAccount;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.util.ImageLoadingHelper;
import com.keylesspalace.tusky.util.SmartLengthInputFilter;
import com.keylesspalace.tusky.util.StatusDisplayOptions;
import com.keylesspalace.tusky.viewdata.PollViewDataKt;
+import com.keylesspalace.tusky.viewdata.StatusViewData;
import java.util.List;
@@ -69,11 +72,12 @@ public class ConversationViewHolder extends StatusBaseViewHolder {
return context.getResources().getDimensionPixelSize(R.dimen.status_media_preview_height);
}
- void setupWithConversation(ConversationEntity conversation) {
- ConversationStatusEntity status = conversation.getLastStatus();
- ConversationAccountEntity account = status.getAccount();
+ void setupWithConversation(ConversationViewData conversation) {
+ StatusViewData.Concrete statusViewData = conversation.getLastStatus();
+ Status status = statusViewData.getStatus();
+ TimelineAccount account = status.getAccount();
- setupCollapsedState(status.getCollapsible(), status.getCollapsed(), status.getExpanded(), status.getSpoilerText(), listener);
+ setupCollapsedState(statusViewData.isCollapsible(), statusViewData.isCollapsed(), statusViewData.isExpanded(), statusViewData.getSpoilerText(), listener);
setDisplayName(account.getDisplayName(), account.getEmojis(), statusDisplayOptions);
setUsername(account.getUsername());
@@ -84,7 +88,7 @@ public class ConversationViewHolder extends StatusBaseViewHolder {
List attachments = status.getAttachments();
boolean sensitive = status.getSensitive();
if (statusDisplayOptions.mediaPreviewEnabled() && hasPreviewableAttachment(attachments)) {
- setMediaPreviews(attachments, sensitive, listener, status.getShowingHiddenContent(),
+ setMediaPreviews(attachments, sensitive, listener, statusViewData.isShowingContent(),
statusDisplayOptions.useBlurhash());
if (attachments.size() == 0) {
@@ -95,7 +99,7 @@ public class ConversationViewHolder extends StatusBaseViewHolder {
mediaLabel.setVisibility(View.GONE);
}
} else {
- setMediaLabel(attachments, sensitive, listener, status.getShowingHiddenContent());
+ setMediaLabel(attachments, sensitive, listener, statusViewData.isShowingContent());
// Hide all unused views.
mediaPreviews[0].setVisibility(View.GONE);
mediaPreviews[1].setVisibility(View.GONE);
@@ -104,10 +108,10 @@ public class ConversationViewHolder extends StatusBaseViewHolder {
hideSensitiveMediaWarning();
}
- setupButtons(listener, account.getId(), status.getContent().toString(),
+ setupButtons(listener, account.getId(), statusViewData.getContent().toString(),
statusDisplayOptions);
- setSpoilerAndContent(status.getExpanded(), status.getContent(), status.getSpoilerText(),
+ setSpoilerAndContent(statusViewData.isExpanded(), statusViewData.getContent(), status.getSpoilerText(),
status.getMentions(), status.getTags(), status.getEmojis(),
PollViewDataKt.toViewData(status.getPoll()), statusDisplayOptions, listener);
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt
index a09026c2..243c3744 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt
@@ -153,24 +153,24 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res
}
override fun onFavourite(favourite: Boolean, position: Int) {
- adapter.item(position)?.let { conversation ->
+ adapter.peek(position)?.let { conversation ->
viewModel.favourite(favourite, conversation)
}
}
override fun onBookmark(favourite: Boolean, position: Int) {
- adapter.item(position)?.let { conversation ->
+ adapter.peek(position)?.let { conversation ->
viewModel.bookmark(favourite, conversation)
}
}
override fun onMore(view: View, position: Int) {
- adapter.item(position)?.let { conversation ->
+ adapter.peek(position)?.let { conversation ->
val popup = PopupMenu(requireContext(), view)
popup.inflate(R.menu.conversation_more)
- if (conversation.lastStatus.muted) {
+ if (conversation.lastStatus.status.muted == true) {
popup.menu.removeItem(R.id.status_mute_conversation)
} else {
popup.menu.removeItem(R.id.status_unmute_conversation)
@@ -189,14 +189,14 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res
}
override fun onViewMedia(position: Int, attachmentIndex: Int, view: View?) {
- adapter.item(position)?.let { conversation ->
- viewMedia(attachmentIndex, AttachmentViewData.list(conversation.lastStatus.toStatus()), view)
+ adapter.peek(position)?.let { conversation ->
+ viewMedia(attachmentIndex, AttachmentViewData.list(conversation.lastStatus.status), view)
}
}
override fun onViewThread(position: Int) {
- adapter.item(position)?.let { conversation ->
- viewThread(conversation.lastStatus.id, conversation.lastStatus.url)
+ adapter.peek(position)?.let { conversation ->
+ viewThread(conversation.lastStatus.id, conversation.lastStatus.status.url)
}
}
@@ -205,13 +205,13 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res
}
override fun onExpandedChange(expanded: Boolean, position: Int) {
- adapter.item(position)?.let { conversation ->
+ adapter.peek(position)?.let { conversation ->
viewModel.expandHiddenStatus(expanded, conversation)
}
}
override fun onContentHiddenChange(isShowing: Boolean, position: Int) {
- adapter.item(position)?.let { conversation ->
+ adapter.peek(position)?.let { conversation ->
viewModel.showContent(isShowing, conversation)
}
}
@@ -221,7 +221,7 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res
}
override fun onContentCollapsedChange(isCollapsed: Boolean, position: Int) {
- adapter.item(position)?.let { conversation ->
+ adapter.peek(position)?.let { conversation ->
viewModel.collapseLongStatus(isCollapsed, conversation)
}
}
@@ -241,12 +241,12 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res
}
override fun onReply(position: Int) {
- adapter.item(position)?.let { conversation ->
- reply(conversation.lastStatus.toStatus())
+ adapter.peek(position)?.let { conversation ->
+ reply(conversation.lastStatus.status)
}
}
- private fun deleteConversation(conversation: ConversationEntity) {
+ private fun deleteConversation(conversation: ConversationViewData) {
AlertDialog.Builder(requireContext())
.setMessage(R.string.dialog_delete_conversation_warning)
.setNegativeButton(android.R.string.cancel, null)
@@ -268,7 +268,7 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res
}
override fun onVoteInPoll(position: Int, choices: MutableList) {
- adapter.item(position)?.let { conversation ->
+ adapter.peek(position)?.let { conversation ->
viewModel.voteInPoll(choices, conversation)
}
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsViewModel.kt
index 396f8e48..9326a05c 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsViewModel.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsViewModel.kt
@@ -16,16 +16,18 @@
package com.keylesspalace.tusky.components.conversation
import android.util.Log
+import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.cachedIn
+import androidx.paging.map
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.db.AppDatabase
import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.network.TimelineCases
-import com.keylesspalace.tusky.util.RxAwareViewModel
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.rx3.await
import javax.inject.Inject
@@ -35,7 +37,7 @@ class ConversationsViewModel @Inject constructor(
private val database: AppDatabase,
private val accountManager: AccountManager,
private val api: MastodonApi
-) : RxAwareViewModel() {
+) : ViewModel() {
@OptIn(ExperimentalPagingApi::class)
val conversationFlow = Pager(
@@ -44,104 +46,117 @@ class ConversationsViewModel @Inject constructor(
pagingSourceFactory = { database.conversationDao().conversationsForAccount(accountManager.activeAccount!!.id) }
)
.flow
+ .map { pagingData ->
+ pagingData.map { conversation -> conversation.toViewData() }
+ }
.cachedIn(viewModelScope)
- fun favourite(favourite: Boolean, conversation: ConversationEntity) {
+ fun favourite(favourite: Boolean, conversation: ConversationViewData) {
viewModelScope.launch {
try {
timelineCases.favourite(conversation.lastStatus.id, favourite).await()
- val newConversation = conversation.copy(
- lastStatus = conversation.lastStatus.copy(favourited = favourite)
+ val newConversation = conversation.toEntity(
+ accountId = accountManager.activeAccount!!.id,
+ favourited = favourite
)
- database.conversationDao().insert(newConversation)
+ saveConversationToDb(newConversation)
} catch (e: Exception) {
Log.w(TAG, "failed to favourite status", e)
}
}
}
- fun bookmark(bookmark: Boolean, conversation: ConversationEntity) {
+ fun bookmark(bookmark: Boolean, conversation: ConversationViewData) {
viewModelScope.launch {
try {
timelineCases.bookmark(conversation.lastStatus.id, bookmark).await()
- val newConversation = conversation.copy(
- lastStatus = conversation.lastStatus.copy(bookmarked = bookmark)
+ val newConversation = conversation.toEntity(
+ accountId = accountManager.activeAccount!!.id,
+ bookmarked = bookmark
)
- database.conversationDao().insert(newConversation)
+ saveConversationToDb(newConversation)
} catch (e: Exception) {
Log.w(TAG, "failed to bookmark status", e)
}
}
}
- fun voteInPoll(choices: List, conversation: ConversationEntity) {
+ fun voteInPoll(choices: List, conversation: ConversationViewData) {
viewModelScope.launch {
try {
- val poll = timelineCases.voteInPoll(conversation.lastStatus.id, conversation.lastStatus.poll?.id!!, choices).await()
- val newConversation = conversation.copy(
- lastStatus = conversation.lastStatus.copy(poll = poll)
+ val poll = timelineCases.voteInPoll(conversation.lastStatus.id, conversation.lastStatus.status.poll?.id!!, choices).await()
+ val newConversation = conversation.toEntity(
+ accountId = accountManager.activeAccount!!.id,
+ poll = poll
)
- database.conversationDao().insert(newConversation)
+ saveConversationToDb(newConversation)
} catch (e: Exception) {
Log.w(TAG, "failed to vote in poll", e)
}
}
}
- fun expandHiddenStatus(expanded: Boolean, conversation: ConversationEntity) {
+ fun expandHiddenStatus(expanded: Boolean, conversation: ConversationViewData) {
viewModelScope.launch {
- val newConversation = conversation.copy(
- lastStatus = conversation.lastStatus.copy(expanded = expanded)
+ val newConversation = conversation.toEntity(
+ accountId = accountManager.activeAccount!!.id,
+ expanded = expanded
)
saveConversationToDb(newConversation)
}
}
- fun collapseLongStatus(collapsed: Boolean, conversation: ConversationEntity) {
+ fun collapseLongStatus(collapsed: Boolean, conversation: ConversationViewData) {
viewModelScope.launch {
- val newConversation = conversation.copy(
- lastStatus = conversation.lastStatus.copy(collapsed = collapsed)
+ val newConversation = conversation.toEntity(
+ accountId = accountManager.activeAccount!!.id,
+ collapsed = collapsed
)
saveConversationToDb(newConversation)
}
}
- fun showContent(showing: Boolean, conversation: ConversationEntity) {
+ fun showContent(showing: Boolean, conversation: ConversationViewData) {
viewModelScope.launch {
- val newConversation = conversation.copy(
- lastStatus = conversation.lastStatus.copy(showingHiddenContent = showing)
+ val newConversation = conversation.toEntity(
+ accountId = accountManager.activeAccount!!.id,
+ showingHiddenContent = showing
)
saveConversationToDb(newConversation)
}
}
- fun remove(conversation: ConversationEntity) {
+ fun remove(conversation: ConversationViewData) {
viewModelScope.launch {
try {
api.deleteConversation(conversationId = conversation.id)
- database.conversationDao().delete(conversation)
+ database.conversationDao().delete(
+ id = conversation.id,
+ accountId = accountManager.activeAccount!!.id
+ )
} catch (e: Exception) {
Log.w(TAG, "failed to delete conversation", e)
}
}
}
- fun muteConversation(conversation: ConversationEntity) {
+ fun muteConversation(conversation: ConversationViewData) {
viewModelScope.launch {
try {
- val newStatus = timelineCases.muteConversation(
+ timelineCases.muteConversation(
conversation.lastStatus.id,
- !conversation.lastStatus.muted
+ !(conversation.lastStatus.status.muted ?: false)
).await()
- val newConversation = conversation.copy(
- lastStatus = newStatus.toEntity()
+ val newConversation = conversation.toEntity(
+ accountId = accountManager.activeAccount!!.id,
+ muted = !(conversation.lastStatus.status.muted ?: false)
)
database.conversationDao().insert(newConversation)
@@ -151,7 +166,7 @@ class ConversationsViewModel @Inject constructor(
}
}
- suspend fun saveConversationToDb(conversation: ConversationEntity) {
+ private suspend fun saveConversationToDb(conversation: ConversationEntity) {
database.conversationDao().insert(conversation)
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/ReportViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/ReportViewModel.kt
index f8991282..9f99da53 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/report/ReportViewModel.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/report/ReportViewModel.kt
@@ -21,6 +21,7 @@ import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.cachedIn
+import androidx.paging.map
import com.keylesspalace.tusky.appstore.BlockEvent
import com.keylesspalace.tusky.appstore.EventHub
import com.keylesspalace.tusky.appstore.MuteEvent
@@ -34,11 +35,13 @@ import com.keylesspalace.tusky.util.Loading
import com.keylesspalace.tusky.util.Resource
import com.keylesspalace.tusky.util.RxAwareViewModel
import com.keylesspalace.tusky.util.Success
+import com.keylesspalace.tusky.util.toViewData
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import javax.inject.Inject
@@ -74,6 +77,11 @@ class ReportViewModel @Inject constructor(
pagingSourceFactory = { StatusesPagingSource(accountId, mastodonApi) }
).flow
}
+ .map { pagingData ->
+ /* TODO: refactor reports to use the isShowingContent / isExpanded / isCollapsed attributes from StatusViewData.Concrete
+ instead of StatusViewState */
+ pagingData.map { status -> status.toViewData(false, false, false) }
+ }
.cachedIn(viewModelScope)
private val selectedIds = HashSet()
@@ -155,7 +163,7 @@ class ReportViewModel @Inject constructor(
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ relationship ->
- val muting = relationship?.muting == true
+ val muting = relationship.muting
muteStateMutable.value = Success(muting)
if (muting) {
eventHub.dispatch(MuteEvent(accountId))
@@ -180,7 +188,7 @@ class ReportViewModel @Inject constructor(
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ relationship ->
- val blocking = relationship?.blocking == true
+ val blocking = relationship.blocking
blockStateMutable.value = Success(blocking)
if (blocking) {
eventHub.dispatch(BlockEvent(accountId))
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusViewHolder.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusViewHolder.kt
index 1b3b0de6..9dceddec 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusViewHolder.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusViewHolder.kt
@@ -37,6 +37,7 @@ import com.keylesspalace.tusky.util.setClickableMentions
import com.keylesspalace.tusky.util.setClickableText
import com.keylesspalace.tusky.util.shouldTrimStatus
import com.keylesspalace.tusky.util.show
+import com.keylesspalace.tusky.viewdata.StatusViewData
import com.keylesspalace.tusky.viewdata.toViewData
import java.util.Date
@@ -45,20 +46,21 @@ class StatusViewHolder(
private val statusDisplayOptions: StatusDisplayOptions,
private val viewState: StatusViewState,
private val adapterHandler: AdapterHandler,
- private val getStatusForPosition: (Int) -> Status?
+ private val getStatusForPosition: (Int) -> StatusViewData.Concrete?
) : RecyclerView.ViewHolder(binding.root) {
+
private val mediaViewHeight = itemView.context.resources.getDimensionPixelSize(R.dimen.status_media_preview_height)
private val statusViewHelper = StatusViewHelper(itemView)
private val previewListener = object : StatusViewHelper.MediaPreviewListener {
override fun onViewMedia(v: View?, idx: Int) {
- status()?.let { status ->
- adapterHandler.showMedia(v, status, idx)
+ viewdata()?.let { viewdata ->
+ adapterHandler.showMedia(v, viewdata.status, idx)
}
}
override fun onContentHiddenChange(isShowing: Boolean) {
- status()?.id?.let { id ->
+ viewdata()?.id?.let { id ->
viewState.setMediaShow(id, isShowing)
}
}
@@ -66,57 +68,57 @@ class StatusViewHolder(
init {
binding.statusSelection.setOnCheckedChangeListener { _, isChecked ->
- status()?.let { status ->
- adapterHandler.setStatusChecked(status, isChecked)
+ viewdata()?.let { viewdata ->
+ adapterHandler.setStatusChecked(viewdata.status, isChecked)
}
}
binding.statusMediaPreviewContainer.clipToOutline = true
}
- fun bind(status: Status) {
- binding.statusSelection.isChecked = adapterHandler.isStatusChecked(status.id)
+ fun bind(viewData: StatusViewData.Concrete) {
+ binding.statusSelection.isChecked = adapterHandler.isStatusChecked(viewData.id)
updateTextView()
- val sensitive = status.sensitive
+ val sensitive = viewData.status.sensitive
statusViewHelper.setMediasPreview(
- statusDisplayOptions, status.attachments,
- sensitive, previewListener, viewState.isMediaShow(status.id, status.sensitive),
+ statusDisplayOptions, viewData.status.attachments,
+ sensitive, previewListener, viewState.isMediaShow(viewData.id, viewData.status.sensitive),
mediaViewHeight
)
- statusViewHelper.setupPollReadonly(status.poll.toViewData(), status.emojis, statusDisplayOptions)
- setCreatedAt(status.createdAt)
+ statusViewHelper.setupPollReadonly(viewData.status.poll.toViewData(), viewData.status.emojis, statusDisplayOptions)
+ setCreatedAt(viewData.status.createdAt)
}
private fun updateTextView() {
- status()?.let { status ->
+ viewdata()?.let { viewdata ->
setupCollapsedState(
- shouldTrimStatus(status.content), viewState.isCollapsed(status.id, true),
- viewState.isContentShow(status.id, status.sensitive), status.spoilerText
+ shouldTrimStatus(viewdata.content), viewState.isCollapsed(viewdata.id, true),
+ viewState.isContentShow(viewdata.id, viewdata.status.sensitive), viewdata.spoilerText
)
- if (status.spoilerText.isBlank()) {
- setTextVisible(true, status.content, status.mentions, status.tags, status.emojis, adapterHandler)
+ if (viewdata.spoilerText.isBlank()) {
+ setTextVisible(true, viewdata.content, viewdata.status.mentions, viewdata.status.tags, viewdata.status.emojis, adapterHandler)
binding.statusContentWarningButton.hide()
binding.statusContentWarningDescription.hide()
} else {
- val emojiSpoiler = status.spoilerText.emojify(status.emojis, binding.statusContentWarningDescription, statusDisplayOptions.animateEmojis)
+ val emojiSpoiler = viewdata.spoilerText.emojify(viewdata.status.emojis, binding.statusContentWarningDescription, statusDisplayOptions.animateEmojis)
binding.statusContentWarningDescription.text = emojiSpoiler
binding.statusContentWarningDescription.show()
binding.statusContentWarningButton.show()
- setContentWarningButtonText(viewState.isContentShow(status.id, true))
+ setContentWarningButtonText(viewState.isContentShow(viewdata.id, true))
binding.statusContentWarningButton.setOnClickListener {
- status()?.let { status ->
- val contentShown = viewState.isContentShow(status.id, true)
+ viewdata()?.let { viewdata ->
+ val contentShown = viewState.isContentShow(viewdata.id, true)
binding.statusContentWarningDescription.invalidate()
- viewState.setContentShow(status.id, !contentShown)
- setTextVisible(!contentShown, status.content, status.mentions, status.tags, status.emojis, adapterHandler)
+ viewState.setContentShow(viewdata.id, !contentShown)
+ setTextVisible(!contentShown, viewdata.content, viewdata.status.mentions, viewdata.status.tags, viewdata.status.emojis, adapterHandler)
setContentWarningButtonText(!contentShown)
}
}
- setTextVisible(viewState.isContentShow(status.id, true), status.content, status.mentions, status.tags, status.emojis, adapterHandler)
+ setTextVisible(viewState.isContentShow(viewdata.id, true), viewdata.content, viewdata.status.mentions, viewdata.status.tags, viewdata.status.emojis, adapterHandler)
}
}
}
@@ -169,8 +171,8 @@ class StatusViewHolder(
/* input filter for TextViews have to be set before text */
if (collapsible && (expanded || TextUtils.isEmpty(spoilerText))) {
binding.buttonToggleContent.setOnClickListener {
- status()?.let { status ->
- viewState.setCollapsed(status.id, !collapsed)
+ viewdata()?.let { viewdata ->
+ viewState.setCollapsed(viewdata.id, !collapsed)
updateTextView()
}
}
@@ -189,5 +191,5 @@ class StatusViewHolder(
}
}
- private fun status() = getStatusForPosition(bindingAdapterPosition)
+ private fun viewdata() = getStatusForPosition(bindingAdapterPosition)
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusesAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusesAdapter.kt
index 76ed2ebe..314513eb 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusesAdapter.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusesAdapter.kt
@@ -22,16 +22,16 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.keylesspalace.tusky.components.report.model.StatusViewState
import com.keylesspalace.tusky.databinding.ItemReportStatusBinding
-import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.util.StatusDisplayOptions
+import com.keylesspalace.tusky.viewdata.StatusViewData
class StatusesAdapter(
private val statusDisplayOptions: StatusDisplayOptions,
private val statusViewState: StatusViewState,
private val adapterHandler: AdapterHandler
-) : PagingDataAdapter(STATUS_COMPARATOR) {
+) : PagingDataAdapter(STATUS_COMPARATOR) {
- private val statusForPosition: (Int) -> Status? = { position: Int ->
+ private val statusForPosition: (Int) -> StatusViewData.Concrete? = { position: Int ->
if (position != RecyclerView.NO_POSITION) getItem(position) else null
}
@@ -50,11 +50,11 @@ class StatusesAdapter(
}
companion object {
- val STATUS_COMPARATOR = object : DiffUtil.ItemCallback() {
- override fun areContentsTheSame(oldItem: Status, newItem: Status): Boolean =
+ val STATUS_COMPARATOR = object : DiffUtil.ItemCallback() {
+ override fun areContentsTheSame(oldItem: StatusViewData.Concrete, newItem: StatusViewData.Concrete): Boolean =
oldItem == newItem
- override fun areItemsTheSame(oldItem: Status, newItem: Status): Boolean =
+ override fun areItemsTheSame(oldItem: StatusViewData.Concrete, newItem: StatusViewData.Concrete): Boolean =
oldItem.id == newItem.id
}
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineTypeMappers.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineTypeMappers.kt
index 252b9880..6ec95423 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineTypeMappers.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineTypeMappers.kt
@@ -15,9 +15,6 @@
package com.keylesspalace.tusky.components.timeline
-import android.text.SpannedString
-import androidx.core.text.parseAsHtml
-import androidx.core.text.toHtml
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.keylesspalace.tusky.db.TimelineAccountEntity
@@ -29,8 +26,6 @@ import com.keylesspalace.tusky.entity.HashTag
import com.keylesspalace.tusky.entity.Poll
import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.entity.TimelineAccount
-import com.keylesspalace.tusky.util.shouldTrimStatus
-import com.keylesspalace.tusky.util.trimTrailingWhitespace
import com.keylesspalace.tusky.viewdata.StatusViewData
import java.util.Date
@@ -119,7 +114,7 @@ fun Status.toEntity(
authorServerId = actionableStatus.account.id,
inReplyToId = actionableStatus.inReplyToId,
inReplyToAccountId = actionableStatus.inReplyToAccountId,
- content = actionableStatus.content.toHtml(),
+ content = actionableStatus.content,
createdAt = actionableStatus.createdAt.time,
emojis = actionableStatus.emojis.let(gson::toJson),
reblogsCount = actionableStatus.reblogsCount,
@@ -165,8 +160,7 @@ fun TimelineStatusWithAccount.toViewData(gson: Gson): StatusViewData {
inReplyToId = status.inReplyToId,
inReplyToAccountId = status.inReplyToAccountId,
reblog = null,
- content = status.content?.parseAsHtml()?.trimTrailingWhitespace()
- ?: SpannedString(""),
+ content = status.content.orEmpty(),
createdAt = Date(status.createdAt),
emojis = emojis,
reblogsCount = status.reblogsCount,
@@ -195,7 +189,7 @@ fun TimelineStatusWithAccount.toViewData(gson: Gson): StatusViewData {
inReplyToId = null,
inReplyToAccountId = null,
reblog = reblog,
- content = SpannedString(""),
+ content = "",
createdAt = Date(status.createdAt), // lie but whatever?
emojis = listOf(),
reblogsCount = 0,
@@ -223,8 +217,7 @@ fun TimelineStatusWithAccount.toViewData(gson: Gson): StatusViewData {
inReplyToId = status.inReplyToId,
inReplyToAccountId = status.inReplyToAccountId,
reblog = null,
- content = status.content?.parseAsHtml()?.trimTrailingWhitespace()
- ?: SpannedString(""),
+ content = status.content.orEmpty(),
createdAt = Date(status.createdAt),
emojis = emojis,
reblogsCount = status.reblogsCount,
@@ -249,7 +242,6 @@ fun TimelineStatusWithAccount.toViewData(gson: Gson): StatusViewData {
status = status,
isExpanded = this.status.expanded,
isShowingContent = this.status.contentShowing,
- isCollapsible = shouldTrimStatus(status.content),
isCollapsed = this.status.contentCollapsed
)
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineViewModel.kt
index 304b4e5a..7158a7b3 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineViewModel.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineViewModel.kt
@@ -42,7 +42,10 @@ import com.keylesspalace.tusky.network.FilterModel
import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.network.TimelineCases
import com.keylesspalace.tusky.viewdata.StatusViewData
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.rx3.await
@@ -79,15 +82,13 @@ class CachedTimelineViewModel @Inject constructor(
}
).flow
.map { pagingData ->
- pagingData.map { timelineStatus ->
+ pagingData.map(Dispatchers.Default.asExecutor()) { timelineStatus ->
timelineStatus.toViewData(gson)
- }
- }
- .map { pagingData ->
- pagingData.filter { statusViewData ->
+ }.filter(Dispatchers.Default.asExecutor()) { statusViewData ->
!shouldFilterStatus(statusViewData)
}
}
+ .flowOn(Dispatchers.Default)
.cachedIn(viewModelScope)
init {
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/NetworkTimelineViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/NetworkTimelineViewModel.kt
index f70fdcc8..ca7988bb 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/NetworkTimelineViewModel.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/NetworkTimelineViewModel.kt
@@ -40,6 +40,9 @@ import com.keylesspalace.tusky.util.isLessThan
import com.keylesspalace.tusky.util.isLessThanOrEqual
import com.keylesspalace.tusky.util.toViewData
import com.keylesspalace.tusky.viewdata.StatusViewData
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.rx3.await
@@ -79,10 +82,11 @@ class NetworkTimelineViewModel @Inject constructor(
remoteMediator = NetworkTimelineRemoteMediator(accountManager, this)
).flow
.map { pagingData ->
- pagingData.filter { statusViewData ->
+ pagingData.filter(Dispatchers.Default.asExecutor()) { statusViewData ->
!shouldFilterStatus(statusViewData)
}
}
+ .flowOn(Dispatchers.Default)
.cachedIn(viewModelScope)
override fun updatePoll(newPoll: Poll, status: StatusViewData.Concrete) {
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 2131300c..c541958a 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 = 32)
+ }, version = 33)
public abstract class AppDatabase extends RoomDatabase {
public abstract AccountDao accountDao();
@@ -490,4 +490,41 @@ public abstract class AppDatabase extends RoomDatabase {
database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `notificationsSignUps` INTEGER NOT NULL DEFAULT 1");
}
};
+
+ public static final Migration MIGRATION_32_33 = new Migration(32, 33) {
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+
+ // ConversationEntity lost the s_collapsible column
+ // since SQLite does not support removing columns and it is just a cache table, we recreate the whole table.
+ database.execSQL("DROP TABLE `ConversationEntity`");
+ database.execSQL("CREATE TABLE IF NOT EXISTS `ConversationEntity` (" +
+ "`accountId` INTEGER NOT NULL," +
+ "`id` TEXT NOT NULL," +
+ "`accounts` TEXT NOT NULL," +
+ "`unread` INTEGER NOT NULL," +
+ "`s_id` TEXT NOT NULL," +
+ "`s_url` TEXT," +
+ "`s_inReplyToId` TEXT," +
+ "`s_inReplyToAccountId` TEXT," +
+ "`s_account` TEXT NOT NULL," +
+ "`s_content` TEXT NOT NULL," +
+ "`s_createdAt` INTEGER NOT NULL," +
+ "`s_emojis` TEXT NOT NULL," +
+ "`s_favouritesCount` INTEGER NOT NULL," +
+ "`s_favourited` INTEGER NOT NULL," +
+ "`s_bookmarked` INTEGER NOT NULL," +
+ "`s_sensitive` INTEGER NOT NULL," +
+ "`s_spoilerText` TEXT NOT NULL," +
+ "`s_attachments` TEXT NOT NULL," +
+ "`s_mentions` TEXT NOT NULL," +
+ "`s_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," +
+ "PRIMARY KEY(`id`, `accountId`))");
+ }
+ };
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/db/ConversationsDao.kt b/app/src/main/java/com/keylesspalace/tusky/db/ConversationsDao.kt
index 393a2392..fe093bd0 100644
--- a/app/src/main/java/com/keylesspalace/tusky/db/ConversationsDao.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/db/ConversationsDao.kt
@@ -17,7 +17,6 @@ package com.keylesspalace.tusky.db
import androidx.paging.PagingSource
import androidx.room.Dao
-import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
@@ -31,8 +30,8 @@ interface ConversationsDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(conversation: ConversationEntity): Long
- @Delete
- suspend fun delete(conversation: ConversationEntity): Int
+ @Query("DELETE FROM ConversationEntity WHERE id = :id AND accountId = :accountId")
+ suspend fun delete(id: String, accountId: Long): Int
@Query("SELECT * FROM ConversationEntity WHERE accountId = :accountId ORDER BY s_createdAt DESC")
fun conversationsForAccount(accountId: Long): PagingSource
diff --git a/app/src/main/java/com/keylesspalace/tusky/db/Converters.kt b/app/src/main/java/com/keylesspalace/tusky/db/Converters.kt
index c9daec0a..34ff6474 100644
--- a/app/src/main/java/com/keylesspalace/tusky/db/Converters.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/db/Converters.kt
@@ -15,9 +15,6 @@
package com.keylesspalace.tusky.db
-import android.text.Spanned
-import androidx.core.text.parseAsHtml
-import androidx.core.text.toHtml
import androidx.room.ProvidedTypeConverter
import androidx.room.TypeConverter
import com.google.gson.Gson
@@ -31,10 +28,8 @@ import com.keylesspalace.tusky.entity.HashTag
import com.keylesspalace.tusky.entity.NewPoll
import com.keylesspalace.tusky.entity.Poll
import com.keylesspalace.tusky.entity.Status
-import com.keylesspalace.tusky.util.trimTrailingWhitespace
import java.net.URLDecoder
import java.net.URLEncoder
-import java.util.ArrayList
import java.util.Date
import javax.inject.Inject
import javax.inject.Singleton
@@ -140,22 +135,6 @@ class Converters @Inject constructor (
return Date(date)
}
- @TypeConverter
- fun spannedToString(spanned: Spanned?): String? {
- if (spanned == null) {
- return null
- }
- return spanned.toHtml()
- }
-
- @TypeConverter
- fun stringToSpanned(spannedString: String?): Spanned? {
- if (spannedString == null) {
- return null
- }
- return spannedString.parseAsHtml().trimTrailingWhitespace()
- }
-
@TypeConverter
fun pollToJson(poll: Poll?): String? {
return gson.toJson(poll)
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 677f8167..7f0fbd01 100644
--- a/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt
@@ -63,6 +63,7 @@ class AppModule {
AppDatabase.Migration25_26(appContext.getExternalFilesDir("Tusky")),
AppDatabase.MIGRATION_26_27, AppDatabase.MIGRATION_27_28, AppDatabase.MIGRATION_28_29,
AppDatabase.MIGRATION_29_30, AppDatabase.MIGRATION_30_31, AppDatabase.MIGRATION_31_32,
+ AppDatabase.MIGRATION_32_33
)
.build()
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt
index d927c299..d8b52ca3 100644
--- a/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt
@@ -18,13 +18,10 @@ package com.keylesspalace.tusky.di
import android.content.Context
import android.content.SharedPreferences
import android.os.Build
-import android.text.Spanned
import at.connyduck.calladapter.kotlinresult.KotlinResultCallAdapterFactory
import com.google.gson.Gson
-import com.google.gson.GsonBuilder
import com.keylesspalace.tusky.BuildConfig
import com.keylesspalace.tusky.db.AccountManager
-import com.keylesspalace.tusky.json.SpannedTypeAdapter
import com.keylesspalace.tusky.network.InstanceSwitchAuthInterceptor
import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.util.getNonNullString
@@ -52,11 +49,7 @@ class NetworkModule {
@Provides
@Singleton
- fun providesGson(): Gson {
- return GsonBuilder()
- .registerTypeAdapter(Spanned::class.java, SpannedTypeAdapter())
- .create()
- }
+ fun providesGson() = Gson()
@Provides
@Singleton
diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Account.kt b/app/src/main/java/com/keylesspalace/tusky/entity/Account.kt
index 672bd5aa..bf5431ee 100644
--- a/app/src/main/java/com/keylesspalace/tusky/entity/Account.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/entity/Account.kt
@@ -15,7 +15,6 @@
package com.keylesspalace.tusky.entity
-import android.text.Spanned
import com.google.gson.annotations.SerializedName
import java.util.Date
@@ -24,7 +23,7 @@ data class Account(
@SerializedName("username") val localUsername: String,
@SerializedName("acct") val username: String,
@SerializedName("display_name") val displayName: String?, // should never be null per Api definition, but some servers break the contract
- val note: Spanned,
+ val note: String,
val url: String,
val avatar: String,
val header: String,
@@ -46,56 +45,6 @@ data class Account(
} else displayName
fun isRemote(): Boolean = this.username != this.localUsername
-
- /**
- * overriding equals & hashcode because Spanned does not always compare correctly otherwise
- */
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (javaClass != other?.javaClass) return false
- other as Account
-
- if (id != other.id) return false
- if (localUsername != other.localUsername) return false
- if (username != other.username) return false
- if (displayName != other.displayName) return false
- if (note.toString() != other.note.toString()) return false
- if (url != other.url) return false
- if (avatar != other.avatar) return false
- if (header != other.header) return false
- if (locked != other.locked) return false
- if (followersCount != other.followersCount) return false
- if (followingCount != other.followingCount) return false
- if (statusesCount != other.statusesCount) return false
- if (source != other.source) return false
- if (bot != other.bot) return false
- if (emojis != other.emojis) return false
- if (fields != other.fields) return false
- if (moved != other.moved) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = id.hashCode()
- result = 31 * result + localUsername.hashCode()
- result = 31 * result + username.hashCode()
- result = 31 * result + (displayName?.hashCode() ?: 0)
- result = 31 * result + note.toString().hashCode()
- result = 31 * result + url.hashCode()
- result = 31 * result + avatar.hashCode()
- result = 31 * result + header.hashCode()
- result = 31 * result + locked.hashCode()
- result = 31 * result + followersCount
- result = 31 * result + followingCount
- result = 31 * result + statusesCount
- result = 31 * result + (source?.hashCode() ?: 0)
- result = 31 * result + bot.hashCode()
- result = 31 * result + (emojis?.hashCode() ?: 0)
- result = 31 * result + (fields?.hashCode() ?: 0)
- result = 31 * result + (moved?.hashCode() ?: 0)
- return result
- }
}
data class AccountSource(
@@ -107,7 +56,7 @@ data class AccountSource(
data class Field(
val name: String,
- val value: Spanned,
+ val value: String,
@SerializedName("verified_at") val verifiedAt: Date?
)
diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Announcement.kt b/app/src/main/java/com/keylesspalace/tusky/entity/Announcement.kt
index 400e9764..00d5659d 100644
--- a/app/src/main/java/com/keylesspalace/tusky/entity/Announcement.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/entity/Announcement.kt
@@ -15,13 +15,12 @@
package com.keylesspalace.tusky.entity
-import android.text.Spanned
import com.google.gson.annotations.SerializedName
import java.util.Date
data class Announcement(
val id: String,
- val content: Spanned,
+ val content: String,
@SerializedName("starts_at") val startsAt: Date?,
@SerializedName("ends_at") val endsAt: Date?,
@SerializedName("all_day") val allDay: Boolean,
diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Card.kt b/app/src/main/java/com/keylesspalace/tusky/entity/Card.kt
index 52011f3d..29fe7f8e 100644
--- a/app/src/main/java/com/keylesspalace/tusky/entity/Card.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/entity/Card.kt
@@ -15,13 +15,12 @@
package com.keylesspalace.tusky.entity
-import android.text.Spanned
import com.google.gson.annotations.SerializedName
data class Card(
val url: String,
- val title: Spanned,
- val description: Spanned,
+ val title: String,
+ val description: String,
@SerializedName("author_name") val authorName: String,
val image: String,
val type: String,
@@ -31,9 +30,7 @@ data class Card(
val embed_url: String?
) {
- override fun hashCode(): Int {
- return url.hashCode()
- }
+ override fun hashCode() = url.hashCode()
override fun equals(other: Any?): Boolean {
if (other !is Card) {
diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Status.kt b/app/src/main/java/com/keylesspalace/tusky/entity/Status.kt
index f75ce4e7..19cb7aa6 100644
--- a/app/src/main/java/com/keylesspalace/tusky/entity/Status.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/entity/Status.kt
@@ -16,9 +16,9 @@
package com.keylesspalace.tusky.entity
import android.text.SpannableStringBuilder
-import android.text.Spanned
import android.text.style.URLSpan
import com.google.gson.annotations.SerializedName
+import com.keylesspalace.tusky.util.parseAsMastodonHtml
import java.util.ArrayList
import java.util.Date
@@ -29,7 +29,7 @@ data class Status(
@SerializedName("in_reply_to_id") var inReplyToId: String?,
@SerializedName("in_reply_to_account_id") val inReplyToAccountId: String?,
val reblog: Status?,
- val content: Spanned,
+ val content: String,
@SerializedName("created_at") val createdAt: Date,
val emojis: List,
@SerializedName("reblogs_count") val reblogsCount: Int,
@@ -134,8 +134,9 @@ data class Status(
}
private fun getEditableText(): String {
- val builder = SpannableStringBuilder(content)
- for (span in content.getSpans(0, content.length, URLSpan::class.java)) {
+ val contentSpanned = content.parseAsMastodonHtml()
+ val builder = SpannableStringBuilder(content.parseAsMastodonHtml())
+ for (span in contentSpanned.getSpans(0, content.length, URLSpan::class.java)) {
val url = span.url
for ((_, url1, username) in mentions) {
if (url == url1) {
@@ -149,71 +150,6 @@ data class Status(
return builder.toString()
}
- /**
- * overriding equals & hashcode because Spanned does not always compare correctly otherwise
- */
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (javaClass != other?.javaClass) return false
- other as Status
-
- if (id != other.id) return false
- if (url != other.url) return false
- if (account != other.account) return false
- if (inReplyToId != other.inReplyToId) return false
- if (inReplyToAccountId != other.inReplyToAccountId) return false
- if (reblog != other.reblog) return false
- if (content.toString() != other.content.toString()) return false
- if (createdAt != other.createdAt) return false
- if (emojis != other.emojis) return false
- if (reblogsCount != other.reblogsCount) return false
- if (favouritesCount != other.favouritesCount) return false
- if (reblogged != other.reblogged) return false
- if (favourited != other.favourited) return false
- if (bookmarked != other.bookmarked) return false
- if (sensitive != other.sensitive) return false
- if (spoilerText != other.spoilerText) return false
- if (visibility != other.visibility) return false
- if (attachments != other.attachments) return false
- if (mentions != other.mentions) return false
- if (tags != other.tags) return false
- if (application != other.application) return false
- if (pinned != other.pinned) return false
- if (muted != other.muted) return false
- if (poll != other.poll) return false
- if (card != other.card) return false
- return true
- }
-
- override fun hashCode(): Int {
- var result = id.hashCode()
- result = 31 * result + (url?.hashCode() ?: 0)
- result = 31 * result + account.hashCode()
- result = 31 * result + (inReplyToId?.hashCode() ?: 0)
- result = 31 * result + (inReplyToAccountId?.hashCode() ?: 0)
- result = 31 * result + (reblog?.hashCode() ?: 0)
- result = 31 * result + content.toString().hashCode()
- result = 31 * result + createdAt.hashCode()
- result = 31 * result + emojis.hashCode()
- result = 31 * result + reblogsCount
- result = 31 * result + favouritesCount
- result = 31 * result + reblogged.hashCode()
- result = 31 * result + favourited.hashCode()
- result = 31 * result + bookmarked.hashCode()
- result = 31 * result + sensitive.hashCode()
- result = 31 * result + spoilerText.hashCode()
- result = 31 * result + visibility.hashCode()
- result = 31 * result + attachments.hashCode()
- result = 31 * result + mentions.hashCode()
- result = 31 * result + (tags?.hashCode() ?: 0)
- result = 31 * result + (application?.hashCode() ?: 0)
- result = 31 * result + (pinned?.hashCode() ?: 0)
- result = 31 * result + (muted?.hashCode() ?: 0)
- result = 31 * result + (poll?.hashCode() ?: 0)
- result = 31 * result + (card?.hashCode() ?: 0)
- return result
- }
-
data class Mention(
val id: String,
val url: String,
diff --git a/app/src/main/java/com/keylesspalace/tusky/json/SpannedTypeAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/json/SpannedTypeAdapter.kt
deleted file mode 100644
index 60af6134..00000000
--- a/app/src/main/java/com/keylesspalace/tusky/json/SpannedTypeAdapter.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-/* Copyright 2020 Tusky Contributors
- *
- * 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.json
-
-import android.text.Spanned
-import android.text.SpannedString
-import androidx.core.text.HtmlCompat
-import androidx.core.text.parseAsHtml
-import androidx.core.text.toHtml
-import com.google.gson.JsonDeserializationContext
-import com.google.gson.JsonDeserializer
-import com.google.gson.JsonElement
-import com.google.gson.JsonParseException
-import com.google.gson.JsonPrimitive
-import com.google.gson.JsonSerializationContext
-import com.google.gson.JsonSerializer
-import com.keylesspalace.tusky.util.trimTrailingWhitespace
-import java.lang.reflect.Type
-
-class SpannedTypeAdapter : JsonDeserializer, JsonSerializer {
- @Throws(JsonParseException::class)
- override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Spanned {
- return json.asString
- /* Mastodon uses 'white-space: pre-wrap;' so spaces are displayed as returned by the Api.
- * We can't use CSS so we replace spaces with non-breaking-spaces to emulate the behavior.
- */
- ?.replace("
", "
")
- ?.replace("
", "
")
- ?.replace("
", "
")
- ?.replace(" ", " ")
- ?.parseAsHtml()
- /* Html.fromHtml returns trailing whitespace if the html ends in a
tag, which
- * most status contents do, so it should be trimmed. */
- ?.trimTrailingWhitespace()
- ?: SpannedString("")
- }
-
- override fun serialize(src: Spanned?, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
- return JsonPrimitive(src!!.toHtml(HtmlCompat.TO_HTML_PARAGRAPH_LINES_INDIVIDUAL))
- }
-}
diff --git a/app/src/main/java/com/keylesspalace/tusky/util/StatusParsingHelper.kt b/app/src/main/java/com/keylesspalace/tusky/util/StatusParsingHelper.kt
new file mode 100644
index 00000000..fc62c78d
--- /dev/null
+++ b/app/src/main/java/com/keylesspalace/tusky/util/StatusParsingHelper.kt
@@ -0,0 +1,62 @@
+/* Copyright 2022 Tusky Contributors
+ *
+ * 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.util
+
+import android.text.SpannableStringBuilder
+import android.text.Spanned
+import androidx.core.text.parseAsHtml
+
+/**
+ * parse a String containing html from the Mastodon api to Spanned
+ */
+fun String.parseAsMastodonHtml(): Spanned {
+ return this.replace("
", "
")
+ .replace("
", "
")
+ .replace("
", "
")
+ .replace(" ", " ")
+ .parseAsHtml()
+ /* Html.fromHtml returns trailing whitespace if the html ends in a tag, which
+ * most status contents do, so it should be trimmed. */
+ .trimTrailingWhitespace()
+}
+
+fun replaceCrashingCharacters(content: Spanned): Spanned {
+ return replaceCrashingCharacters(content as CharSequence) as Spanned
+}
+
+fun replaceCrashingCharacters(content: CharSequence): CharSequence? {
+ var replacing = false
+ var builder: SpannableStringBuilder? = null
+ val length = content.length
+ for (index in 0 until length) {
+ val character = content[index]
+
+ // If there are more than one or two, switch to a map
+ if (character == SOFT_HYPHEN) {
+ if (!replacing) {
+ replacing = true
+ builder = SpannableStringBuilder(content, 0, index)
+ }
+ builder!!.append(ASCII_HYPHEN)
+ } else if (replacing) {
+ builder!!.append(character)
+ }
+ }
+ return if (replacing) builder else content
+}
+
+private const val SOFT_HYPHEN = '\u00ad'
+private const val ASCII_HYPHEN = '-'
diff --git a/app/src/main/java/com/keylesspalace/tusky/util/ViewDataUtils.kt b/app/src/main/java/com/keylesspalace/tusky/util/ViewDataUtils.kt
index 52d9713f..fef9c0bb 100644
--- a/app/src/main/java/com/keylesspalace/tusky/util/ViewDataUtils.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/util/ViewDataUtils.kt
@@ -27,12 +27,9 @@ fun Status.toViewData(
isExpanded: Boolean,
isCollapsed: Boolean
): StatusViewData.Concrete {
- val visibleStatus = this.reblog ?: this
-
return StatusViewData.Concrete(
status = this,
isShowingContent = isShowingContent,
- isCollapsible = shouldTrimStatus(visibleStatus.content),
isCollapsed = isCollapsed,
isExpanded = isExpanded,
)
diff --git a/app/src/main/java/com/keylesspalace/tusky/viewdata/StatusViewData.kt b/app/src/main/java/com/keylesspalace/tusky/viewdata/StatusViewData.kt
index d8f27157..8ac212d9 100644
--- a/app/src/main/java/com/keylesspalace/tusky/viewdata/StatusViewData.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/viewdata/StatusViewData.kt
@@ -15,9 +15,11 @@
package com.keylesspalace.tusky.viewdata
import android.os.Build
-import android.text.SpannableStringBuilder
import android.text.Spanned
import com.keylesspalace.tusky.entity.Status
+import com.keylesspalace.tusky.util.parseAsMastodonHtml
+import com.keylesspalace.tusky.util.replaceCrashingCharacters
+import com.keylesspalace.tusky.util.shouldTrimStatus
/**
* Created by charlag on 11/07/2017.
@@ -32,13 +34,6 @@ sealed class StatusViewData {
val status: Status,
val isExpanded: Boolean,
val isShowingContent: Boolean,
- /**
- * Specifies whether the content of this post is allowed to be collapsed or if it should show
- * all content regardless.
- *
- * @return Whether the post is collapsible or never collapsed.
- */
- val isCollapsible: Boolean,
/**
* Specifies whether the content of this post is currently limited in visibility to the first
* 500 characters or not.
@@ -51,6 +46,14 @@ sealed class StatusViewData {
override val id: String
get() = status.id
+ /**
+ * Specifies whether the content of this post is allowed to be collapsed or if it should show
+ * all content regardless.
+ *
+ * @return Whether the post is collapsible or never collapsed.
+ */
+ val isCollapsible: Boolean
+
val content: Spanned
val spoilerText: String
val username: String
@@ -74,45 +77,17 @@ sealed class StatusViewData {
init {
if (Build.VERSION.SDK_INT == 23) {
// https://github.com/tuskyapp/Tusky/issues/563
- this.content = replaceCrashingCharacters(status.actionableStatus.content)
+ this.content = replaceCrashingCharacters(status.actionableStatus.content.parseAsMastodonHtml())
this.spoilerText =
replaceCrashingCharacters(status.actionableStatus.spoilerText).toString()
this.username =
replaceCrashingCharacters(status.actionableStatus.account.username).toString()
} else {
- this.content = status.actionableStatus.content
+ this.content = status.actionableStatus.content.parseAsMastodonHtml()
this.spoilerText = status.actionableStatus.spoilerText
this.username = status.actionableStatus.account.username
}
- }
-
- companion object {
- private const val SOFT_HYPHEN = '\u00ad'
- private const val ASCII_HYPHEN = '-'
- fun replaceCrashingCharacters(content: Spanned): Spanned {
- return replaceCrashingCharacters(content as CharSequence) as Spanned
- }
-
- fun replaceCrashingCharacters(content: CharSequence): CharSequence? {
- var replacing = false
- var builder: SpannableStringBuilder? = null
- val length = content.length
- for (index in 0 until length) {
- val character = content[index]
-
- // If there are more than one or two, switch to a map
- if (character == SOFT_HYPHEN) {
- if (!replacing) {
- replacing = true
- builder = SpannableStringBuilder(content, 0, index)
- }
- builder!!.append(ASCII_HYPHEN)
- } else if (replacing) {
- builder!!.append(character)
- }
- }
- return if (replacing) builder else content
- }
+ this.isCollapsible = shouldTrimStatus(this.content)
}
/** Helper for Java */
diff --git a/app/src/test/java/com/keylesspalace/tusky/BottomSheetActivityTest.kt b/app/src/test/java/com/keylesspalace/tusky/BottomSheetActivityTest.kt
index ff208823..beb6af9b 100644
--- a/app/src/test/java/com/keylesspalace/tusky/BottomSheetActivityTest.kt
+++ b/app/src/test/java/com/keylesspalace/tusky/BottomSheetActivityTest.kt
@@ -15,7 +15,6 @@
package com.keylesspalace.tusky
-import android.text.SpannedString
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.keylesspalace.tusky.entity.SearchResult
import com.keylesspalace.tusky.entity.Status
@@ -70,7 +69,7 @@ class BottomSheetActivityTest {
inReplyToId = null,
inReplyToAccountId = null,
reblog = null,
- content = SpannedString("omgwat"),
+ content = "omgwat",
createdAt = Date(),
emojis = emptyList(),
reblogsCount = 0,
diff --git a/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt b/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt
index dc4a412f..5396a21e 100644
--- a/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt
+++ b/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt
@@ -17,7 +17,6 @@ package com.keylesspalace.tusky
import android.content.Intent
import android.os.Looper.getMainLooper
-import android.text.SpannedString
import android.widget.EditText
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.keylesspalace.tusky.components.compose.ComposeActivity
@@ -469,7 +468,7 @@ class ComposeActivityTest {
"admin",
"admin",
"admin",
- SpannedString(""),
+ "",
"https://example.token",
"",
"",
diff --git a/app/src/test/java/com/keylesspalace/tusky/FilterTest.kt b/app/src/test/java/com/keylesspalace/tusky/FilterTest.kt
index d5063943..91ea38d3 100644
--- a/app/src/test/java/com/keylesspalace/tusky/FilterTest.kt
+++ b/app/src/test/java/com/keylesspalace/tusky/FilterTest.kt
@@ -1,6 +1,5 @@
package com.keylesspalace.tusky
-import android.text.SpannedString
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.keylesspalace.tusky.entity.Attachment
import com.keylesspalace.tusky.entity.Filter
@@ -162,7 +161,7 @@ class FilterTest {
inReplyToId = null,
inReplyToAccountId = null,
reblog = null,
- content = SpannedString(content),
+ content = content,
createdAt = Date(),
emojis = emptyList(),
reblogsCount = 0,
diff --git a/app/src/test/java/com/keylesspalace/tusky/StatusComparisonTest.kt b/app/src/test/java/com/keylesspalace/tusky/StatusComparisonTest.kt
index ed06e27c..3086036a 100644
--- a/app/src/test/java/com/keylesspalace/tusky/StatusComparisonTest.kt
+++ b/app/src/test/java/com/keylesspalace/tusky/StatusComparisonTest.kt
@@ -1,10 +1,8 @@
package com.keylesspalace.tusky
-import android.text.Spanned
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.google.gson.GsonBuilder
+import com.google.gson.Gson
import com.keylesspalace.tusky.entity.Status
-import com.keylesspalace.tusky.json.SpannedTypeAdapter
import com.keylesspalace.tusky.viewdata.StatusViewData
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
@@ -39,9 +37,7 @@ class StatusComparisonTest {
assertEquals(createStatus(note = "Test"), createStatus(note = "Test 123456"))
}
- private val gson = GsonBuilder().registerTypeAdapter(
- Spanned::class.java, SpannedTypeAdapter()
- ).create()
+ private val gson = Gson()
@Test
fun `two equal status view data - should be equal`() {
@@ -49,14 +45,12 @@ class StatusComparisonTest {
status = createStatus(),
isExpanded = false,
isShowingContent = false,
- isCollapsible = false,
isCollapsed = false
)
val viewdata2 = StatusViewData.Concrete(
status = createStatus(),
isExpanded = false,
isShowingContent = false,
- isCollapsible = false,
isCollapsed = false
)
assertEquals(viewdata1, viewdata2)
@@ -68,14 +62,12 @@ class StatusComparisonTest {
status = createStatus(),
isExpanded = true,
isShowingContent = false,
- isCollapsible = false,
isCollapsed = false
)
val viewdata2 = StatusViewData.Concrete(
status = createStatus(),
isExpanded = false,
isShowingContent = false,
- isCollapsible = false,
isCollapsed = false
)
assertNotEquals(viewdata1, viewdata2)
@@ -87,14 +79,12 @@ class StatusComparisonTest {
status = createStatus(content = "whatever"),
isExpanded = true,
isShowingContent = false,
- isCollapsible = false,
isCollapsed = false
)
val viewdata2 = StatusViewData.Concrete(
status = createStatus(),
isExpanded = false,
isShowingContent = false,
- isCollapsible = false,
isCollapsed = false
)
assertNotEquals(viewdata1, viewdata2)
diff --git a/app/src/test/java/com/keylesspalace/tusky/components/timeline/NetworkTimelinePagingSourceTest.kt b/app/src/test/java/com/keylesspalace/tusky/components/timeline/NetworkTimelinePagingSourceTest.kt
index 60dda419..33215e67 100644
--- a/app/src/test/java/com/keylesspalace/tusky/components/timeline/NetworkTimelinePagingSourceTest.kt
+++ b/app/src/test/java/com/keylesspalace/tusky/components/timeline/NetworkTimelinePagingSourceTest.kt
@@ -1,14 +1,19 @@
package com.keylesspalace.tusky.components.timeline
import androidx.paging.PagingSource
+import androidx.test.ext.junit.runners.AndroidJUnit4
import com.keylesspalace.tusky.components.timeline.viewmodel.NetworkTimelinePagingSource
import com.keylesspalace.tusky.components.timeline.viewmodel.NetworkTimelineViewModel
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Test
+import org.junit.runner.RunWith
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
+import org.robolectric.annotation.Config
+@Config(sdk = [28])
+@RunWith(AndroidJUnit4::class)
class NetworkTimelinePagingSourceTest {
private val status = mockStatusViewData()
diff --git a/app/src/test/java/com/keylesspalace/tusky/components/timeline/StatusMocker.kt b/app/src/test/java/com/keylesspalace/tusky/components/timeline/StatusMocker.kt
index f7c998b5..cc6a90bd 100644
--- a/app/src/test/java/com/keylesspalace/tusky/components/timeline/StatusMocker.kt
+++ b/app/src/test/java/com/keylesspalace/tusky/components/timeline/StatusMocker.kt
@@ -1,6 +1,5 @@
package com.keylesspalace.tusky.components.timeline
-import android.text.SpannedString
import com.google.gson.Gson
import com.keylesspalace.tusky.db.TimelineStatusWithAccount
import com.keylesspalace.tusky.entity.Status
@@ -25,7 +24,7 @@ fun mockStatus(id: String = "100") = Status(
inReplyToId = null,
inReplyToAccountId = null,
reblog = null,
- content = SpannedString("Test"),
+ content = "Test",
createdAt = fixedDate,
emojis = emptyList(),
reblogsCount = 1,
@@ -50,7 +49,6 @@ fun mockStatusViewData(id: String = "100") = StatusViewData.Concrete(
status = mockStatus(id),
isExpanded = false,
isShowingContent = false,
- isCollapsible = false,
isCollapsed = true,
)
From 027b659d1c0ecfcfa53607fffdd8fba0e98cbae6 Mon Sep 17 00:00:00 2001
From: Konrad Pozniak
Date: Sat, 16 Apr 2022 09:44:05 +0200
Subject: [PATCH 007/104] fix notifications showing unparsed html (#2436)
---
.../tusky/components/notifications/NotificationHelper.java | 7 ++++---
.../com/keylesspalace/tusky/util/StatusParsingHelper.kt | 1 +
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java
index 63c17082..83682ab2 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java
+++ b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java
@@ -16,6 +16,7 @@
package com.keylesspalace.tusky.components.notifications;
+import static com.keylesspalace.tusky.util.StatusParsingHelper.parseAsMastodonHtml;
import static com.keylesspalace.tusky.viewdata.PollViewDataKt.buildDescription;
import android.app.NotificationChannel;
@@ -341,7 +342,7 @@ public class NotificationHelper {
Status status = body.getStatus();
String citedLocalAuthor = status.getAccount().getLocalUsername();
- String citedText = status.getContent().toString();
+ String citedText = parseAsMastodonHtml(status.getContent()).toString();
String inReplyToId = status.getId();
Status actionableStatus = status.getActionableStatus();
Status.Visibility replyVisibility = actionableStatus.getVisibility();
@@ -690,13 +691,13 @@ public class NotificationHelper {
if (!TextUtils.isEmpty(notification.getStatus().getSpoilerText())) {
return notification.getStatus().getSpoilerText();
} else {
- return notification.getStatus().getContent().toString();
+ return parseAsMastodonHtml(notification.getStatus().getContent()).toString();
}
case POLL:
if (!TextUtils.isEmpty(notification.getStatus().getSpoilerText())) {
return notification.getStatus().getSpoilerText();
} else {
- StringBuilder builder = new StringBuilder(notification.getStatus().getContent());
+ StringBuilder builder = new StringBuilder(parseAsMastodonHtml(notification.getStatus().getContent()));
builder.append('\n');
Poll poll = notification.getStatus().getPoll();
List options = poll.getOptions();
diff --git a/app/src/main/java/com/keylesspalace/tusky/util/StatusParsingHelper.kt b/app/src/main/java/com/keylesspalace/tusky/util/StatusParsingHelper.kt
index fc62c78d..2ac4782c 100644
--- a/app/src/main/java/com/keylesspalace/tusky/util/StatusParsingHelper.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/util/StatusParsingHelper.kt
@@ -13,6 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
+@file:JvmName("StatusParsingHelper")
package com.keylesspalace.tusky.util
import android.text.SpannableStringBuilder
From f2fc87a79ee7bb22693b24f54ef05a5244ca0f58 Mon Sep 17 00:00:00 2001
From: Konrad Pozniak
Date: Sat, 16 Apr 2022 09:44:37 +0200
Subject: [PATCH 008/104] upgrade Kotlin and Coroutines (#2434)
---
app/build.gradle | 4 +---
build.gradle | 3 +--
2 files changed, 2 insertions(+), 5 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index 3c5cb162..6ed56f42 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -88,7 +88,7 @@ android {
}
}
-ext.coroutinesVersion = "1.6.0"
+ext.coroutinesVersion = "1.6.1"
ext.lifecycleVersion = "2.4.1"
ext.roomVersion = '2.4.2'
ext.retrofitVersion = '2.9.0'
@@ -99,8 +99,6 @@ ext.materialdrawerVersion = '8.4.5'
// if libraries are changed here, they should also be changed in LicenseActivity
dependencies {
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
-
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx3:$coroutinesVersion"
diff --git a/build.gradle b/build.gradle
index c9311701..ace9a117 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,5 +1,4 @@
buildscript {
- ext.kotlin_version = '1.6.10'
repositories {
google()
mavenCentral()
@@ -7,7 +6,7 @@ buildscript {
}
dependencies {
classpath "com.android.tools.build:gradle:7.1.2"
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.20"
classpath "org.jlleitschuh.gradle:ktlint-gradle:10.1.0"
}
}
From 216f094e983018c27689835e277bd2611ab2bf9d Mon Sep 17 00:00:00 2001
From: Konrad Pozniak
Date: Sat, 16 Apr 2022 09:45:45 +0200
Subject: [PATCH 009/104] upgrade ktlint gradle plugin to 10.2.1 (#2435)
---
.../main/java/com/keylesspalace/tusky/ViewMediaActivity.kt | 1 -
build.gradle | 4 ++--
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt b/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt
index 64d29577..fda2c82b 100644
--- a/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt
@@ -283,7 +283,6 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
}
return@fromCallable false
}
-
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnDispose {
diff --git a/build.gradle b/build.gradle
index ace9a117..3a5251fa 100644
--- a/build.gradle
+++ b/build.gradle
@@ -7,11 +7,11 @@ buildscript {
dependencies {
classpath "com.android.tools.build:gradle:7.1.2"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.20"
- classpath "org.jlleitschuh.gradle:ktlint-gradle:10.1.0"
+ classpath "org.jlleitschuh.gradle:ktlint-gradle:10.2.1"
}
}
plugins {
- id "org.jlleitschuh.gradle.ktlint" version "10.1.0"
+ id "org.jlleitschuh.gradle.ktlint" version "10.2.1"
}
allprojects {
From e0abcbfada6a5265331d5ce78f1f20e17984e23e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?M=C3=A9lanie=20Chauvel?=
Date: Mon, 18 Apr 2022 21:41:18 +0200
Subject: [PATCH 010/104] Improve time format of posts when using absolute time
(#2413)
* Improve time format of posts when using absolute time
* fix AbsoluteTimeFormatter, add tests
* fix tests
Co-authored-by: Conny Duck
---
.../tusky/adapter/NotificationsAdapter.java | 30 ++++------
.../tusky/adapter/StatusBaseViewHolder.java | 27 ++-------
.../report/adapter/StatusViewHolder.kt | 4 +-
.../tusky/util/AbsoluteTimeFormatter.kt | 59 +++++++++++++++++++
.../tusky/util/StatusViewHelper.kt | 22 +------
.../tusky/util/AbsoluteTimeFormatterTest.kt | 46 +++++++++++++++
6 files changed, 127 insertions(+), 61 deletions(-)
create mode 100644 app/src/main/java/com/keylesspalace/tusky/util/AbsoluteTimeFormatter.kt
create mode 100644 app/src/test/java/com/keylesspalace/tusky/util/AbsoluteTimeFormatterTest.kt
diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
index 936f20a3..c1f78b46 100644
--- a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
+++ b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
@@ -47,6 +47,7 @@ import com.keylesspalace.tusky.entity.TimelineAccount;
import com.keylesspalace.tusky.interfaces.AccountActionListener;
import com.keylesspalace.tusky.interfaces.LinkListener;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
+import com.keylesspalace.tusky.util.AbsoluteTimeFormatter;
import com.keylesspalace.tusky.util.CardViewMode;
import com.keylesspalace.tusky.util.CustomEmojiHelper;
import com.keylesspalace.tusky.util.ImageLoadingHelper;
@@ -58,10 +59,8 @@ import com.keylesspalace.tusky.util.TimestampUtils;
import com.keylesspalace.tusky.viewdata.NotificationViewData;
import com.keylesspalace.tusky.viewdata.StatusViewData;
-import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
-import java.util.Locale;
import at.connyduck.sparkbutton.helpers.Utils;
@@ -90,6 +89,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
private NotificationActionListener notificationActionListener;
private AccountActionListener accountActionListener;
private AdapterDataSource dataSource;
+ private final AbsoluteTimeFormatter absoluteTimeFormatter = new AbsoluteTimeFormatter();
public NotificationsAdapter(String accountId,
AdapterDataSource dataSource,
@@ -119,7 +119,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
case VIEW_TYPE_STATUS_NOTIFICATION: {
View view = inflater
.inflate(R.layout.item_status_notification, parent, false);
- return new StatusNotificationViewHolder(view, statusDisplayOptions);
+ return new StatusNotificationViewHolder(view, statusDisplayOptions, absoluteTimeFormatter);
}
case VIEW_TYPE_FOLLOW: {
View view = inflater
@@ -383,19 +383,22 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
private final Button contentWarningButton;
private final Button contentCollapseButton; // TODO: This code SHOULD be based on StatusBaseViewHolder
private StatusDisplayOptions statusDisplayOptions;
+ private final AbsoluteTimeFormatter absoluteTimeFormatter;
private String accountId;
private String notificationId;
private NotificationActionListener notificationActionListener;
private StatusViewData.Concrete statusViewData;
- private SimpleDateFormat shortSdf;
- private SimpleDateFormat longSdf;
private int avatarRadius48dp;
private int avatarRadius36dp;
private int avatarRadius24dp;
- StatusNotificationViewHolder(View itemView, StatusDisplayOptions statusDisplayOptions) {
+ StatusNotificationViewHolder(
+ View itemView,
+ StatusDisplayOptions statusDisplayOptions,
+ AbsoluteTimeFormatter absoluteTimeFormatter
+ ) {
super(itemView);
message = itemView.findViewById(R.id.notification_top_text);
statusNameBar = itemView.findViewById(R.id.status_name_bar);
@@ -409,6 +412,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
contentWarningButton = itemView.findViewById(R.id.notification_content_warning_button);
contentCollapseButton = itemView.findViewById(R.id.button_toggle_notification_content);
this.statusDisplayOptions = statusDisplayOptions;
+ this.absoluteTimeFormatter = absoluteTimeFormatter;
int darkerFilter = Color.rgb(123, 123, 123);
statusAvatar.setColorFilter(darkerFilter, PorterDuff.Mode.MULTIPLY);
@@ -417,8 +421,6 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
itemView.setOnClickListener(this);
message.setOnClickListener(this);
statusContent.setOnClickListener(this);
- shortSdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());
- longSdf = new SimpleDateFormat("MM/dd HH:mm:ss", Locale.getDefault());
this.avatarRadius48dp = itemView.getContext().getResources().getDimensionPixelSize(R.dimen.avatar_radius_48dp);
this.avatarRadius36dp = itemView.getContext().getResources().getDimensionPixelSize(R.dimen.avatar_radius_36dp);
@@ -448,17 +450,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
protected void setCreatedAt(@Nullable Date createdAt) {
if (statusDisplayOptions.useAbsoluteTime()) {
- String time;
- if (createdAt != null) {
- if (System.currentTimeMillis() - createdAt.getTime() > 86400000L) {
- time = longSdf.format(createdAt);
- } else {
- time = shortSdf.format(createdAt);
- }
- } else {
- time = "??:??:??";
- }
- timestampInfo.setText(time);
+ timestampInfo.setText(absoluteTimeFormatter.format(createdAt, true));
} else {
// This is the visible timestampInfo.
String readout;
diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java
index dbca518a..c2729aa5 100644
--- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java
+++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java
@@ -40,6 +40,7 @@ import com.keylesspalace.tusky.entity.Emoji;
import com.keylesspalace.tusky.entity.HashTag;
import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
+import com.keylesspalace.tusky.util.AbsoluteTimeFormatter;
import com.keylesspalace.tusky.util.CardViewMode;
import com.keylesspalace.tusky.util.CustomEmojiHelper;
import com.keylesspalace.tusky.util.ImageLoadingHelper;
@@ -54,10 +55,8 @@ import com.keylesspalace.tusky.viewdata.PollViewDataKt;
import com.keylesspalace.tusky.viewdata.StatusViewData;
import java.text.NumberFormat;
-import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
-import java.util.Locale;
import at.connyduck.sparkbutton.SparkButton;
import at.connyduck.sparkbutton.helpers.Utils;
@@ -103,10 +102,8 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
private TextView cardUrl;
private PollAdapter pollAdapter;
- private SimpleDateFormat shortSdf;
- private SimpleDateFormat longSdf;
-
private final NumberFormat numberFormat = NumberFormat.getNumberInstance();
+ private final AbsoluteTimeFormatter absoluteTimeFormatter = new AbsoluteTimeFormatter();
protected int avatarRadius48dp;
private int avatarRadius36dp;
@@ -170,9 +167,6 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
pollOptions.setLayoutManager(new LinearLayoutManager(pollOptions.getContext()));
((DefaultItemAnimator) pollOptions.getItemAnimator()).setSupportsChangeAnimations(false);
- this.shortSdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());
- this.longSdf = new SimpleDateFormat("MM/dd HH:mm:ss", Locale.getDefault());
-
this.avatarRadius48dp = itemView.getContext().getResources().getDimensionPixelSize(R.dimen.avatar_radius_48dp);
this.avatarRadius36dp = itemView.getContext().getResources().getDimensionPixelSize(R.dimen.avatar_radius_36dp);
this.avatarRadius24dp = itemView.getContext().getResources().getDimensionPixelSize(R.dimen.avatar_radius_24dp);
@@ -320,7 +314,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
protected void setCreatedAt(Date createdAt, StatusDisplayOptions statusDisplayOptions) {
if (statusDisplayOptions.useAbsoluteTime()) {
- timestampInfo.setText(getAbsoluteTime(createdAt));
+ timestampInfo.setText(absoluteTimeFormatter.format(createdAt, true));
} else {
if (createdAt == null) {
timestampInfo.setText("?m");
@@ -333,21 +327,10 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
}
}
- private String getAbsoluteTime(Date createdAt) {
- if (createdAt == null) {
- return "??:??:??";
- }
- if (DateUtils.isToday(createdAt.getTime())) {
- return shortSdf.format(createdAt);
- } else {
- return longSdf.format(createdAt);
- }
- }
-
private CharSequence getCreatedAtDescription(Date createdAt,
StatusDisplayOptions statusDisplayOptions) {
if (statusDisplayOptions.useAbsoluteTime()) {
- return getAbsoluteTime(createdAt);
+ return absoluteTimeFormatter.format(createdAt, true);
} else {
/* This one is for screen-readers. Frequently, they would mispronounce timestamps like "17m"
* as 17 meters instead of minutes. */
@@ -1028,7 +1011,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
return votesText;
} else {
if (statusDisplayOptions.useAbsoluteTime()) {
- pollDurationInfo = context.getString(R.string.poll_info_time_absolute, getAbsoluteTime(poll.getExpiresAt()));
+ pollDurationInfo = context.getString(R.string.poll_info_time_absolute, absoluteTimeFormatter.format(poll.getExpiresAt(), false));
} else {
pollDurationInfo = TimestampUtils.formatPollDuration(pollDescription.getContext(), poll.getExpiresAt().getTime(), timestamp);
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusViewHolder.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusViewHolder.kt
index 9dceddec..82dbf163 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusViewHolder.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusViewHolder.kt
@@ -26,6 +26,7 @@ import com.keylesspalace.tusky.entity.Emoji
import com.keylesspalace.tusky.entity.HashTag
import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.interfaces.LinkListener
+import com.keylesspalace.tusky.util.AbsoluteTimeFormatter
import com.keylesspalace.tusky.util.StatusDisplayOptions
import com.keylesspalace.tusky.util.StatusViewHelper
import com.keylesspalace.tusky.util.StatusViewHelper.Companion.COLLAPSE_INPUT_FILTER
@@ -51,6 +52,7 @@ class StatusViewHolder(
private val mediaViewHeight = itemView.context.resources.getDimensionPixelSize(R.dimen.status_media_preview_height)
private val statusViewHelper = StatusViewHelper(itemView)
+ private val absoluteTimeFormatter = AbsoluteTimeFormatter()
private val previewListener = object : StatusViewHelper.MediaPreviewListener {
override fun onViewMedia(v: View?, idx: Int) {
@@ -154,7 +156,7 @@ class StatusViewHolder(
private fun setCreatedAt(createdAt: Date?) {
if (statusDisplayOptions.useAbsoluteTime) {
- binding.timestampInfo.text = statusViewHelper.getAbsoluteTime(createdAt)
+ binding.timestampInfo.text = absoluteTimeFormatter.format(createdAt)
} else {
binding.timestampInfo.text = if (createdAt != null) {
val then = createdAt.time
diff --git a/app/src/main/java/com/keylesspalace/tusky/util/AbsoluteTimeFormatter.kt b/app/src/main/java/com/keylesspalace/tusky/util/AbsoluteTimeFormatter.kt
new file mode 100644
index 00000000..7d46388b
--- /dev/null
+++ b/app/src/main/java/com/keylesspalace/tusky/util/AbsoluteTimeFormatter.kt
@@ -0,0 +1,59 @@
+/* Copyright 2022 Tusky Contributors
+ *
+ * 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.util
+
+import java.text.SimpleDateFormat
+import java.util.Calendar
+import java.util.Date
+import java.util.Locale
+import java.util.TimeZone
+
+class AbsoluteTimeFormatter @JvmOverloads constructor(private val tz: TimeZone = TimeZone.getDefault()) {
+ private val sameDaySdf = SimpleDateFormat("HH:mm", Locale.getDefault()).apply { this.timeZone = tz }
+ private val sameYearSdf = SimpleDateFormat("MM-dd HH:mm", Locale.getDefault()).apply { this.timeZone = tz }
+ private val otherYearSdf = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).apply { this.timeZone = tz }
+ private val otherYearCompleteSdf = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()).apply { this.timeZone = tz }
+
+ @JvmOverloads
+ fun format(time: Date?, shortFormat: Boolean = true, now: Date = Date()): String {
+ return when {
+ time == null -> "??"
+ isSameDate(time, now, tz) -> sameDaySdf.format(time)
+ isSameYear(time, now, tz) -> sameYearSdf.format(time)
+ shortFormat -> otherYearSdf.format(time)
+ else -> otherYearCompleteSdf.format(time)
+ }
+ }
+
+ companion object {
+
+ private fun isSameDate(dateOne: Date, dateTwo: Date, tz: TimeZone): Boolean {
+ val calendarOne = Calendar.getInstance(tz).apply { time = dateOne }
+ val calendarTwo = Calendar.getInstance(tz).apply { time = dateTwo }
+
+ return calendarOne.get(Calendar.YEAR) == calendarTwo.get(Calendar.YEAR) &&
+ calendarOne.get(Calendar.MONTH) == calendarTwo.get(Calendar.MONTH) &&
+ calendarOne.get(Calendar.DAY_OF_MONTH) == calendarTwo.get(Calendar.DAY_OF_MONTH)
+ }
+
+ private fun isSameYear(dateOne: Date, dateTwo: Date, timeZone1: TimeZone): Boolean {
+ val calendarOne = Calendar.getInstance(timeZone1).apply { time = dateOne }
+ val calendarTwo = Calendar.getInstance(timeZone1).apply { time = dateTwo }
+
+ return calendarOne.get(Calendar.YEAR) == calendarTwo.get(Calendar.YEAR)
+ }
+ }
+}
diff --git a/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt b/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt
index 60ac73f4..0752c4e5 100644
--- a/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt
@@ -34,20 +34,16 @@ import com.keylesspalace.tusky.viewdata.PollViewData
import com.keylesspalace.tusky.viewdata.buildDescription
import com.keylesspalace.tusky.viewdata.calculatePercent
import java.text.NumberFormat
-import java.text.SimpleDateFormat
-import java.util.Date
-import java.util.Locale
import kotlin.math.min
class StatusViewHelper(private val itemView: View) {
+ private val absoluteTimeFormatter = AbsoluteTimeFormatter()
+
interface MediaPreviewListener {
fun onViewMedia(v: View?, idx: Int)
fun onContentHiddenChange(isShowing: Boolean)
}
- private val shortSdf = SimpleDateFormat("HH:mm:ss", Locale.getDefault())
- private val longSdf = SimpleDateFormat("MM/dd HH:mm:ss", Locale.getDefault())
-
fun setMediasPreview(
statusDisplayOptions: StatusDisplayOptions,
attachments: List,
@@ -295,7 +291,7 @@ class StatusViewHelper(private val itemView: View) {
context.getString(R.string.poll_info_closed)
} else {
if (useAbsoluteTime) {
- context.getString(R.string.poll_info_time_absolute, getAbsoluteTime(poll.expiresAt))
+ context.getString(R.string.poll_info_time_absolute, absoluteTimeFormatter.format(poll.expiresAt, false))
} else {
TimestampUtils.formatPollDuration(context, poll.expiresAt!!.time, timestamp)
}
@@ -330,18 +326,6 @@ class StatusViewHelper(private val itemView: View) {
}
}
- fun getAbsoluteTime(time: Date?): String {
- return if (time != null) {
- if (android.text.format.DateUtils.isToday(time.time)) {
- shortSdf.format(time)
- } else {
- longSdf.format(time)
- }
- } else {
- "??:??:??"
- }
- }
-
companion object {
val COLLAPSE_INPUT_FILTER = arrayOf(SmartLengthInputFilter)
val NO_INPUT_FILTER = arrayOfNulls(0)
diff --git a/app/src/test/java/com/keylesspalace/tusky/util/AbsoluteTimeFormatterTest.kt b/app/src/test/java/com/keylesspalace/tusky/util/AbsoluteTimeFormatterTest.kt
new file mode 100644
index 00000000..57f3bed4
--- /dev/null
+++ b/app/src/test/java/com/keylesspalace/tusky/util/AbsoluteTimeFormatterTest.kt
@@ -0,0 +1,46 @@
+package com.keylesspalace.tusky.util
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import java.time.Instant
+import java.util.Date
+import java.util.TimeZone
+
+class AbsoluteTimeFormatterTest {
+
+ private val formatter = AbsoluteTimeFormatter(TimeZone.getTimeZone("UTC"))
+ private val now = Date.from(Instant.parse("2022-04-11T00:00:00.00Z"))
+
+ @Test
+ fun `null handling`() {
+ assertEquals("??", formatter.format(null, true, now))
+ assertEquals("??", formatter.format(null, false, now))
+ }
+
+ @Test
+ fun `same day formatting`() {
+ val tenTen = Date.from(Instant.parse("2022-04-11T10:10:00.00Z"))
+ assertEquals("10:10", formatter.format(tenTen, true, now))
+ assertEquals("10:10", formatter.format(tenTen, false, now))
+ }
+
+ @Test
+ fun `same year formatting`() {
+ val nextDay = Date.from(Instant.parse("2022-04-12T00:10:00.00Z"))
+ assertEquals("04-12 00:10", formatter.format(nextDay, true, now))
+ assertEquals("04-12 00:10", formatter.format(nextDay, false, now))
+ val endOfYear = Date.from(Instant.parse("2022-12-31T23:59:00.00Z"))
+ assertEquals("12-31 23:59", formatter.format(endOfYear, true, now))
+ assertEquals("12-31 23:59", formatter.format(endOfYear, false, now))
+ }
+
+ @Test
+ fun `other year formatting`() {
+ val firstDayNextYear = Date.from(Instant.parse("2023-01-01T00:00:00.00Z"))
+ assertEquals("2023-01-01", formatter.format(firstDayNextYear, true, now))
+ assertEquals("2023-01-01 00:00", formatter.format(firstDayNextYear, false, now))
+ val inTenYears = Date.from(Instant.parse("2032-04-11T10:10:00.00Z"))
+ assertEquals("2032-04-11", formatter.format(inTenYears, true, now))
+ assertEquals("2032-04-11 10:10", formatter.format(inTenYears, false, now))
+ }
+}
From dff039e123166c69e16cccb27afe429682289052 Mon Sep 17 00:00:00 2001
From: Levi Bard
Date: Tue, 19 Apr 2022 11:10:13 +0200
Subject: [PATCH 011/104] Add support for post edit notifications (#2431)
* Add support for post edit notifications
* Update notification icon
---
.../34.json | 815 ++++++++++++++++++
.../tusky/adapter/NotificationsAdapter.java | 42 +-
.../notifications/NotificationHelper.java | 8 +
.../NotificationPreferencesFragment.kt | 11 +
.../keylesspalace/tusky/db/AccountEntity.kt | 1 +
.../keylesspalace/tusky/db/AppDatabase.java | 9 +-
.../com/keylesspalace/tusky/di/AppModule.kt | 2 +-
.../tusky/entity/Notification.kt | 3 +-
.../tusky/fragment/NotificationsFragment.java | 2 +
.../tusky/settings/SettingsConstants.kt | 1 +
app/src/main/res/drawable/ic_edit_24dp.xml | 10 +
app/src/main/res/values/strings.xml | 4 +
12 files changed, 885 insertions(+), 23 deletions(-)
create mode 100644 app/schemas/com.keylesspalace.tusky.db.AppDatabase/34.json
create mode 100644 app/src/main/res/drawable/ic_edit_24dp.xml
diff --git a/app/schemas/com.keylesspalace.tusky.db.AppDatabase/34.json b/app/schemas/com.keylesspalace.tusky.db.AppDatabase/34.json
new file mode 100644
index 00000000..c1354690
--- /dev/null
+++ b/app/schemas/com.keylesspalace.tusky.db.AppDatabase/34.json
@@ -0,0 +1,815 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 34,
+ "identityHash": "7f766d68ab5d72a7988cd81c183e9a9d",
+ "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)",
+ "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
+ }
+ ],
+ "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, `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, `notificationSound` INTEGER NOT NULL, `notificationVibration` INTEGER NOT NULL, `notificationLight` INTEGER NOT NULL, `defaultPostPrivacy` INTEGER NOT NULL, `defaultMediaSensitivity` INTEGER NOT NULL, `alwaysShowSensitiveMedia` INTEGER NOT NULL, `alwaysOpenSpoiler` INTEGER NOT NULL, `mediaPreviewEnabled` INTEGER NOT NULL, `lastNotificationId` TEXT NOT NULL, `activeNotifications` TEXT NOT NULL, `emojis` TEXT NOT NULL, `tabPreferences` TEXT NOT NULL, `notificationsFilter` TEXT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "domain",
+ "columnName": "domain",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "accessToken",
+ "columnName": "accessToken",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isActive",
+ "columnName": "isActive",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "accountId",
+ "columnName": "accountId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "username",
+ "columnName": "username",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "displayName",
+ "columnName": "displayName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "profilePictureUrl",
+ "columnName": "profilePictureUrl",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notificationsEnabled",
+ "columnName": "notificationsEnabled",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notificationsMentioned",
+ "columnName": "notificationsMentioned",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notificationsFollowed",
+ "columnName": "notificationsFollowed",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "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": "notificationSound",
+ "columnName": "notificationSound",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notificationVibration",
+ "columnName": "notificationVibration",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notificationLight",
+ "columnName": "notificationLight",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "defaultPostPrivacy",
+ "columnName": "defaultPostPrivacy",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "defaultMediaSensitivity",
+ "columnName": "defaultMediaSensitivity",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "alwaysShowSensitiveMedia",
+ "columnName": "alwaysShowSensitiveMedia",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "alwaysOpenSpoiler",
+ "columnName": "alwaysOpenSpoiler",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "mediaPreviewEnabled",
+ "columnName": "mediaPreviewEnabled",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastNotificationId",
+ "columnName": "lastNotificationId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "activeNotifications",
+ "columnName": "activeNotifications",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "emojis",
+ "columnName": "emojis",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "tabPreferences",
+ "columnName": "tabPreferences",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notificationsFilter",
+ "columnName": "notificationsFilter",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "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, 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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "instance"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "TimelineStatusEntity",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serverId` TEXT NOT NULL, `url` TEXT, `timelineUserId` INTEGER NOT NULL, `authorServerId` TEXT, `inReplyToId` TEXT, `inReplyToAccountId` TEXT, `content` TEXT, `createdAt` INTEGER NOT NULL, `emojis` TEXT, `reblogsCount` INTEGER NOT NULL, `favouritesCount` INTEGER NOT NULL, `reblogged` INTEGER NOT NULL, `bookmarked` INTEGER NOT NULL, `favourited` INTEGER NOT NULL, `sensitive` INTEGER NOT NULL, `spoilerText` TEXT 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, PRIMARY KEY(`serverId`, `timelineUserId`), FOREIGN KEY(`authorServerId`, `timelineUserId`) REFERENCES `TimelineAccountEntity`(`serverId`, `timelineUserId`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
+ "fields": [
+ {
+ "fieldPath": "serverId",
+ "columnName": "serverId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "url",
+ "columnName": "url",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "timelineUserId",
+ "columnName": "timelineUserId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "authorServerId",
+ "columnName": "authorServerId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "inReplyToId",
+ "columnName": "inReplyToId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "inReplyToAccountId",
+ "columnName": "inReplyToAccountId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "createdAt",
+ "columnName": "createdAt",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "emojis",
+ "columnName": "emojis",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "reblogsCount",
+ "columnName": "reblogsCount",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "favouritesCount",
+ "columnName": "favouritesCount",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "reblogged",
+ "columnName": "reblogged",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "bookmarked",
+ "columnName": "bookmarked",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "favourited",
+ "columnName": "favourited",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "sensitive",
+ "columnName": "sensitive",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "spoilerText",
+ "columnName": "spoilerText",
+ "affinity": "TEXT",
+ "notNull": 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
+ }
+ ],
+ "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, `accounts` TEXT NOT NULL, `unread` INTEGER NOT NULL, `s_id` TEXT NOT NULL, `s_url` TEXT, `s_inReplyToId` TEXT, `s_inReplyToAccountId` TEXT, `s_account` TEXT NOT NULL, `s_content` TEXT NOT NULL, `s_createdAt` INTEGER NOT NULL, `s_emojis` TEXT NOT NULL, `s_favouritesCount` INTEGER NOT NULL, `s_favourited` INTEGER NOT NULL, `s_bookmarked` INTEGER NOT NULL, `s_sensitive` INTEGER NOT NULL, `s_spoilerText` TEXT NOT NULL, `s_attachments` TEXT NOT NULL, `s_mentions` TEXT NOT NULL, `s_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, PRIMARY KEY(`id`, `accountId`))",
+ "fields": [
+ {
+ "fieldPath": "accountId",
+ "columnName": "accountId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "accounts",
+ "columnName": "accounts",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "unread",
+ "columnName": "unread",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.id",
+ "columnName": "s_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.url",
+ "columnName": "s_url",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastStatus.inReplyToId",
+ "columnName": "s_inReplyToId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastStatus.inReplyToAccountId",
+ "columnName": "s_inReplyToAccountId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastStatus.account",
+ "columnName": "s_account",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.content",
+ "columnName": "s_content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.createdAt",
+ "columnName": "s_createdAt",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.emojis",
+ "columnName": "s_emojis",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.favouritesCount",
+ "columnName": "s_favouritesCount",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.favourited",
+ "columnName": "s_favourited",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.bookmarked",
+ "columnName": "s_bookmarked",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.sensitive",
+ "columnName": "s_sensitive",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.spoilerText",
+ "columnName": "s_spoilerText",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.attachments",
+ "columnName": "s_attachments",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.mentions",
+ "columnName": "s_mentions",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.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
+ }
+ ],
+ "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, '7f766d68ab5d72a7988cd81c183e9a9d')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
index c1f78b46..0ac029b0 100644
--- a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
+++ b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
@@ -32,6 +32,8 @@ import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.annotation.ColorRes;
+import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
@@ -201,7 +203,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
holder.setUsername(status.getAccount().getUsername());
holder.setCreatedAt(status.getCreatedAt());
- if (concreteNotificaton.getType() == Notification.Type.STATUS) {
+ if (concreteNotificaton.getType() == Notification.Type.STATUS ||
+ concreteNotificaton.getType() == Notification.Type.UPDATE) {
holder.setAvatar(status.getAccount().getAvatar(), status.getAccount().getBot());
} else {
holder.setAvatars(status.getAccount().getAvatar(),
@@ -280,7 +283,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
}
case STATUS:
case FAVOURITE:
- case REBLOG: {
+ case REBLOG:
+ case UPDATE: {
return VIEW_TYPE_STATUS_NOTIFICATION;
}
case FOLLOW:
@@ -474,6 +478,14 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
}
}
+ Drawable getIconWithColor(Context context, @DrawableRes int drawable, @ColorRes int color) {
+ Drawable icon = ContextCompat.getDrawable(context, drawable);
+ if (icon != null) {
+ icon.setColorFilter(ContextCompat.getColor(context, color), PorterDuff.Mode.SRC_ATOP);
+ }
+ return icon;
+ }
+
void setMessage(NotificationViewData.Concrete notificationViewData, LinkListener listener) {
this.statusViewData = notificationViewData.getStatusViewData();
@@ -486,35 +498,25 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
switch (type) {
default:
case FAVOURITE: {
- icon = ContextCompat.getDrawable(context, R.drawable.ic_star_24dp);
- if (icon != null) {
- icon.setColorFilter(ContextCompat.getColor(context,
- R.color.tusky_orange), PorterDuff.Mode.SRC_ATOP);
- }
-
+ icon = getIconWithColor(context, R.drawable.ic_star_24dp, R.color.tusky_orange);
format = context.getString(R.string.notification_favourite_format);
break;
}
case REBLOG: {
- icon = ContextCompat.getDrawable(context, R.drawable.ic_repeat_24dp);
- if (icon != null) {
- icon.setColorFilter(ContextCompat.getColor(context,
- R.color.tusky_blue), PorterDuff.Mode.SRC_ATOP);
- }
-
+ icon = getIconWithColor(context, R.drawable.ic_repeat_24dp, R.color.tusky_blue);
format = context.getString(R.string.notification_reblog_format);
break;
}
case STATUS: {
- icon = ContextCompat.getDrawable(context, R.drawable.ic_home_24dp);
- if (icon != null) {
- icon.setColorFilter(ContextCompat.getColor(context,
- R.color.tusky_blue), PorterDuff.Mode.SRC_ATOP);
- }
-
+ icon = getIconWithColor(context, R.drawable.ic_home_24dp, R.color.tusky_blue);
format = context.getString(R.string.notification_subscription_format);
break;
}
+ case UPDATE: {
+ icon = getIconWithColor(context, R.drawable.ic_edit_24dp, R.color.tusky_blue);
+ format = context.getString(R.string.notification_update_format);
+ break;
+ }
}
message.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
String wholeMessage = String.format(format, displayName);
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java
index 83682ab2..79586897 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java
+++ b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java
@@ -118,6 +118,7 @@ public class NotificationHelper {
public static final String CHANNEL_POLL = "CHANNEL_POLL";
public static final String CHANNEL_SUBSCRIPTIONS = "CHANNEL_SUBSCRIPTIONS";
public static final String CHANNEL_SIGN_UP = "CHANNEL_SIGN_UP";
+ public static final String CHANNEL_UPDATES = "CHANNEL_UPDATES";
/**
* WorkManager Tag
@@ -395,6 +396,7 @@ public class NotificationHelper {
CHANNEL_POLL + account.getIdentifier(),
CHANNEL_SUBSCRIPTIONS + account.getIdentifier(),
CHANNEL_SIGN_UP + account.getIdentifier(),
+ CHANNEL_UPDATES + account.getIdentifier(),
};
int[] channelNames = {
R.string.notification_mention_name,
@@ -405,6 +407,7 @@ public class NotificationHelper {
R.string.notification_poll_name,
R.string.notification_subscription_name,
R.string.notification_sign_up_name,
+ R.string.notification_update_name,
};
int[] channelDescriptions = {
R.string.notification_mention_descriptions,
@@ -415,6 +418,7 @@ public class NotificationHelper {
R.string.notification_poll_description,
R.string.notification_subscription_description,
R.string.notification_sign_up_description,
+ R.string.notification_update_description,
};
List channels = new ArrayList<>(6);
@@ -567,6 +571,8 @@ public class NotificationHelper {
return account.getNotificationsPolls();
case SIGN_UP:
return account.getNotificationsSignUps();
+ case UPDATE:
+ return account.getNotificationsUpdates();
default:
return false;
}
@@ -674,6 +680,8 @@ public class NotificationHelper {
}
case SIGN_UP:
return String.format(context.getString(R.string.notification_sign_up_format), accountName);
+ case UPDATE:
+ return String.format(context.getString(R.string.notification_update_format), accountName);
}
return null;
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/NotificationPreferencesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/NotificationPreferencesFragment.kt
index 82ee0a38..6fdc1e8a 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/preference/NotificationPreferencesFragment.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/NotificationPreferencesFragment.kt
@@ -133,6 +133,17 @@ class NotificationPreferencesFragment : PreferenceFragmentCompat(), Injectable {
true
}
}
+
+ switchPreference {
+ setTitle(R.string.pref_title_notification_filter_updates)
+ key = PrefKeys.NOTIFICATION_FILTER_UPDATES
+ isIconSpaceReserved = false
+ isChecked = activeAccount.notificationsUpdates
+ setOnPreferenceChangeListener { _, newValue ->
+ updateAccount { it.notificationsUpdates = newValue as Boolean }
+ true
+ }
+ }
}
preferenceCategory(R.string.pref_title_notification_alerts) { category ->
diff --git a/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt b/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt
index 5da91e20..400eb073 100644
--- a/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt
@@ -51,6 +51,7 @@ data class AccountEntity(
var notificationsPolls: Boolean = true,
var notificationsSubscriptions: Boolean = true,
var notificationsSignUps: Boolean = true,
+ var notificationsUpdates: Boolean = true,
var notificationSound: Boolean = true,
var notificationVibration: Boolean = true,
var notificationLight: Boolean = true,
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 c541958a..293db65e 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 = 33)
+ }, version = 34)
public abstract class AppDatabase extends RoomDatabase {
public abstract AccountDao accountDao();
@@ -527,4 +527,11 @@ public abstract class AppDatabase extends RoomDatabase {
"PRIMARY KEY(`id`, `accountId`))");
}
};
+
+ public static final Migration MIGRATION_33_34 = new Migration(33, 34) {
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `notificationsUpdates` INTEGER NOT NULL DEFAULT 1");
+ }
+ };
}
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 7f0fbd01..c92d52ef 100644
--- a/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt
@@ -63,7 +63,7 @@ class AppModule {
AppDatabase.Migration25_26(appContext.getExternalFilesDir("Tusky")),
AppDatabase.MIGRATION_26_27, AppDatabase.MIGRATION_27_28, AppDatabase.MIGRATION_28_29,
AppDatabase.MIGRATION_29_30, AppDatabase.MIGRATION_30_31, AppDatabase.MIGRATION_31_32,
- AppDatabase.MIGRATION_32_33
+ AppDatabase.MIGRATION_32_33, AppDatabase.MIGRATION_33_34
)
.build()
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Notification.kt b/app/src/main/java/com/keylesspalace/tusky/entity/Notification.kt
index ddcf5e61..f6e38150 100644
--- a/app/src/main/java/com/keylesspalace/tusky/entity/Notification.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/entity/Notification.kt
@@ -39,6 +39,7 @@ data class Notification(
POLL("poll"),
STATUS("status"),
SIGN_UP("admin.sign_up"),
+ UPDATE("update"),
;
companion object {
@@ -51,7 +52,7 @@ data class Notification(
}
return UNKNOWN
}
- val asList = listOf(MENTION, REBLOG, FAVOURITE, FOLLOW, FOLLOW_REQUEST, POLL, STATUS, SIGN_UP)
+ val asList = listOf(MENTION, REBLOG, FAVOURITE, FOLLOW, FOLLOW_REQUEST, POLL, STATUS, SIGN_UP, UPDATE)
}
override fun toString(): String {
diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java
index f267f29e..94ee496e 100644
--- a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java
+++ b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java
@@ -709,6 +709,8 @@ public class NotificationsFragment extends SFragment implements
return getString(R.string.notification_subscription_name);
case SIGN_UP:
return getString(R.string.notification_sign_up_name);
+ case UPDATE:
+ return getString(R.string.notification_update_name);
default:
return "Unknown";
}
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 c728e1f6..6540601a 100644
--- a/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt
@@ -60,6 +60,7 @@ object PrefKeys {
const val NOTIFICATIONS_FILTER_FOLLOWS = "notificationFilterFollows"
const val NOTIFICATION_FILTER_SUBSCRIPTIONS = "notificationFilterSubscriptions"
const val NOTIFICATION_FILTER_SIGN_UPS = "notificationFilterSignUps"
+ const val NOTIFICATION_FILTER_UPDATES = "notificationFilterUpdates"
const val TAB_FILTER_HOME_REPLIES = "tabFilterHomeReplies"
const val TAB_FILTER_HOME_BOOSTS = "tabFilterHomeBoosts"
diff --git a/app/src/main/res/drawable/ic_edit_24dp.xml b/app/src/main/res/drawable/ic_edit_24dp.xml
new file mode 100644
index 00000000..2844bafe
--- /dev/null
+++ b/app/src/main/res/drawable/ic_edit_24dp.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e6ce2338..e007c50e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -64,6 +64,7 @@
%s requested to follow you
%s signed up
%s just posted
+ %s edited their post
Report @%s
Additional comments?
@@ -230,6 +231,7 @@
polls have ended
somebody I\'m subscribed to published a new post
somebody signed up
+ a post I\'ve interacted with is edited
Appearance
App Theme
Timelines
@@ -299,6 +301,8 @@
Notifications when somebody you\'re subscribed to published a new post
Sign ups
Notifications about new users
+ Post edits
+ Notifications when posts you\'ve interacted with are edited
%s mentioned you
%1$s, %2$s, %3$s and %4$d others
From b5d8b730443698273219d92282c025e9360c4dbb Mon Sep 17 00:00:00 2001
From: Konrad Pozniak
Date: Tue, 19 Apr 2022 11:10:23 +0200
Subject: [PATCH 012/104] fix "account moved" on profiles not being clickable
(#2438)
---
.../components/account/AccountActivity.kt | 9 --
app/src/main/res/drawable/ic_briefcase.xml | 2 +-
app/src/main/res/layout/activity_account.xml | 112 ++++++++++--------
3 files changed, 61 insertions(+), 62 deletions(-)
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt
index 114a6cd0..ddecd20c 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt
@@ -20,8 +20,6 @@ import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Color
-import android.graphics.PorterDuff
-import android.graphics.PorterDuffColorFilter
import android.os.Bundle
import android.text.Editable
import android.view.Menu
@@ -499,13 +497,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
loadAvatar(movedAccount.avatar, binding.accountMovedAvatar, avatarRadius, animateAvatar)
binding.accountMovedText.text = getString(R.string.account_moved_description, movedAccount.name)
-
- // this is necessary because API 19 can't handle vector compound drawables
- val movedIcon = ContextCompat.getDrawable(this, R.drawable.ic_briefcase)?.mutate()
- val textColor = ThemeUtils.getColor(this, android.R.attr.textColorTertiary)
- movedIcon?.colorFilter = PorterDuffColorFilter(textColor, PorterDuff.Mode.SRC_IN)
-
- binding.accountMovedText.setCompoundDrawablesRelativeWithIntrinsicBounds(movedIcon, null, null, null)
}
}
diff --git a/app/src/main/res/drawable/ic_briefcase.xml b/app/src/main/res/drawable/ic_briefcase.xml
index eeb80619..6df5b88b 100644
--- a/app/src/main/res/drawable/ic_briefcase.xml
+++ b/app/src/main/res/drawable/ic_briefcase.xml
@@ -4,6 +4,6 @@
android:viewportHeight="24"
android:viewportWidth="24">
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_account.xml b/app/src/main/res/layout/activity_account.xml
index 57064412..89a56d9e 100644
--- a/app/src/main/res/layout/activity_account.xml
+++ b/app/src/main/res/layout/activity_account.xml
@@ -248,63 +248,71 @@
app:layout_constraintTop_toBottomOf="@id/accountFieldList"
tools:visibility="visible" />
-
-
-
+ tools:visibility="visible">
-
+
-
+
-
+
+
+
+
+
+ app:layout_constraintTop_toBottomOf="@id/accountMovedView">
+ app:layout_constraintTop_toBottomOf="@id/accountMovedView">
+ app:layout_constraintTop_toBottomOf="@id/accountMovedView">
Date: Fri, 15 Apr 2022 20:40:27 +0000
Subject: [PATCH 013/104] Translated using Weblate (French)
Currently translated at 100.0% (16 of 16 strings)
Translation: Tusky/Tusky description
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky-app/fr/
---
fastlane/metadata/android/fr/changelogs/89.txt | 7 +++++++
1 file changed, 7 insertions(+)
create mode 100644 fastlane/metadata/android/fr/changelogs/89.txt
diff --git a/fastlane/metadata/android/fr/changelogs/89.txt b/fastlane/metadata/android/fr/changelogs/89.txt
new file mode 100644
index 00000000..9c8ae3b9
--- /dev/null
+++ b/fastlane/metadata/android/fr/changelogs/89.txt
@@ -0,0 +1,7 @@
+Tusky v17.0
+
+- L'option « Ouvrir comme… » disponible quand plusieurs comptes sont connectés est maintenant aussi accessible depuis le menu sur les profils
+- L'identification se fait maintenant par une WebView dans l'application
+- Android 12 est pris en charge
+- La nouvelle API Mastodon de configuration d'instance est prise en charge
+- et beaucoup d'autres petites corrections et améliorations
From d2bfceae7bf232a07157a9f60571d7e5ae89225a Mon Sep 17 00:00:00 2001
From: Konrad Pozniak
Date: Thu, 21 Apr 2022 18:46:21 +0200
Subject: [PATCH 014/104] refactor compose & announcements to coroutines
(#2446)
* refactor compose & announcements to coroutines
* fix code formatting
* add javadoc to InstanceInfoRepository
* fix comments in ImageDownsizer
* remove unused Either extensions
* add explicit return type for InstanceInfoRepository.getEmojis
* make ComposeViewModel.pickMedia return Result
* cleanup code in ImageDownsizer
---
app/build.gradle | 1 -
.../com/keylesspalace/tusky/MainActivity.kt | 24 +-
.../announcements/AnnouncementsViewModel.kt | 264 ++++++++----------
.../components/compose/ComposeActivity.kt | 37 +--
.../components/compose/ComposeViewModel.kt | 205 ++++----------
.../components/compose/DownsizeImageTask.java | 154 ----------
.../components/compose/ImageDownsizer.kt | 101 +++++++
.../tusky/components/compose/MediaUploader.kt | 204 +++++++-------
.../compose/dialog/CaptionDialog.kt | 15 +-
.../components/instanceinfo/InstanceInfo.kt | 26 ++
.../instanceinfo/InstanceInfoRepository.kt | 104 +++++++
.../com/keylesspalace/tusky/db/InstanceDao.kt | 14 +-
.../keylesspalace/tusky/db/InstanceEntity.kt | 19 +-
.../tusky/network/MastodonApi.kt | 26 +-
.../tusky/ComposeActivityTest.kt | 30 +-
15 files changed, 596 insertions(+), 628 deletions(-)
delete mode 100644 app/src/main/java/com/keylesspalace/tusky/components/compose/DownsizeImageTask.java
create mode 100644 app/src/main/java/com/keylesspalace/tusky/components/compose/ImageDownsizer.kt
create mode 100644 app/src/main/java/com/keylesspalace/tusky/components/instanceinfo/InstanceInfo.kt
create mode 100644 app/src/main/java/com/keylesspalace/tusky/components/instanceinfo/InstanceInfoRepository.kt
diff --git a/app/build.gradle b/app/build.gradle
index 6ed56f42..02d7907d 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -124,7 +124,6 @@ dependencies {
implementation "androidx.work:work-runtime:2.7.1"
implementation "androidx.room:room-ktx:$roomVersion"
implementation "androidx.room:room-paging:$roomVersion"
- implementation "androidx.room:room-rxjava3:$roomVersion"
kapt "androidx.room:room-compiler:$roomVersion"
implementation 'androidx.core:core-splashscreen:1.0.0-beta02'
diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt
index a934ff9a..3f559399 100644
--- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt
@@ -779,18 +779,18 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
}
private fun fetchAnnouncements() {
- mastodonApi.listAnnouncements(false)
- .observeOn(AndroidSchedulers.mainThread())
- .autoDispose(this, Lifecycle.Event.ON_DESTROY)
- .subscribe(
- { announcements ->
- unreadAnnouncementsCount = announcements.count { !it.read }
- updateAnnouncementsBadge()
- },
- {
- Log.w(TAG, "Failed to fetch announcements.", it)
- }
- )
+ lifecycleScope.launch {
+ mastodonApi.listAnnouncements(false)
+ .fold(
+ { announcements ->
+ unreadAnnouncementsCount = announcements.count { !it.read }
+ updateAnnouncementsBadge()
+ },
+ { throwable ->
+ Log.w(TAG, "Failed to fetch announcements.", throwable)
+ }
+ )
+ }
}
private fun updateAnnouncementsBadge() {
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsViewModel.kt
index 10dc303f..0934c48f 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsViewModel.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsViewModel.kt
@@ -18,32 +18,26 @@ package com.keylesspalace.tusky.components.announcements
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
import com.keylesspalace.tusky.appstore.AnnouncementReadEvent
import com.keylesspalace.tusky.appstore.EventHub
-import com.keylesspalace.tusky.components.compose.DEFAULT_MAXIMUM_URL_LENGTH
-import com.keylesspalace.tusky.db.AccountManager
-import com.keylesspalace.tusky.db.AppDatabase
-import com.keylesspalace.tusky.db.InstanceEntity
+import com.keylesspalace.tusky.components.instanceinfo.InstanceInfoRepository
import com.keylesspalace.tusky.entity.Announcement
import com.keylesspalace.tusky.entity.Emoji
-import com.keylesspalace.tusky.entity.Instance
import com.keylesspalace.tusky.network.MastodonApi
-import com.keylesspalace.tusky.util.Either
import com.keylesspalace.tusky.util.Error
import com.keylesspalace.tusky.util.Loading
import com.keylesspalace.tusky.util.Resource
-import com.keylesspalace.tusky.util.RxAwareViewModel
import com.keylesspalace.tusky.util.Success
-import io.reactivex.rxjava3.core.Single
-import kotlinx.coroutines.rx3.rxSingle
+import kotlinx.coroutines.launch
import javax.inject.Inject
class AnnouncementsViewModel @Inject constructor(
- accountManager: AccountManager,
- private val appDatabase: AppDatabase,
+ private val instanceInfoRepo: InstanceInfoRepository,
private val mastodonApi: MastodonApi,
private val eventHub: EventHub
-) : RxAwareViewModel() {
+) : ViewModel() {
private val announcementsMutable = MutableLiveData>>()
val announcements: LiveData>> = announcementsMutable
@@ -52,156 +46,130 @@ class AnnouncementsViewModel @Inject constructor(
val emojis: LiveData> = emojisMutable
init {
- Single.zip(
- mastodonApi.getCustomEmojis(),
- appDatabase.instanceDao().loadMetadataForInstance(accountManager.activeAccount?.domain!!)
- .map> { Either.Left(it) }
- .onErrorResumeNext {
- rxSingle {
- mastodonApi.getInstance().getOrThrow()
- }.map { Either.Right(it) }
- }
- ) { emojis, either ->
- either.asLeftOrNull()?.copy(emojiList = emojis)
- ?: InstanceEntity(
- accountManager.activeAccount?.domain!!,
- emojis,
- either.asRight().configuration?.statuses?.maxCharacters ?: either.asRight().maxTootChars,
- either.asRight().configuration?.polls?.maxOptions ?: either.asRight().pollConfiguration?.maxOptions,
- either.asRight().configuration?.polls?.maxCharactersPerOption ?: either.asRight().pollConfiguration?.maxOptionChars,
- either.asRight().configuration?.polls?.minExpiration ?: either.asRight().pollConfiguration?.minExpiration,
- either.asRight().configuration?.polls?.maxExpiration ?: either.asRight().pollConfiguration?.maxExpiration,
- either.asRight().configuration?.statuses?.charactersReservedPerUrl ?: DEFAULT_MAXIMUM_URL_LENGTH,
- either.asRight().version
- )
+ viewModelScope.launch {
+ emojisMutable.postValue(instanceInfoRepo.getEmojis())
}
- .doOnSuccess {
- appDatabase.instanceDao().insertOrReplace(it)
- }
- .subscribe(
- {
- emojisMutable.postValue(it.emojiList.orEmpty())
- },
- {
- Log.w(TAG, "Failed to get custom emojis.", it)
- }
- )
- .autoDispose()
}
fun load() {
- announcementsMutable.postValue(Loading())
- mastodonApi.listAnnouncements()
- .subscribe(
- {
- announcementsMutable.postValue(Success(it))
- it.filter { announcement -> !announcement.read }
- .forEach { announcement ->
- mastodonApi.dismissAnnouncement(announcement.id)
- .subscribe(
- {
- eventHub.dispatch(AnnouncementReadEvent(announcement.id))
- },
- { throwable ->
- Log.d(TAG, "Failed to mark announcement as read.", throwable)
- }
- )
- .autoDispose()
- }
- },
- {
- announcementsMutable.postValue(Error(cause = it))
- }
- )
- .autoDispose()
+ viewModelScope.launch {
+ announcementsMutable.postValue(Loading())
+ mastodonApi.listAnnouncements()
+ .fold(
+ {
+ announcementsMutable.postValue(Success(it))
+ it.filter { announcement -> !announcement.read }
+ .forEach { announcement ->
+ mastodonApi.dismissAnnouncement(announcement.id)
+ .fold(
+ {
+ eventHub.dispatch(AnnouncementReadEvent(announcement.id))
+ },
+ { throwable ->
+ Log.d(
+ TAG,
+ "Failed to mark announcement as read.",
+ throwable
+ )
+ }
+ )
+ }
+ },
+ {
+ announcementsMutable.postValue(Error(cause = it))
+ }
+ )
+ }
}
fun addReaction(announcementId: String, name: String) {
- mastodonApi.addAnnouncementReaction(announcementId, name)
- .subscribe(
- {
- announcementsMutable.postValue(
- Success(
- announcements.value!!.data!!.map { announcement ->
- if (announcement.id == announcementId) {
- announcement.copy(
- reactions = if (announcement.reactions.find { reaction -> reaction.name == name } != null) {
- announcement.reactions.map { reaction ->
+ viewModelScope.launch {
+ mastodonApi.addAnnouncementReaction(announcementId, name)
+ .fold(
+ {
+ announcementsMutable.postValue(
+ Success(
+ announcements.value!!.data!!.map { announcement ->
+ if (announcement.id == announcementId) {
+ announcement.copy(
+ reactions = if (announcement.reactions.find { reaction -> reaction.name == name } != null) {
+ announcement.reactions.map { reaction ->
+ if (reaction.name == name) {
+ reaction.copy(
+ count = reaction.count + 1,
+ me = true
+ )
+ } else {
+ reaction
+ }
+ }
+ } else {
+ listOf(
+ *announcement.reactions.toTypedArray(),
+ emojis.value!!.find { emoji -> emoji.shortcode == name }
+ !!.run {
+ Announcement.Reaction(
+ name,
+ 1,
+ true,
+ url,
+ staticUrl
+ )
+ }
+ )
+ }
+ )
+ } else {
+ announcement
+ }
+ }
+ )
+ )
+ },
+ {
+ Log.w(TAG, "Failed to add reaction to the announcement.", it)
+ }
+ )
+ }
+ }
+
+ fun removeReaction(announcementId: String, name: String) {
+ viewModelScope.launch {
+ mastodonApi.removeAnnouncementReaction(announcementId, name)
+ .fold(
+ {
+ announcementsMutable.postValue(
+ Success(
+ announcements.value!!.data!!.map { announcement ->
+ if (announcement.id == announcementId) {
+ announcement.copy(
+ reactions = announcement.reactions.mapNotNull { reaction ->
if (reaction.name == name) {
- reaction.copy(
- count = reaction.count + 1,
- me = true
- )
+ if (reaction.count > 1) {
+ reaction.copy(
+ count = reaction.count - 1,
+ me = false
+ )
+ } else {
+ null
+ }
} else {
reaction
}
}
- } else {
- listOf(
- *announcement.reactions.toTypedArray(),
- emojis.value!!.find { emoji -> emoji.shortcode == name }
- !!.run {
- Announcement.Reaction(
- name,
- 1,
- true,
- url,
- staticUrl
- )
- }
- )
- }
- )
- } else {
- announcement
+ )
+ } else {
+ announcement
+ }
}
- }
+ )
)
- )
- },
- {
- Log.w(TAG, "Failed to add reaction to the announcement.", it)
- }
- )
- .autoDispose()
- }
-
- fun removeReaction(announcementId: String, name: String) {
- mastodonApi.removeAnnouncementReaction(announcementId, name)
- .subscribe(
- {
- announcementsMutable.postValue(
- Success(
- announcements.value!!.data!!.map { announcement ->
- if (announcement.id == announcementId) {
- announcement.copy(
- reactions = announcement.reactions.mapNotNull { reaction ->
- if (reaction.name == name) {
- if (reaction.count > 1) {
- reaction.copy(
- count = reaction.count - 1,
- me = false
- )
- } else {
- null
- }
- } else {
- reaction
- }
- }
- )
- } else {
- announcement
- }
- }
- )
- )
- },
- {
- Log.w(TAG, "Failed to remove reaction from the announcement.", it)
- }
- )
- .autoDispose()
+ },
+ {
+ Log.w(TAG, "Failed to remove reaction from the announcement.", it)
+ }
+ )
+ }
}
companion object {
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt
index ed590cd1..b7a65a7f 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt
@@ -51,6 +51,7 @@ import androidx.core.view.ContentInfoCompat
import androidx.core.view.OnReceiveContentListener
import androidx.core.view.isGone
import androidx.core.view.isVisible
+import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.transition.TransitionManager
@@ -65,6 +66,7 @@ import com.keylesspalace.tusky.components.compose.dialog.makeCaptionDialog
import com.keylesspalace.tusky.components.compose.dialog.showAddPollDialog
import com.keylesspalace.tusky.components.compose.view.ComposeOptionsListener
import com.keylesspalace.tusky.components.compose.view.ComposeScheduleView
+import com.keylesspalace.tusky.components.instanceinfo.InstanceInfoRepository
import com.keylesspalace.tusky.databinding.ActivityComposeBinding
import com.keylesspalace.tusky.db.AccountEntity
import com.keylesspalace.tusky.db.DraftAttachment
@@ -93,6 +95,7 @@ import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp
+import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize
import java.io.File
import java.io.IOException
@@ -123,8 +126,8 @@ class ComposeActivity :
private var photoUploadUri: Uri? = null
@VisibleForTesting
- var maximumTootCharacters = DEFAULT_CHARACTER_LIMIT
- var charactersReservedPerUrl = DEFAULT_MAXIMUM_URL_LENGTH
+ var maximumTootCharacters = InstanceInfoRepository.DEFAULT_CHARACTER_LIMIT
+ var charactersReservedPerUrl = InstanceInfoRepository.DEFAULT_CHARACTERS_RESERVED_PER_URL
private val viewModel: ComposeViewModel by viewModels { viewModelFactory }
@@ -328,7 +331,7 @@ class ComposeActivity :
private fun subscribeToUpdates(mediaAdapter: MediaPreviewAdapter) {
withLifecycleContext {
- viewModel.instanceParams.observe { instanceData ->
+ viewModel.instanceInfo.observe { instanceData ->
maximumTootCharacters = instanceData.maxChars
charactersReservedPerUrl = instanceData.charactersReservedPerUrl
updateVisibleCharactersLeft()
@@ -666,7 +669,7 @@ class ComposeActivity :
private fun openPollDialog() {
addMediaBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
- val instanceParams = viewModel.instanceParams.value!!
+ val instanceParams = viewModel.instanceInfo.value!!
showAddPollDialog(
this, viewModel.poll.value, instanceParams.pollMaxOptions,
instanceParams.pollMaxLength, instanceParams.pollMinDuration, instanceParams.pollMaxDuration,
@@ -866,25 +869,15 @@ class ComposeActivity :
}
private fun pickMedia(uri: Uri) {
- withLifecycleContext {
- viewModel.pickMedia(uri).observe { exceptionOrItem ->
- exceptionOrItem.asLeftOrNull()?.let {
- val errorId = when (it) {
- is VideoSizeException -> {
- R.string.error_video_upload_size
- }
- is AudioSizeException -> {
- R.string.error_audio_upload_size
- }
- is VideoOrImageException -> {
- R.string.error_media_upload_image_or_video
- }
- else -> {
- R.string.error_media_upload_opening
- }
- }
- displayTransientError(errorId)
+ lifecycleScope.launch {
+ viewModel.pickMedia(uri).onFailure { throwable ->
+ val errorId = when (throwable) {
+ is VideoSizeException -> R.string.error_video_upload_size
+ is AudioSizeException -> R.string.error_audio_upload_size
+ is VideoOrImageException -> R.string.error_media_upload_image_or_video
+ else -> R.string.error_media_upload_opening
}
+ displayTransientError(errorId)
}
}
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt
index 08df6dc9..fce3d0bd 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt
@@ -20,14 +20,14 @@ import android.util.Log
import androidx.core.net.toUri
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.Observer
+import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.keylesspalace.tusky.components.compose.ComposeActivity.QueuedMedia
import com.keylesspalace.tusky.components.drafts.DraftHelper
+import com.keylesspalace.tusky.components.instanceinfo.InstanceInfo
+import com.keylesspalace.tusky.components.instanceinfo.InstanceInfoRepository
import com.keylesspalace.tusky.components.search.SearchType
import com.keylesspalace.tusky.db.AccountManager
-import com.keylesspalace.tusky.db.AppDatabase
-import com.keylesspalace.tusky.db.InstanceEntity
import com.keylesspalace.tusky.entity.Attachment
import com.keylesspalace.tusky.entity.Emoji
import com.keylesspalace.tusky.entity.NewPoll
@@ -35,9 +35,6 @@ import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.service.ServiceClient
import com.keylesspalace.tusky.service.StatusToSend
-import com.keylesspalace.tusky.util.Either
-import com.keylesspalace.tusky.util.RxAwareViewModel
-import com.keylesspalace.tusky.util.VersionUtils
import com.keylesspalace.tusky.util.combineLiveData
import com.keylesspalace.tusky.util.filter
import com.keylesspalace.tusky.util.map
@@ -45,10 +42,12 @@ import com.keylesspalace.tusky.util.randomAlphanumericString
import com.keylesspalace.tusky.util.toLiveData
import com.keylesspalace.tusky.util.withoutFirstWhich
import io.reactivex.rxjava3.core.Observable
-import io.reactivex.rxjava3.core.Single
-import io.reactivex.rxjava3.disposables.Disposable
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.launch
import kotlinx.coroutines.rx3.rxSingle
+import kotlinx.coroutines.withContext
import java.util.Locale
import javax.inject.Inject
@@ -58,8 +57,8 @@ class ComposeViewModel @Inject constructor(
private val mediaUploader: MediaUploader,
private val serviceClient: ServiceClient,
private val draftHelper: DraftHelper,
- private val db: AppDatabase
-) : RxAwareViewModel() {
+ private val instanceInfoRepo: InstanceInfoRepository
+) : ViewModel() {
private var replyingStatusAuthor: String? = null
private var replyingStatusContent: String? = null
@@ -73,19 +72,8 @@ class ComposeViewModel @Inject constructor(
private var contentWarningStateChanged: Boolean = false
private var modifiedInitialState: Boolean = false
- private val instance: MutableLiveData = MutableLiveData(null)
+ val instanceInfo: MutableLiveData = MutableLiveData()
- val instanceParams: LiveData = instance.map { instance ->
- ComposeInstanceParams(
- maxChars = instance?.maximumTootCharacters ?: DEFAULT_CHARACTER_LIMIT,
- pollMaxOptions = instance?.maxPollOptions ?: DEFAULT_MAX_OPTION_COUNT,
- pollMaxLength = instance?.maxPollOptionLength ?: DEFAULT_MAX_OPTION_LENGTH,
- pollMinDuration = instance?.minPollDuration ?: DEFAULT_MIN_POLL_DURATION,
- pollMaxDuration = instance?.maxPollDuration ?: DEFAULT_MAX_POLL_DURATION,
- charactersReservedPerUrl = instance?.charactersReservedPerUrl ?: DEFAULT_MAXIMUM_URL_LENGTH,
- supportsScheduled = instance?.version?.let { VersionUtils(it).supportsScheduledToots() } ?: false
- )
- }
val emoji: MutableLiveData?> = MutableLiveData()
val markMediaAsSensitive =
mutableLiveData(accountManager.activeAccount?.defaultMediaSensitivity ?: false)
@@ -99,75 +87,35 @@ class ComposeViewModel @Inject constructor(
val media = mutableLiveData>(listOf())
val uploadError = MutableLiveData()
- private val mediaToDisposable = mutableMapOf()
+ private val mediaToJob = mutableMapOf()
private val isEditingScheduledToot get() = !scheduledTootId.isNullOrEmpty()
init {
-
- Single.zip(
- api.getCustomEmojis(),
- rxSingle {
- api.getInstance().getOrThrow()
- }
- ) { emojis, instance ->
- InstanceEntity(
- instance = accountManager.activeAccount?.domain!!,
- emojiList = emojis,
- maximumTootCharacters = instance.configuration?.statuses?.maxCharacters ?: instance.maxTootChars,
- maxPollOptions = instance.configuration?.polls?.maxOptions ?: instance.pollConfiguration?.maxOptions,
- maxPollOptionLength = instance.configuration?.polls?.maxCharactersPerOption ?: instance.pollConfiguration?.maxOptionChars,
- minPollDuration = instance.configuration?.polls?.minExpiration ?: instance.pollConfiguration?.minExpiration,
- maxPollDuration = instance.configuration?.polls?.maxExpiration ?: instance.pollConfiguration?.maxExpiration,
- charactersReservedPerUrl = instance.configuration?.statuses?.charactersReservedPerUrl,
- version = instance.version
- )
+ viewModelScope.launch {
+ emoji.postValue(instanceInfoRepo.getEmojis())
+ }
+ viewModelScope.launch {
+ instanceInfo.postValue(instanceInfoRepo.getInstanceInfo())
}
- .doOnSuccess {
- db.instanceDao().insertOrReplace(it)
- }
- .onErrorResumeNext {
- db.instanceDao().loadMetadataForInstance(accountManager.activeAccount?.domain!!)
- }
- .subscribe(
- { instanceEntity ->
- emoji.postValue(instanceEntity.emojiList)
- instance.postValue(instanceEntity)
- },
- { throwable ->
- // this can happen on network error when no cached data is available
- Log.w(TAG, "error loading instance data", throwable)
- }
- )
- .autoDispose()
}
- fun pickMedia(uri: Uri, description: String? = null): LiveData> {
- // We are not calling .toLiveData() here because we don't want to stop the process when
- // the Activity goes away temporarily (like on screen rotation).
- val liveData = MutableLiveData>()
- mediaUploader.prepareMedia(uri)
- .map { (type, uri, size) ->
- val mediaItems = media.value!!
- if (type != QueuedMedia.Type.IMAGE &&
- mediaItems.isNotEmpty() &&
- mediaItems[0].type == QueuedMedia.Type.IMAGE
- ) {
- throw VideoOrImageException()
- } else {
- addMediaToQueue(type, uri, size, description)
- }
+ suspend fun pickMedia(mediaUri: Uri, description: String? = null): Result = withContext(Dispatchers.IO) {
+ try {
+ val (type, uri, size) = mediaUploader.prepareMedia(mediaUri)
+ val mediaItems = media.value!!
+ if (type != QueuedMedia.Type.IMAGE &&
+ mediaItems.isNotEmpty() &&
+ mediaItems[0].type == QueuedMedia.Type.IMAGE
+ ) {
+ Result.failure(VideoOrImageException())
+ } else {
+ val queuedMedia = addMediaToQueue(type, uri, size, description)
+ Result.success(queuedMedia)
}
- .subscribe(
- { queuedMedia ->
- liveData.postValue(Either.Right(queuedMedia))
- },
- { error ->
- liveData.postValue(Either.Left(error))
- }
- )
- .autoDispose()
- return liveData
+ } catch (e: Exception) {
+ Result.failure(e)
+ }
}
private fun addMediaToQueue(
@@ -183,13 +131,17 @@ class ComposeViewModel @Inject constructor(
mediaSize = mediaSize,
description = description
)
- media.value = media.value!! + mediaItem
- mediaToDisposable[mediaItem.localId] = mediaUploader
- .uploadMedia(mediaItem)
- .subscribe(
- { event ->
+ media.postValue(media.value!! + mediaItem)
+ mediaToJob[mediaItem.localId] = viewModelScope.launch {
+ mediaUploader
+ .uploadMedia(mediaItem)
+ .catch { error ->
+ media.postValue(media.value?.filter { it.localId != mediaItem.localId } ?: emptyList())
+ uploadError.postValue(error)
+ }
+ .collect { event ->
val item = media.value?.find { it.localId == mediaItem.localId }
- ?: return@subscribe
+ ?: return@collect
val newMediaItem = when (event) {
is UploadEvent.ProgressEvent ->
item.copy(uploadPercent = event.percentage)
@@ -207,12 +159,8 @@ class ComposeViewModel @Inject constructor(
}
)
}
- },
- { error ->
- media.postValue(media.value?.filter { it.localId != mediaItem.localId } ?: emptyList())
- uploadError.postValue(error)
}
- )
+ }
return mediaItem
}
@@ -222,7 +170,7 @@ class ComposeViewModel @Inject constructor(
}
fun removeMediaFromQueue(item: QueuedMedia) {
- mediaToDisposable[item.localId]?.dispose()
+ mediaToJob[item.localId]?.cancel()
media.value = media.value!!.withoutFirstWhich { it.localId == item.localId }
}
@@ -337,35 +285,24 @@ class ComposeViewModel @Inject constructor(
return combineLiveData(deletionObservable, sendObservable) { _, _ -> }
}
- fun updateDescription(localId: Long, description: String): LiveData {
+ suspend fun updateDescription(localId: Long, description: String): Boolean {
val newList = media.value!!.toMutableList()
val index = newList.indexOfFirst { it.localId == localId }
if (index != -1) {
newList[index] = newList[index].copy(description = description)
}
media.value = newList
- val completedCaptioningLiveData = MutableLiveData()
- media.observeForever(object : Observer> {
- override fun onChanged(mediaItems: List) {
- val updatedItem = mediaItems.find { it.localId == localId }
- if (updatedItem == null) {
- media.removeObserver(this)
- } else if (updatedItem.id != null) {
- api.updateMedia(updatedItem.id, description)
- .subscribe(
- {
- completedCaptioningLiveData.postValue(true)
- },
- {
- completedCaptioningLiveData.postValue(false)
- }
- )
- .autoDispose()
- media.removeObserver(this)
- }
- }
- })
- return completedCaptioningLiveData
+ val updatedItem = newList.find { it.localId == localId }
+ if (updatedItem?.id != null) {
+ return api.updateMedia(updatedItem.id, description)
+ .fold({
+ true
+ }, { throwable ->
+ Log.w(TAG, "failed to update media", throwable)
+ false
+ })
+ }
+ return true
}
fun searchAutocompleteSuggestions(token: String): List {
@@ -447,7 +384,11 @@ class ComposeViewModel @Inject constructor(
val draftAttachments = composeOptions?.draftAttachments
if (draftAttachments != null) {
// when coming from DraftActivity
- draftAttachments.forEach { attachment -> pickMedia(attachment.uri, attachment.description) }
+ draftAttachments.forEach { attachment ->
+ viewModelScope.launch {
+ pickMedia(attachment.uri, attachment.description)
+ }
+ }
} else composeOptions?.mediaAttachments?.forEach { a ->
// when coming from redraft or ScheduledTootActivity
val mediaType = when (a.type) {
@@ -498,13 +439,6 @@ class ComposeViewModel @Inject constructor(
scheduledAt.value = newScheduledAt
}
- override fun onCleared() {
- for (uploadDisposable in mediaToDisposable.values) {
- uploadDisposable.dispose()
- }
- super.onCleared()
- }
-
private companion object {
const val TAG = "ComposeViewModel"
}
@@ -512,25 +446,6 @@ class ComposeViewModel @Inject constructor(
fun mutableLiveData(default: T) = MutableLiveData().apply { value = default }
-const val DEFAULT_CHARACTER_LIMIT = 500
-private const val DEFAULT_MAX_OPTION_COUNT = 4
-private const val DEFAULT_MAX_OPTION_LENGTH = 50
-private const val DEFAULT_MIN_POLL_DURATION = 300
-private const val DEFAULT_MAX_POLL_DURATION = 604800
-
-// Mastodon only counts URLs as this long in terms of status character limits
-const val DEFAULT_MAXIMUM_URL_LENGTH = 23
-
-data class ComposeInstanceParams(
- val maxChars: Int,
- val pollMaxOptions: Int,
- val pollMaxLength: Int,
- val pollMinDuration: Int,
- val pollMaxDuration: Int,
- val charactersReservedPerUrl: Int,
- val supportsScheduled: Boolean
-)
-
/**
* Thrown when trying to add an image when video is already present or the other way around
*/
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/DownsizeImageTask.java b/app/src/main/java/com/keylesspalace/tusky/components/compose/DownsizeImageTask.java
deleted file mode 100644
index 880a4167..00000000
--- a/app/src/main/java/com/keylesspalace/tusky/components/compose/DownsizeImageTask.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/* Copyright 2017 Andrew Dawson
- *
- * 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.components.compose;
-
-import android.content.ContentResolver;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.net.Uri;
-import android.os.AsyncTask;
-
-import com.keylesspalace.tusky.util.IOUtils;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-import static com.keylesspalace.tusky.util.MediaUtilsKt.calculateInSampleSize;
-import static com.keylesspalace.tusky.util.MediaUtilsKt.getImageOrientation;
-import static com.keylesspalace.tusky.util.MediaUtilsKt.reorientBitmap;
-
-/**
- * Reduces the file size of images to fit under a given limit by resizing them, maintaining both
- * aspect ratio and orientation.
- */
-public class DownsizeImageTask extends AsyncTask {
- private int sizeLimit;
- private ContentResolver contentResolver;
- private Listener listener;
- private File tempFile;
-
- /**
- * @param sizeLimit the maximum number of bytes each image can take
- * @param contentResolver to resolve the specified images' URIs
- * @param tempFile the file where the result will be stored
- * @param listener to whom the results are given
- */
- public DownsizeImageTask(int sizeLimit, ContentResolver contentResolver, File tempFile, Listener listener) {
- this.sizeLimit = sizeLimit;
- this.contentResolver = contentResolver;
- this.tempFile = tempFile;
- this.listener = listener;
- }
-
- @Override
- protected Boolean doInBackground(Uri... uris) {
- boolean result = DownsizeImageTask.resize(uris, sizeLimit, contentResolver, tempFile);
- if (isCancelled()) {
- return false;
- }
- return result;
- }
-
- @Override
- protected void onPostExecute(Boolean successful) {
- if (successful) {
- listener.onSuccess(tempFile);
- } else {
- listener.onFailure();
- }
- super.onPostExecute(successful);
- }
-
- public static boolean resize(Uri[] uris, int sizeLimit, ContentResolver contentResolver,
- File tempFile) {
- for (Uri uri : uris) {
- InputStream inputStream;
- try {
- inputStream = contentResolver.openInputStream(uri);
- } catch (FileNotFoundException e) {
- return false;
- }
- // Initially, just get the image dimensions.
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeStream(inputStream, null, options);
- IOUtils.closeQuietly(inputStream);
- // Get EXIF data, for orientation info.
- int orientation = getImageOrientation(uri, contentResolver);
- /* Unfortunately, there isn't a determined worst case compression ratio for image
- * formats. So, the only way to tell if they're too big is to compress them and
- * test, and keep trying at smaller sizes. The initial estimate should be good for
- * many cases, so it should only iterate once, but the loop is used to be absolutely
- * sure it gets downsized to below the limit. */
- int scaledImageSize = 1024;
- do {
- OutputStream stream;
- try {
- stream = new FileOutputStream(tempFile);
- } catch (FileNotFoundException e) {
- return false;
- }
- try {
- inputStream = contentResolver.openInputStream(uri);
- } catch (FileNotFoundException e) {
- return false;
- }
- options.inSampleSize = calculateInSampleSize(options, scaledImageSize, scaledImageSize);
- options.inJustDecodeBounds = false;
- Bitmap scaledBitmap;
- try {
- scaledBitmap = BitmapFactory.decodeStream(inputStream, null, options);
- } catch (OutOfMemoryError error) {
- return false;
- } finally {
- IOUtils.closeQuietly(inputStream);
- }
- if (scaledBitmap == null) {
- return false;
- }
- Bitmap reorientedBitmap = reorientBitmap(scaledBitmap, orientation);
- if (reorientedBitmap == null) {
- scaledBitmap.recycle();
- return false;
- }
- Bitmap.CompressFormat format;
- /* It's not likely the user will give transparent images over the upload limit, but
- * if they do, make sure the transparency is retained. */
- if (!reorientedBitmap.hasAlpha()) {
- format = Bitmap.CompressFormat.JPEG;
- } else {
- format = Bitmap.CompressFormat.PNG;
- }
- reorientedBitmap.compress(format, 85, stream);
- reorientedBitmap.recycle();
- scaledImageSize /= 2;
- } while (tempFile.length() > sizeLimit);
- }
- return true;
- }
-
- /**
- * Used to communicate the results of the task.
- */
- public interface Listener {
- void onSuccess(File file);
-
- void onFailure();
- }
-}
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ImageDownsizer.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ImageDownsizer.kt
new file mode 100644
index 00000000..a0215847
--- /dev/null
+++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ImageDownsizer.kt
@@ -0,0 +1,101 @@
+/* Copyright 2022 Tusky contributors
+ *
+ * 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.components.compose
+
+import android.content.ContentResolver
+import android.graphics.Bitmap
+import android.graphics.Bitmap.CompressFormat
+import android.graphics.BitmapFactory
+import android.net.Uri
+import com.keylesspalace.tusky.util.IOUtils
+import com.keylesspalace.tusky.util.calculateInSampleSize
+import com.keylesspalace.tusky.util.getImageOrientation
+import com.keylesspalace.tusky.util.reorientBitmap
+import java.io.File
+import java.io.FileNotFoundException
+import java.io.FileOutputStream
+
+/**
+ * @param uri the uri pointing to the input file
+ * @param sizeLimit the maximum number of bytes the output image is allowed to have
+ * @param contentResolver to resolve the specified input uri
+ * @param tempFile the file where the result will be stored
+ * @return true when the image was successfully resized, false otherwise
+ */
+fun downsizeImage(
+ uri: Uri,
+ sizeLimit: Int,
+ contentResolver: ContentResolver,
+ tempFile: File
+): Boolean {
+
+ val decodeBoundsInputStream = try {
+ contentResolver.openInputStream(uri)
+ } catch (e: FileNotFoundException) {
+ return false
+ }
+ // Initially, just get the image dimensions.
+ val options = BitmapFactory.Options()
+ options.inJustDecodeBounds = true
+ BitmapFactory.decodeStream(decodeBoundsInputStream, null, options)
+ IOUtils.closeQuietly(decodeBoundsInputStream)
+ // Get EXIF data, for orientation info.
+ val orientation = getImageOrientation(uri, contentResolver)
+ /* Unfortunately, there isn't a determined worst case compression ratio for image
+ * formats. So, the only way to tell if they're too big is to compress them and
+ * test, and keep trying at smaller sizes. The initial estimate should be good for
+ * many cases, so it should only iterate once, but the loop is used to be absolutely
+ * sure it gets downsized to below the limit. */
+ var scaledImageSize = 1024
+ do {
+ val outputStream = try {
+ FileOutputStream(tempFile)
+ } catch (e: FileNotFoundException) {
+ return false
+ }
+ val decodeBitmapInputStream = try {
+ contentResolver.openInputStream(uri)
+ } catch (e: FileNotFoundException) {
+ return false
+ }
+ options.inSampleSize = calculateInSampleSize(options, scaledImageSize, scaledImageSize)
+ options.inJustDecodeBounds = false
+ val scaledBitmap: Bitmap = try {
+ BitmapFactory.decodeStream(decodeBitmapInputStream, null, options)
+ } catch (error: OutOfMemoryError) {
+ return false
+ } finally {
+ IOUtils.closeQuietly(decodeBitmapInputStream)
+ } ?: return false
+
+ val reorientedBitmap = reorientBitmap(scaledBitmap, orientation)
+ if (reorientedBitmap == null) {
+ scaledBitmap.recycle()
+ return false
+ }
+ /* Retain transparency if there is any by encoding as png */
+ val format: CompressFormat = if (!reorientedBitmap.hasAlpha()) {
+ CompressFormat.JPEG
+ } else {
+ CompressFormat.PNG
+ }
+ reorientedBitmap.compress(format, 85, outputStream)
+ reorientedBitmap.recycle()
+ scaledImageSize /= 2
+ } while (tempFile.length() > sizeLimit)
+
+ return true
+}
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaUploader.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaUploader.kt
index 0e3ac9e8..85be146c 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaUploader.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaUploader.kt
@@ -32,9 +32,14 @@ import com.keylesspalace.tusky.util.MEDIA_SIZE_UNKNOWN
import com.keylesspalace.tusky.util.getImageSquarePixels
import com.keylesspalace.tusky.util.getMediaSize
import com.keylesspalace.tusky.util.randomAlphanumericString
-import io.reactivex.rxjava3.core.Observable
-import io.reactivex.rxjava3.core.Single
-import io.reactivex.rxjava3.schedulers.Schedulers
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import java.io.File
@@ -72,61 +77,40 @@ class MediaUploader @Inject constructor(
private val context: Context,
private val mastodonApi: MastodonApi
) {
- fun uploadMedia(media: QueuedMedia): Observable {
- return Observable
- .fromCallable {
- if (shouldResizeMedia(media)) {
- downsize(media)
- } else media
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ fun uploadMedia(media: QueuedMedia): Flow {
+ return flow {
+ if (shouldResizeMedia(media)) {
+ emit(downsize(media))
+ } else {
+ emit(media)
}
- .switchMap { upload(it) }
- .subscribeOn(Schedulers.io())
+ }
+ .flatMapLatest { upload(it) }
+ .flowOn(Dispatchers.IO)
}
- fun prepareMedia(inUri: Uri): Single {
- return Single.fromCallable {
- var mediaSize = MEDIA_SIZE_UNKNOWN
- var uri = inUri
- var mimeType: String? = null
+ fun prepareMedia(inUri: Uri): PreparedMedia {
+ var mediaSize = MEDIA_SIZE_UNKNOWN
+ var uri = inUri
+ val mimeType: String?
- try {
- when (inUri.scheme) {
- ContentResolver.SCHEME_CONTENT -> {
+ try {
+ when (inUri.scheme) {
+ ContentResolver.SCHEME_CONTENT -> {
- mimeType = contentResolver.getType(uri)
+ mimeType = contentResolver.getType(uri)
- val suffix = "." + MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType ?: "tmp")
+ val suffix = "." + MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType ?: "tmp")
- contentResolver.openInputStream(inUri).use { input ->
- if (input == null) {
- Log.w(TAG, "Media input is null")
- uri = inUri
- return@use
- }
- val file = File.createTempFile("randomTemp1", suffix, context.cacheDir)
- FileOutputStream(file.absoluteFile).use { out ->
- input.copyTo(out)
- uri = FileProvider.getUriForFile(
- context,
- BuildConfig.APPLICATION_ID + ".fileprovider",
- file
- )
- mediaSize = getMediaSize(contentResolver, uri)
- }
+ contentResolver.openInputStream(inUri).use { input ->
+ if (input == null) {
+ Log.w(TAG, "Media input is null")
+ uri = inUri
+ return@use
}
- }
- ContentResolver.SCHEME_FILE -> {
- val path = uri.path
- if (path == null) {
- Log.w(TAG, "empty uri path $uri")
- throw CouldNotOpenFileException()
- }
- val inputFile = File(path)
- val suffix = inputFile.name.substringAfterLast('.', "tmp")
- mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(suffix)
- val file = File.createTempFile("randomTemp1", ".$suffix", context.cacheDir)
- val input = FileInputStream(inputFile)
-
+ val file = File.createTempFile("randomTemp1", suffix, context.cacheDir)
FileOutputStream(file.absoluteFile).use { out ->
input.copyTo(out)
uri = FileProvider.getUriForFile(
@@ -137,53 +121,74 @@ class MediaUploader @Inject constructor(
mediaSize = getMediaSize(contentResolver, uri)
}
}
- else -> {
- Log.w(TAG, "Unknown uri scheme $uri")
+ }
+ ContentResolver.SCHEME_FILE -> {
+ val path = uri.path
+ if (path == null) {
+ Log.w(TAG, "empty uri path $uri")
throw CouldNotOpenFileException()
}
- }
- } catch (e: IOException) {
- Log.w(TAG, e)
- throw CouldNotOpenFileException()
- }
- if (mediaSize == MEDIA_SIZE_UNKNOWN) {
- Log.w(TAG, "Could not determine file size of upload")
- throw MediaTypeException()
- }
+ val inputFile = File(path)
+ val suffix = inputFile.name.substringAfterLast('.', "tmp")
+ mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(suffix)
+ val file = File.createTempFile("randomTemp1", ".$suffix", context.cacheDir)
+ val input = FileInputStream(inputFile)
- if (mimeType != null) {
- val topLevelType = mimeType.substring(0, mimeType.indexOf('/'))
- when (topLevelType) {
- "video" -> {
- if (mediaSize > STATUS_VIDEO_SIZE_LIMIT) {
- throw VideoSizeException()
- }
- PreparedMedia(QueuedMedia.Type.VIDEO, uri, mediaSize)
- }
- "image" -> {
- PreparedMedia(QueuedMedia.Type.IMAGE, uri, mediaSize)
- }
- "audio" -> {
- if (mediaSize > STATUS_AUDIO_SIZE_LIMIT) {
- throw AudioSizeException()
- }
- PreparedMedia(QueuedMedia.Type.AUDIO, uri, mediaSize)
- }
- else -> {
- throw MediaTypeException()
+ FileOutputStream(file.absoluteFile).use { out ->
+ input.copyTo(out)
+ uri = FileProvider.getUriForFile(
+ context,
+ BuildConfig.APPLICATION_ID + ".fileprovider",
+ file
+ )
+ mediaSize = getMediaSize(contentResolver, uri)
}
}
- } else {
- Log.w(TAG, "Could not determine mime type of upload")
- throw MediaTypeException()
+ else -> {
+ Log.w(TAG, "Unknown uri scheme $uri")
+ throw CouldNotOpenFileException()
+ }
}
+ } catch (e: IOException) {
+ Log.w(TAG, e)
+ throw CouldNotOpenFileException()
+ }
+ if (mediaSize == MEDIA_SIZE_UNKNOWN) {
+ Log.w(TAG, "Could not determine file size of upload")
+ throw MediaTypeException()
+ }
+
+ if (mimeType != null) {
+ return when (mimeType.substring(0, mimeType.indexOf('/'))) {
+ "video" -> {
+ if (mediaSize > STATUS_VIDEO_SIZE_LIMIT) {
+ throw VideoSizeException()
+ }
+ PreparedMedia(QueuedMedia.Type.VIDEO, uri, mediaSize)
+ }
+ "image" -> {
+ PreparedMedia(QueuedMedia.Type.IMAGE, uri, mediaSize)
+ }
+ "audio" -> {
+ if (mediaSize > STATUS_AUDIO_SIZE_LIMIT) {
+ throw AudioSizeException()
+ }
+ PreparedMedia(QueuedMedia.Type.AUDIO, uri, mediaSize)
+ }
+ else -> {
+ throw MediaTypeException()
+ }
+ }
+ } else {
+ Log.w(TAG, "Could not determine mime type of upload")
+ throw MediaTypeException()
}
}
private val contentResolver = context.contentResolver
- private fun upload(media: QueuedMedia): Observable {
- return Observable.create { emitter ->
+ private suspend fun upload(media: QueuedMedia): Flow {
+ return callbackFlow {
var mimeType = contentResolver.getType(media.uri)
val map = MimeTypeMap.getSingleton()
val fileExtension = map.getExtensionFromMimeType(mimeType)
@@ -200,11 +205,11 @@ class MediaUploader @Inject constructor(
var lastProgress = -1
val fileBody = ProgressRequestBody(
- stream, media.mediaSize,
- mimeType.toMediaTypeOrNull()
+ stream!!, media.mediaSize,
+ mimeType.toMediaTypeOrNull()!!
) { percentage ->
if (percentage != lastProgress) {
- emitter.onNext(UploadEvent.ProgressEvent(percentage))
+ trySend(UploadEvent.ProgressEvent(percentage))
}
lastProgress = percentage
}
@@ -217,28 +222,15 @@ class MediaUploader @Inject constructor(
null
}
- val uploadDisposable = mastodonApi.uploadMedia(body, description)
- .subscribe(
- { result ->
- emitter.onNext(UploadEvent.FinishedEvent(result.id))
- emitter.onComplete()
- },
- { e ->
- emitter.onError(e)
- }
- )
-
- // Cancel the request when our observable is cancelled
- emitter.setDisposable(uploadDisposable)
+ val result = mastodonApi.uploadMedia(body, description).getOrThrow()
+ send(UploadEvent.FinishedEvent(result.id))
+ awaitClose()
}
}
private fun downsize(media: QueuedMedia): QueuedMedia {
val file = createNewImageFile(context)
- DownsizeImageTask.resize(
- arrayOf(media.uri),
- STATUS_IMAGE_SIZE_LIMIT, context.contentResolver, file
- )
+ downsizeImage(media.uri, STATUS_IMAGE_SIZE_LIMIT, contentResolver, file)
return media.copy(uri = file.toUri(), mediaSize = file.length())
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/CaptionDialog.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/CaptionDialog.kt
index 0c15eff0..71789611 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/CaptionDialog.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/CaptionDialog.kt
@@ -27,7 +27,7 @@ import android.widget.LinearLayout
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.LiveData
+import androidx.lifecycle.lifecycleScope
import at.connyduck.sparkbutton.helpers.Utils
import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy
@@ -35,7 +35,7 @@ import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import com.github.chrisbanes.photoview.PhotoView
import com.keylesspalace.tusky.R
-import com.keylesspalace.tusky.util.withLifecycleContext
+import kotlinx.coroutines.launch
// https://github.com/tootsuite/mastodon/blob/c6904c0d3766a2ea8a81ab025c127169ecb51373/app/models/media_attachment.rb#L32
private const val MEDIA_DESCRIPTION_CHARACTER_LIMIT = 1500
@@ -43,7 +43,7 @@ private const val MEDIA_DESCRIPTION_CHARACTER_LIMIT = 1500
fun T.makeCaptionDialog(
existingDescription: String?,
previewUri: Uri,
- onUpdateDescription: (String) -> LiveData
+ onUpdateDescription: suspend (String) -> Boolean
) where T : Activity, T : LifecycleOwner {
val dialogLayout = LinearLayout(this)
val padding = Utils.dpToPx(this, 8)
@@ -77,12 +77,11 @@ fun T.makeCaptionDialog(
input.filters = arrayOf(InputFilter.LengthFilter(MEDIA_DESCRIPTION_CHARACTER_LIMIT))
val okListener = { dialog: DialogInterface, _: Int ->
- onUpdateDescription(input.text.toString())
- withLifecycleContext {
- onUpdateDescription(input.text.toString())
- .observe { success -> if (!success) showFailedCaptionMessage() }
+ lifecycleScope.launch {
+ if (!onUpdateDescription(input.text.toString())) {
+ showFailedCaptionMessage()
+ }
}
-
dialog.dismiss()
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instanceinfo/InstanceInfo.kt b/app/src/main/java/com/keylesspalace/tusky/components/instanceinfo/InstanceInfo.kt
new file mode 100644
index 00000000..db6ec0e1
--- /dev/null
+++ b/app/src/main/java/com/keylesspalace/tusky/components/instanceinfo/InstanceInfo.kt
@@ -0,0 +1,26 @@
+/* Copyright 2022 Tusky contributors
+ *
+ * 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.components.instanceinfo
+
+data class InstanceInfo(
+ val maxChars: Int,
+ val pollMaxOptions: Int,
+ val pollMaxLength: Int,
+ val pollMinDuration: Int,
+ val pollMaxDuration: Int,
+ val charactersReservedPerUrl: Int,
+ val supportsScheduled: Boolean
+)
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instanceinfo/InstanceInfoRepository.kt b/app/src/main/java/com/keylesspalace/tusky/components/instanceinfo/InstanceInfoRepository.kt
new file mode 100644
index 00000000..287d5499
--- /dev/null
+++ b/app/src/main/java/com/keylesspalace/tusky/components/instanceinfo/InstanceInfoRepository.kt
@@ -0,0 +1,104 @@
+/* Copyright 2022 Tusky contributors
+ *
+ * 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.components.instanceinfo
+
+import android.util.Log
+import com.keylesspalace.tusky.db.AccountManager
+import com.keylesspalace.tusky.db.AppDatabase
+import com.keylesspalace.tusky.db.EmojisEntity
+import com.keylesspalace.tusky.db.InstanceInfoEntity
+import com.keylesspalace.tusky.entity.Emoji
+import com.keylesspalace.tusky.network.MastodonApi
+import com.keylesspalace.tusky.util.VersionUtils
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import javax.inject.Inject
+
+class InstanceInfoRepository @Inject constructor(
+ private val api: MastodonApi,
+ db: AppDatabase,
+ accountManager: AccountManager
+) {
+
+ private val dao = db.instanceDao()
+ private val instanceName = accountManager.activeAccount!!.domain
+
+ /**
+ * Returns the custom emojis of the instance.
+ * Will always try to fetch them from the api, falls back to cached Emojis in case it is not available.
+ * Never throws, returns empty list in case of error.
+ */
+ suspend fun getEmojis(): List = withContext(Dispatchers.IO) {
+ api.getCustomEmojis()
+ .onSuccess { emojiList -> dao.insertOrReplace(EmojisEntity(instanceName, emojiList)) }
+ .getOrElse { throwable ->
+ Log.w(TAG, "failed to load custom emojis, falling back to cache", throwable)
+ dao.getEmojiInfo(instanceName)?.emojiList.orEmpty()
+ }
+ }
+
+ /**
+ * Returns information about the instance.
+ * Will always try to fetch the most up-to-date data from the api, falls back to cache in case it is not available.
+ * Never throws, returns defaults of vanilla Mastodon in case of error.
+ */
+ suspend fun getInstanceInfo(): InstanceInfo = withContext(Dispatchers.IO) {
+ api.getInstance()
+ .fold(
+ { instance ->
+ val instanceEntity = InstanceInfoEntity(
+ instance = instanceName,
+ maximumTootCharacters = instance.configuration?.statuses?.maxCharacters ?: instance.maxTootChars,
+ maxPollOptions = instance.configuration?.polls?.maxOptions ?: instance.pollConfiguration?.maxOptions,
+ maxPollOptionLength = instance.configuration?.polls?.maxCharactersPerOption ?: instance.pollConfiguration?.maxOptionChars,
+ minPollDuration = instance.configuration?.polls?.minExpiration ?: instance.pollConfiguration?.minExpiration,
+ maxPollDuration = instance.configuration?.polls?.maxExpiration ?: instance.pollConfiguration?.maxExpiration,
+ charactersReservedPerUrl = instance.configuration?.statuses?.charactersReservedPerUrl,
+ version = instance.version
+ )
+ dao.insertOrReplace(instanceEntity)
+ instanceEntity
+ },
+ { throwable ->
+ Log.w(TAG, "failed to instance, falling back to cache and default values", throwable)
+ dao.getInstanceInfo(instanceName)
+ }
+ ).let { instanceInfo: InstanceInfoEntity? ->
+ InstanceInfo(
+ maxChars = instanceInfo?.maximumTootCharacters ?: DEFAULT_CHARACTER_LIMIT,
+ pollMaxOptions = instanceInfo?.maxPollOptions ?: DEFAULT_MAX_OPTION_COUNT,
+ pollMaxLength = instanceInfo?.maxPollOptionLength ?: DEFAULT_MAX_OPTION_LENGTH,
+ pollMinDuration = instanceInfo?.minPollDuration ?: DEFAULT_MIN_POLL_DURATION,
+ pollMaxDuration = instanceInfo?.maxPollDuration ?: DEFAULT_MAX_POLL_DURATION,
+ charactersReservedPerUrl = instanceInfo?.charactersReservedPerUrl ?: DEFAULT_CHARACTERS_RESERVED_PER_URL,
+ supportsScheduled = instanceInfo?.version?.let { VersionUtils(it).supportsScheduledToots() } ?: false
+ )
+ }
+ }
+
+ companion object {
+ private const val TAG = "InstanceInfoRepo"
+
+ const val DEFAULT_CHARACTER_LIMIT = 500
+ private const val DEFAULT_MAX_OPTION_COUNT = 4
+ private const val DEFAULT_MAX_OPTION_LENGTH = 50
+ private const val DEFAULT_MIN_POLL_DURATION = 300
+ private const val DEFAULT_MAX_POLL_DURATION = 604800
+
+ // Mastodon only counts URLs as this long in terms of status character limits
+ const val DEFAULT_CHARACTERS_RESERVED_PER_URL = 23
+ }
+}
diff --git a/app/src/main/java/com/keylesspalace/tusky/db/InstanceDao.kt b/app/src/main/java/com/keylesspalace/tusky/db/InstanceDao.kt
index 52fc3aa8..9b190bc7 100644
--- a/app/src/main/java/com/keylesspalace/tusky/db/InstanceDao.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/db/InstanceDao.kt
@@ -19,13 +19,19 @@ import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
-import io.reactivex.rxjava3.core.Single
@Dao
interface InstanceDao {
- @Insert(onConflict = OnConflictStrategy.REPLACE)
- fun insertOrReplace(instance: InstanceEntity)
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE, entity = InstanceEntity::class)
+ suspend fun insertOrReplace(instance: InstanceInfoEntity)
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE, entity = InstanceEntity::class)
+ suspend fun insertOrReplace(emojis: EmojisEntity)
@Query("SELECT * FROM InstanceEntity WHERE instance = :instance LIMIT 1")
- fun loadMetadataForInstance(instance: String): Single
+ suspend fun getInstanceInfo(instance: String): InstanceInfoEntity?
+
+ @Query("SELECT * FROM InstanceEntity WHERE instance = :instance LIMIT 1")
+ suspend fun getEmojiInfo(instance: String): EmojisEntity?
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/db/InstanceEntity.kt b/app/src/main/java/com/keylesspalace/tusky/db/InstanceEntity.kt
index dd8e85d0..01767f32 100644
--- a/app/src/main/java/com/keylesspalace/tusky/db/InstanceEntity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/db/InstanceEntity.kt
@@ -23,7 +23,7 @@ import com.keylesspalace.tusky.entity.Emoji
@Entity
@TypeConverters(Converters::class)
data class InstanceEntity(
- @field:PrimaryKey var instance: String,
+ @PrimaryKey val instance: String,
val emojiList: List?,
val maximumTootCharacters: Int?,
val maxPollOptions: Int?,
@@ -33,3 +33,20 @@ data class InstanceEntity(
val charactersReservedPerUrl: Int?,
val version: String?
)
+
+@TypeConverters(Converters::class)
+data class EmojisEntity(
+ @PrimaryKey val instance: String,
+ val emojiList: List?
+)
+
+data class InstanceInfoEntity(
+ @PrimaryKey val instance: String,
+ val maximumTootCharacters: Int?,
+ val maxPollOptions: Int?,
+ val maxPollOptionLength: Int?,
+ val minPollDuration: Int?,
+ val maxPollDuration: Int?,
+ val charactersReservedPerUrl: Int?,
+ val version: String?
+)
diff --git a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt
index 111cad56..2340c5dd 100644
--- a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt
@@ -77,7 +77,7 @@ interface MastodonApi {
fun getLists(): Single>
@GET("/api/v1/custom_emojis")
- fun getCustomEmojis(): Single>
+ suspend fun getCustomEmojis(): Result>
@GET("api/v1/instance")
suspend fun getInstance(): Result
@@ -145,17 +145,17 @@ interface MastodonApi {
@Multipart
@POST("api/v2/media")
- fun uploadMedia(
+ suspend fun uploadMedia(
@Part file: MultipartBody.Part,
@Part description: MultipartBody.Part? = null
- ): Single
+ ): Result
@FormUrlEncoded
@PUT("api/v1/media/{mediaId}")
- fun updateMedia(
+ suspend fun updateMedia(
@Path("mediaId") mediaId: String,
@Field("description") description: String
- ): Single
+ ): Result
@POST("api/v1/statuses")
fun createStatus(
@@ -544,26 +544,26 @@ interface MastodonApi {
): Single
@GET("api/v1/announcements")
- fun listAnnouncements(
+ suspend fun listAnnouncements(
@Query("with_dismissed") withDismissed: Boolean = true
- ): Single>
+ ): Result>
@POST("api/v1/announcements/{id}/dismiss")
- fun dismissAnnouncement(
+ suspend fun dismissAnnouncement(
@Path("id") announcementId: String
- ): Single
+ ): Result
@PUT("api/v1/announcements/{id}/reactions/{name}")
- fun addAnnouncementReaction(
+ suspend fun addAnnouncementReaction(
@Path("id") announcementId: String,
@Path("name") name: String
- ): Single
+ ): Result
@DELETE("api/v1/announcements/{id}/reactions/{name}")
- fun removeAnnouncementReaction(
+ suspend fun removeAnnouncementReaction(
@Path("id") announcementId: String,
@Path("name") name: String
- ): Single
+ ): Result
@FormUrlEncoded
@POST("api/v1/reports")
diff --git a/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt b/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt
index 5396a21e..3a8f2f23 100644
--- a/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt
+++ b/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt
@@ -21,20 +21,19 @@ import android.widget.EditText
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.keylesspalace.tusky.components.compose.ComposeActivity
import com.keylesspalace.tusky.components.compose.ComposeViewModel
-import com.keylesspalace.tusky.components.compose.DEFAULT_CHARACTER_LIMIT
-import com.keylesspalace.tusky.components.compose.DEFAULT_MAXIMUM_URL_LENGTH
+import com.keylesspalace.tusky.components.instanceinfo.InstanceInfoRepository
import com.keylesspalace.tusky.db.AccountEntity
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.db.AppDatabase
+import com.keylesspalace.tusky.db.EmojisEntity
import com.keylesspalace.tusky.db.InstanceDao
-import com.keylesspalace.tusky.db.InstanceEntity
+import com.keylesspalace.tusky.db.InstanceInfoEntity
import com.keylesspalace.tusky.di.ViewModelFactory
import com.keylesspalace.tusky.entity.Account
import com.keylesspalace.tusky.entity.Instance
import com.keylesspalace.tusky.entity.InstanceConfiguration
import com.keylesspalace.tusky.entity.StatusConfiguration
import com.keylesspalace.tusky.network.MastodonApi
-import io.reactivex.rxjava3.core.Single
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
@@ -94,7 +93,7 @@ class ComposeActivityTest {
}
apiMock = mock {
- on { getCustomEmojis() } doReturn Single.just(emptyList())
+ onBlocking { getCustomEmojis() } doReturn Result.success(emptyList())
onBlocking { getInstance() } doReturn instanceResponseCallback?.invoke().let { instance ->
if (instance == null) {
Result.failure(Throwable())
@@ -105,23 +104,25 @@ class ComposeActivityTest {
}
val instanceDaoMock: InstanceDao = mock {
- on { loadMetadataForInstance(any()) } doReturn
- Single.just(InstanceEntity(instanceDomain, emptyList(), null, null, null, null, null, null, null))
- on { loadMetadataForInstance(any()) } doReturn
- Single.just(InstanceEntity(instanceDomain, emptyList(), null, null, null, null, null, null, null))
+ onBlocking { getInstanceInfo(any()) } doReturn
+ InstanceInfoEntity(instanceDomain, null, null, null, null, null, null, null)
+ onBlocking { getEmojiInfo(any()) } doReturn
+ EmojisEntity(instanceDomain, emptyList())
}
val dbMock: AppDatabase = mock {
on { instanceDao() } doReturn instanceDaoMock
}
+ val instanceInfoRepo = InstanceInfoRepository(apiMock, dbMock, accountManagerMock)
+
val viewModel = ComposeViewModel(
apiMock,
accountManagerMock,
mock(),
mock(),
mock(),
- dbMock
+ instanceInfoRepo
)
activity.intent = Intent(activity, ComposeActivity::class.java).apply {
putExtra(ComposeActivity.COMPOSE_OPTIONS_EXTRA, composeOptions)
@@ -135,6 +136,7 @@ class ComposeActivityTest {
activity.viewModelFactory = viewModelFactoryMock
controller.create().start()
+ shadowOf(getMainLooper()).idle()
}
@Test
@@ -185,7 +187,7 @@ class ComposeActivityTest {
fun whenMaximumTootCharsIsNull_defaultLimitIsUsed() {
instanceResponseCallback = { getInstanceWithCustomConfiguration(null) }
setupActivity()
- assertEquals(DEFAULT_CHARACTER_LIMIT, activity.maximumTootCharacters)
+ assertEquals(InstanceInfoRepository.DEFAULT_CHARACTER_LIMIT, activity.maximumTootCharacters)
}
@Test
@@ -236,7 +238,7 @@ class ComposeActivityTest {
val url = "https://www.google.dk/search?biw=1920&bih=990&tbm=isch&sa=1&ei=bmDrWuOoKMv6kwWOkIaoDQ&q=indiana+jones+i+hate+snakes+animated&oq=indiana+jones+i+hate+snakes+animated&gs_l=psy-ab.3...54174.55443.0.55553.9.7.0.0.0.0.255.333.1j0j1.2.0....0...1c.1.64.psy-ab..7.0.0....0.40G-kcDkC6A#imgdii=PSp15hQjN1JqvM:&imgrc=H0hyE2JW5wrpBM:"
val additionalContent = "Check out this @image #search result: "
insertSomeTextInContent(additionalContent + url)
- assertEquals(activity.calculateTextLength(), additionalContent.length + DEFAULT_MAXIMUM_URL_LENGTH)
+ assertEquals(activity.calculateTextLength(), additionalContent.length + InstanceInfoRepository.DEFAULT_CHARACTERS_RESERVED_PER_URL)
}
@Test
@@ -245,7 +247,7 @@ class ComposeActivityTest {
val url = "https://www.google.dk/search?biw=1920&bih=990&tbm=isch&sa=1&ei=bmDrWuOoKMv6kwWOkIaoDQ&q=indiana+jones+i+hate+snakes+animated&oq=indiana+jones+i+hate+snakes+animated&gs_l=psy-ab.3...54174.55443.0.55553.9.7.0.0.0.0.255.333.1j0j1.2.0....0...1c.1.64.psy-ab..7.0.0....0.40G-kcDkC6A#imgdii=PSp15hQjN1JqvM:&imgrc=H0hyE2JW5wrpBM:"
val additionalContent = " Check out this @image #search result: "
insertSomeTextInContent(shortUrl + additionalContent + url)
- assertEquals(activity.calculateTextLength(), additionalContent.length + (DEFAULT_MAXIMUM_URL_LENGTH * 2))
+ assertEquals(activity.calculateTextLength(), additionalContent.length + (InstanceInfoRepository.DEFAULT_CHARACTERS_RESERVED_PER_URL * 2))
}
@Test
@@ -253,7 +255,7 @@ class ComposeActivityTest {
val url = "https://www.google.dk/search?biw=1920&bih=990&tbm=isch&sa=1&ei=bmDrWuOoKMv6kwWOkIaoDQ&q=indiana+jones+i+hate+snakes+animated&oq=indiana+jones+i+hate+snakes+animated&gs_l=psy-ab.3...54174.55443.0.55553.9.7.0.0.0.0.255.333.1j0j1.2.0....0...1c.1.64.psy-ab..7.0.0....0.40G-kcDkC6A#imgdii=PSp15hQjN1JqvM:&imgrc=H0hyE2JW5wrpBM:"
val additionalContent = " Check out this @image #search result: "
insertSomeTextInContent(url + additionalContent + url)
- assertEquals(activity.calculateTextLength(), additionalContent.length + (DEFAULT_MAXIMUM_URL_LENGTH * 2))
+ assertEquals(activity.calculateTextLength(), additionalContent.length + (InstanceInfoRepository.DEFAULT_CHARACTERS_RESERVED_PER_URL * 2))
}
@Test
From 7499ff573329e0f039575441d6c5a68cfcaf1d77 Mon Sep 17 00:00:00 2001
From: Konrad Pozniak
Date: Thu, 21 Apr 2022 18:46:30 +0200
Subject: [PATCH 015/104] never collapse bottom app bar tabs (#2447)
---
app/src/main/res/layout/activity_main.xml | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 76e7a535..39fc717f 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -58,6 +58,7 @@
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:tabIndicator="@null"
+ app:tabGravity="fill"
app:tabMode="fixed" />
From 43709532d6b48c15ba9131e294df964363a3e893 Mon Sep 17 00:00:00 2001
From: Konrad Pozniak
Date: Thu, 21 Apr 2022 18:46:43 +0200
Subject: [PATCH 016/104] fix unparsed html in "replying to" toggle (#2448)
---
.../tusky/components/drafts/DraftsActivity.kt | 3 ++-
.../components/search/fragments/SearchStatusesFragment.kt | 8 ++++----
.../java/com/keylesspalace/tusky/fragment/SFragment.java | 4 +++-
3 files changed, 9 insertions(+), 6 deletions(-)
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 e580f554..db6a8a31 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
@@ -35,6 +35,7 @@ import com.keylesspalace.tusky.components.compose.ComposeActivity
import com.keylesspalace.tusky.databinding.ActivityDraftsBinding
import com.keylesspalace.tusky.db.DraftEntity
import com.keylesspalace.tusky.di.ViewModelFactory
+import com.keylesspalace.tusky.util.parseAsMastodonHtml
import com.keylesspalace.tusky.util.visible
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import kotlinx.coroutines.flow.collectLatest
@@ -100,7 +101,7 @@ class DraftsActivity : BaseActivity(), DraftActionListener {
content = draft.content,
contentWarning = draft.contentWarning,
inReplyToId = draft.inReplyToId,
- replyingStatusContent = status.content.toString(),
+ replyingStatusContent = status.content.parseAsMastodonHtml().toString(),
replyingStatusAuthor = status.account.localUsername,
draftAttachments = draft.attachments,
poll = draft.poll,
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt
index 23ff1b07..11d90da4 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt
@@ -97,7 +97,7 @@ class SearchStatusesFragment : SearchFragment(), Status
}
override fun onReply(position: Int) {
- searchAdapter.peek(position)?.status?.let { status ->
+ searchAdapter.peek(position)?.let { status ->
reply(status)
}
}
@@ -199,8 +199,8 @@ class SearchStatusesFragment : SearchFragment(), Status
fun newInstance() = SearchStatusesFragment()
}
- private fun reply(status: Status) {
- val actionableStatus = status.actionableStatus
+ private fun reply(status: StatusViewData.Concrete) {
+ val actionableStatus = status.actionable
val mentionedUsernames = actionableStatus.mentions.map { it.username }
.toMutableSet()
.apply {
@@ -216,7 +216,7 @@ class SearchStatusesFragment : SearchFragment(), Status
contentWarning = actionableStatus.spoilerText,
mentionedUsernames = mentionedUsernames,
replyingStatusAuthor = actionableStatus.account.localUsername,
- replyingStatusContent = actionableStatus.content.toString()
+ replyingStatusContent = status.content.toString()
)
)
startActivity(intent)
diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java
index b1a47ad8..bb02807d 100644
--- a/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java
+++ b/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java
@@ -15,6 +15,8 @@
package com.keylesspalace.tusky.fragment;
+import static com.keylesspalace.tusky.util.StatusParsingHelper.parseAsMastodonHtml;
+
import android.Manifest;
import android.app.DownloadManager;
import android.content.ClipData;
@@ -150,7 +152,7 @@ public abstract class SFragment extends Fragment implements Injectable {
composeOptions.setContentWarning(contentWarning);
composeOptions.setMentionedUsernames(mentionedUsernames);
composeOptions.setReplyingStatusAuthor(actionableStatus.getAccount().getLocalUsername());
- composeOptions.setReplyingStatusContent(actionableStatus.getContent().toString());
+ composeOptions.setReplyingStatusContent(parseAsMastodonHtml(actionableStatus.getContent()).toString());
Intent intent = ComposeActivity.startIntent(getContext(), composeOptions);
getActivity().startActivity(intent);
From db7eac0a8dd29d8095719669cf3d1520e17d6ef2 Mon Sep 17 00:00:00 2001
From: Konrad Pozniak
Date: Thu, 21 Apr 2022 18:46:55 +0200
Subject: [PATCH 017/104] guard against the status of a notification being null
in rare cases (#2449)
* guard against the status of a notification being null in rare cases
* improve code, fix bug when payloads is not null
* remove findViewById
* add comments in NotificationsAdapter
---
.../tusky/adapter/NotificationsAdapter.java | 14 ++++++--
.../tusky/adapter/StatusBaseViewHolder.java | 33 ++++++++++++++++---
.../adapter/StatusDetailedViewHolder.java | 9 ++---
.../tusky/adapter/StatusViewHolder.java | 12 +++++--
4 files changed, 55 insertions(+), 13 deletions(-)
diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
index 0ac029b0..f8885f36 100644
--- a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
+++ b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
@@ -180,8 +180,16 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
case VIEW_TYPE_STATUS: {
StatusViewHolder holder = (StatusViewHolder) viewHolder;
StatusViewData.Concrete status = concreteNotificaton.getStatusViewData();
- holder.setupWithStatus(status,
- statusListener, statusDisplayOptions, payloadForHolder);
+ if (status == null) {
+ /* in some very rare cases servers sends null status even though they should not,
+ * we have to handle it somehow */
+ holder.showStatusContent(false);
+ } else {
+ if (payloads == null) {
+ holder.showStatusContent(true);
+ }
+ holder.setupWithStatus(status, statusListener, statusDisplayOptions, payloadForHolder);
+ }
if (concreteNotificaton.getType() == Notification.Type.POLL) {
holder.setPollInfo(accountId.equals(concreteNotificaton.getAccount().getId()));
} else {
@@ -194,6 +202,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
StatusViewData.Concrete statusViewData = concreteNotificaton.getStatusViewData();
if (payloadForHolder == null) {
if (statusViewData == null) {
+ /* in some very rare cases servers sends null status even though they should not,
+ * we have to handle it somehow */
holder.showNotificationContent(false);
} else {
holder.showNotificationContent(true);
diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java
index c2729aa5..c7427450 100644
--- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java
+++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java
@@ -20,6 +20,7 @@ import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
+import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.text.HtmlCompat;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
@@ -76,6 +77,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
private SparkButton favouriteButton;
private SparkButton bookmarkButton;
private ImageButton moreButton;
+ private ConstraintLayout mediaContainer;
protected MediaPreviewImageView[] mediaPreviews;
private ImageView[] mediaOverlays;
private TextView sensitiveMediaWarning;
@@ -124,7 +126,8 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
bookmarkButton = itemView.findViewById(R.id.status_bookmark);
moreButton = itemView.findViewById(R.id.status_more);
- itemView.findViewById(R.id.status_media_preview_container).setClipToOutline(true);
+ mediaContainer = itemView.findViewById(R.id.status_media_preview_container);
+ mediaContainer.setClipToOutline(true);
mediaPreviews = new MediaPreviewImageView[]{
itemView.findViewById(R.id.status_media_preview_0),
@@ -719,9 +722,9 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
this.setupWithStatus(status, listener, statusDisplayOptions, null);
}
- public void setupWithStatus(StatusViewData.Concrete status,
- final StatusActionListener listener,
- StatusDisplayOptions statusDisplayOptions,
+ public void setupWithStatus(@NonNull StatusViewData.Concrete status,
+ @NonNull final StatusActionListener listener,
+ @NonNull StatusDisplayOptions statusDisplayOptions,
@Nullable Object payloads) {
if (payloads == null) {
Status actionable = status.getActionable();
@@ -1133,6 +1136,28 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
}
}
+ public void showStatusContent(boolean show) {
+ int visibility = show ? View.VISIBLE : View.GONE;
+ avatar.setVisibility(visibility);
+ avatarInset.setVisibility(visibility);
+ displayName.setVisibility(visibility);
+ username.setVisibility(visibility);
+ timestampInfo.setVisibility(visibility);
+ contentWarningDescription.setVisibility(visibility);
+ contentWarningButton.setVisibility(visibility);
+ content.setVisibility(visibility);
+ cardView.setVisibility(visibility);
+ mediaContainer.setVisibility(visibility);
+ pollOptions.setVisibility(visibility);
+ pollButton.setVisibility(visibility);
+ pollDescription.setVisibility(visibility);
+ replyButton.setVisibility(visibility);
+ reblogButton.setVisibility(visibility);
+ favouriteButton.setVisibility(visibility);
+ bookmarkButton.setVisibility(visibility);
+ moreButton.setVisibility(visibility);
+ }
+
private static String formatDuration(double durationInSeconds) {
int seconds = (int) Math.round(durationInSeconds) % 60;
int minutes = (int) durationInSeconds % 3600 / 60;
diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusDetailedViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusDetailedViewHolder.java
index 56adfcad..1aebf1a7 100644
--- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusDetailedViewHolder.java
+++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusDetailedViewHolder.java
@@ -9,6 +9,7 @@ import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
@@ -101,10 +102,10 @@ class StatusDetailedViewHolder extends StatusBaseViewHolder {
}
@Override
- public void setupWithStatus(final StatusViewData.Concrete status,
- final StatusActionListener listener,
- StatusDisplayOptions statusDisplayOptions,
- @Nullable Object payloads) {
+ public void setupWithStatus(@NonNull final StatusViewData.Concrete status,
+ @NonNull final StatusActionListener listener,
+ @NonNull StatusDisplayOptions statusDisplayOptions,
+ @Nullable Object payloads) {
super.setupWithStatus(status, listener, statusDisplayOptions, payloads);
setupCard(status, CardViewMode.FULL_WIDTH, statusDisplayOptions, listener); // Always show card for detailed status
if (payloads == null) {
diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java
index b054aea9..93c47564 100644
--- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java
+++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java
@@ -22,6 +22,7 @@ import android.view.View;
import android.widget.Button;
import android.widget.TextView;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
@@ -58,9 +59,9 @@ public class StatusViewHolder extends StatusBaseViewHolder {
}
@Override
- public void setupWithStatus(StatusViewData.Concrete status,
- final StatusActionListener listener,
- StatusDisplayOptions statusDisplayOptions,
+ public void setupWithStatus(@NonNull StatusViewData.Concrete status,
+ @NonNull final StatusActionListener listener,
+ @NonNull StatusDisplayOptions statusDisplayOptions,
@Nullable Object payloads) {
if (payloads == null) {
@@ -129,4 +130,9 @@ public class StatusViewHolder extends StatusBaseViewHolder {
content.setFilters(NO_INPUT_FILTER);
}
}
+
+ public void showStatusContent(boolean show) {
+ super.showStatusContent(show);
+ contentCollapseButton.setVisibility(show ? View.VISIBLE : View.GONE);
+ }
}
From adcbe1a8317257ffe44a5540bb74dad6313fb2ae Mon Sep 17 00:00:00 2001
From: Konrad Pozniak
Date: Thu, 21 Apr 2022 18:47:05 +0200
Subject: [PATCH 018/104] fix unparsed html in announcements (#2451)
---
.../tusky/components/announcements/AnnouncementAdapter.kt | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementAdapter.kt
index 4b5e7aa5..70ebfc7d 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementAdapter.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementAdapter.kt
@@ -32,6 +32,7 @@ import com.keylesspalace.tusky.interfaces.LinkListener
import com.keylesspalace.tusky.util.BindingHolder
import com.keylesspalace.tusky.util.EmojiSpan
import com.keylesspalace.tusky.util.emojify
+import com.keylesspalace.tusky.util.parseAsMastodonHtml
import com.keylesspalace.tusky.util.setClickableText
import java.lang.ref.WeakReference
@@ -60,7 +61,7 @@ class AnnouncementAdapter(
val chips = holder.binding.chipGroup
val addReactionChip = holder.binding.addReactionChip
- val emojifiedText: CharSequence = item.content.emojify(item.emojis, text, animateEmojis)
+ val emojifiedText: CharSequence = item.content.parseAsMastodonHtml().emojify(item.emojis, text, animateEmojis)
setClickableText(text, emojifiedText, item.mentions, item.tags, listener)
From 8109a126ae988fe4347f09a588314fb618c72aa7 Mon Sep 17 00:00:00 2001
From: codl
Date: Thu, 21 Apr 2022 14:40:29 +0000
Subject: [PATCH 019/104] Translated using Weblate (French)
Currently translated at 100.0% (473 of 473 strings)
Co-authored-by: codl
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/fr/
Translation: Tusky/Tusky
---
app/src/main/res/values-fr/strings.xml | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index e3acac34..0490c87b 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -540,4 +540,8 @@
14 jours
180 jours
Rédiger un message
+ %s a créé un compte
+ Nouveaux comptes
+ Notifications quand quelqu\'un crée un nouveau compte
+ un nouveau compte a été créé
\ No newline at end of file
From 5243c8ddb5b78f19cecf003ae0172f3ec20309b3 Mon Sep 17 00:00:00 2001
From: ButterflyOfFire
Date: Thu, 21 Apr 2022 14:40:29 +0000
Subject: [PATCH 020/104] Translated using Weblate (Occitan)
Currently translated at 93.0% (440 of 473 strings)
Co-authored-by: ButterflyOfFire
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/oc/
Translation: Tusky/Tusky
---
app/src/main/res/values-oc/strings.xml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/src/main/res/values-oc/strings.xml b/app/src/main/res/values-oc/strings.xml
index c1ffa122..4dc4370f 100644
--- a/app/src/main/res/values-oc/strings.xml
+++ b/app/src/main/res/values-oc/strings.xml
@@ -337,8 +337,8 @@
- %1$s Favorits
- - %s partatge
- - %s partatges
+ - %s Partatge
+ - %s Partatges
Partejat per
Aimat per
From 1a921ec3945c6680a1724db49fe6307c6eb9a1a1 Mon Sep 17 00:00:00 2001
From: Vegard Skjefstad
Date: Thu, 21 Apr 2022 14:40:29 +0000
Subject: [PATCH 021/104] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegi?=
=?UTF-8?q?an=20Bokm=C3=A5l)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Currently translated at 100.0% (477 of 477 strings)
Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (473 of 473 strings)
Co-authored-by: Vegard Skjefstad
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/nb_NO/
Translation: Tusky/Tusky
---
app/src/main/res/values-no-rNB/strings.xml | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/app/src/main/res/values-no-rNB/strings.xml b/app/src/main/res/values-no-rNB/strings.xml
index ade13f04..351c0386 100644
--- a/app/src/main/res/values-no-rNB/strings.xml
+++ b/app/src/main/res/values-no-rNB/strings.xml
@@ -519,4 +519,12 @@
365 dager
14 dager
Komponer toot
+ %s registrerte seg
+ noen registrerte seg
+ Registreringer
+ Varslinger om nye brukere
+ %s redigerte innlegget sitt
+ et innlegg jeg har hatt en interaksjon med er redigert
+ Redigerte innlegg
+ Varslinger når et innlegg du har hatt en interaksjon med er redigert
\ No newline at end of file
From 7461315c33d360030e430baf3e20027c5d1662b1 Mon Sep 17 00:00:00 2001
From: Ihor Hordiichuk
Date: Thu, 21 Apr 2022 14:40:30 +0000
Subject: [PATCH 022/104] Translated using Weblate (Ukrainian)
Currently translated at 100.0% (477 of 477 strings)
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (473 of 473 strings)
Co-authored-by: Ihor Hordiichuk
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/uk/
Translation: Tusky/Tusky
---
app/src/main/res/values-uk/strings.xml | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 0031334b..d9fab3a3 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -541,4 +541,12 @@
180 днів
365 днів
Створити допис
+ %s реєструється
+ хтось реєструється
+ Реєстрації
+ Сповіщення про нових користувачів
+ %s редагує свій допис
+ допис, з яким у мене була взаємодія, відредаговано
+ Сповіщення, коли редагується повідомлення, з яким ви взаємодіяли
+ Редакції допису
\ No newline at end of file
From 5ecc58d9dd20ac7a7c2f355e05d095b82af70797 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=E1=BB=93=20Nh=E1=BA=A5t=20Duy?=
Date: Thu, 21 Apr 2022 14:40:30 +0000
Subject: [PATCH 023/104] Translated using Weblate (Vietnamese)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Currently translated at 100.0% (477 of 477 strings)
Translated using Weblate (Vietnamese)
Currently translated at 100.0% (473 of 473 strings)
Co-authored-by: Hồ Nhất Duy
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/
Translation: Tusky/Tusky
---
app/src/main/res/values-vi/strings.xml | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
index caf759fe..16704821 100644
--- a/app/src/main/res/values-vi/strings.xml
+++ b/app/src/main/res/values-vi/strings.xml
@@ -192,7 +192,7 @@
Ghim
Trả lời
Tút
- Nội dung tút
+ Tút
Xếp tab
Tin nhắn
Thế giới
@@ -508,4 +508,12 @@
180 ngày
365 ngày
Viết tút
+ ai đó đăng ký trên máy chủ
+ %s đăng ký
+ Đăng ký
+ Thông báo về người dùng mới đăng ký
+ %s đã sửa tút của họ
+ khi một tút mà tôi tương tác bị sửa
+ Sửa tút
+ Thông báo khi tút mà tôi tương tác bị sửa
\ No newline at end of file
From d49785b23ba2009b9d131e3714b80003a4947d86 Mon Sep 17 00:00:00 2001
From: GunChleoc
Date: Thu, 21 Apr 2022 14:40:30 +0000
Subject: [PATCH 024/104] Translated using Weblate (Gaelic)
Currently translated at 99.3% (474 of 477 strings)
Translated using Weblate (Gaelic)
Currently translated at 100.0% (473 of 473 strings)
Co-authored-by: GunChleoc
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/gd/
Translation: Tusky/Tusky
---
app/src/main/res/values-gd/strings.xml | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/app/src/main/res/values-gd/strings.xml b/app/src/main/res/values-gd/strings.xml
index bf2a2d52..16ef394d 100644
--- a/app/src/main/res/values-gd/strings.xml
+++ b/app/src/main/res/values-gd/strings.xml
@@ -253,6 +253,9 @@
- Mìnich e dhan fheadhainn air a bheil cion-lèirsinn
\n(%d caractar(an) air a char as fhaide)
+
+
+
Cha deach leinn am fo-thiotal a shuidheachadh
A’ postadh leis a’ chunntas %1$s
@@ -541,4 +544,9 @@
14 làithean
60 latha
Sgrìobh post
+ Chlàraich %s
+ Clàraidhean
+ Brathan mu cleachdaichean ùra
+ chlàraich cuideigin
+ Dheasaich %s am post aca
\ No newline at end of file
From b39588ec2a69e3ecc960c0bef234e1542a969e72 Mon Sep 17 00:00:00 2001
From: XoseM
Date: Thu, 21 Apr 2022 14:40:30 +0000
Subject: [PATCH 025/104] Translated using Weblate (Galician)
Currently translated at 100.0% (473 of 473 strings)
Co-authored-by: XoseM
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/gl/
Translation: Tusky/Tusky
---
app/src/main/res/values-gl/strings.xml | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml
index bfabe4c8..cf0a5083 100644
--- a/app/src/main/res/values-gl/strings.xml
+++ b/app/src/main/res/values-gl/strings.xml
@@ -519,4 +519,8 @@
180 días
365 días
Redactar publicación
+ %s rexistrouse
+ hai unha nova usuaria
+ Rexistros
+ Notificacións sobre novas usuarias
\ No newline at end of file
From 2a0f1c6707ce7ef20d04896f1133672e0640c1c2 Mon Sep 17 00:00:00 2001
From: Ralf Thees
Date: Thu, 21 Apr 2022 14:40:30 +0000
Subject: [PATCH 026/104] Translated using Weblate (German)
Currently translated at 96.6% (461 of 477 strings)
Co-authored-by: Ralf Thees
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/de/
Translation: Tusky/Tusky
---
app/src/main/res/values-de/strings.xml | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 631345ae..b82f4b1d 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -208,7 +208,7 @@
Neue Erwähnungen
Benachrichtigungen über neue Erwähnungen
Neue Folgende
- Benachrichtigunen über neue Folgende
+ Benachrichtigungen über neue Folgende
Geteilte Beiträge
Benachrichtigungen, wenn deine Beiträge geteilt werden
Favorisierte Beiträge
@@ -528,4 +528,11 @@
14 Tage
180 Tage
Beitrag erstellen
+ %s hat den Beitrag bearbeitet
+ Ein Beitrag, mit dem ich interagiert habe, wurde bearbeitet
+ Registrierungen
+ Benachrichtigungen über neue Profile
+ %s hat sich registriert
+ Jemand hat sich registriert
+ Benachrichtigungen, wenn Beiträge bearbeitet werden, mit denen du interagiert hast
\ No newline at end of file
From 1b0f02d48c1645fb283666b4e347004484d8d68f Mon Sep 17 00:00:00 2001
From: Connyduck
Date: Thu, 21 Apr 2022 14:40:30 +0000
Subject: [PATCH 027/104] Translated using Weblate (Gaelic)
Currently translated at 99.3% (474 of 477 strings)
Co-authored-by: Connyduck
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/gd/
Translation: Tusky/Tusky
---
app/src/main/res/values-gd/strings.xml | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/app/src/main/res/values-gd/strings.xml b/app/src/main/res/values-gd/strings.xml
index 16ef394d..19ae1240 100644
--- a/app/src/main/res/values-gd/strings.xml
+++ b/app/src/main/res/values-gd/strings.xml
@@ -253,9 +253,12 @@
- Mìnich e dhan fheadhainn air a bheil cion-lèirsinn
\n(%d caractar(an) air a char as fhaide)
-
-
-
+ - Mìnich e dhan fheadhainn air a bheil cion-lèirsinn
+\n(%d caractar(an) air a char as fhaide)
+ - Mìnich e dhan fheadhainn air a bheil cion-lèirsinn
+\n(%d caractar(an) air a char as fhaide)
+ - Mìnich e dhan fheadhainn air a bheil cion-lèirsinn
+\n(%d caractar(an) air a char as fhaide)
Cha deach leinn am fo-thiotal a shuidheachadh
A’ postadh leis a’ chunntas %1$s
From f15b3e61bbcd89d89af0307a0993540eba333352 Mon Sep 17 00:00:00 2001
From: Constantin A <10349490+C1710@users.noreply.github.com>
Date: Tue, 26 Apr 2022 18:50:58 +0200
Subject: [PATCH 028/104] New emoji picker (#2395)
* Update to Emoji2
* Hopefully fix the emoji picker preference
* Switch to released Filemojicompat version
* Filemojicompat version as an own var
* Remove an unused import
* Small cleanup
* Correct onDisplayPreferenceDialog; test TuskyApplication
* Use TextViews instead of EmojiTextViews
* Recreate the Main Activity if the emoji pack is updated
* Enable coreLibraryDesugaring (for Java Streams); update Filemojicompat, downgrade Emoji2
* Update emoji font versions to 14
* Use FilemojiCompat 3.2.0-beta01
* Make ktLint happy again
* Remove coreLibraryDesugaring and a FIXME
* Use EmojiPickerPreference.get()
* Disable emoji pack import
* Update FilemojiCompat to Beta 2
* Update FilemojiCompat to Beta 3
* Update FilemojiCompat to Beta 3.2.0 final
* Update FilemojiCompat to 3.2.1
---
app/build.gradle | 11 +-
.../com/keylesspalace/tusky/MainActivity.kt | 42 +-
.../keylesspalace/tusky/TuskyApplication.kt | 16 +-
.../tusky/adapter/PollAdapter.kt | 2 +-
.../components/account/AccountActivity.kt | 2 +-
.../components/compose/view/EditTextTyped.kt | 2 +-
.../components/preference/EmojiPreference.kt | 240 ------------
.../preference/PreferencesFragment.kt | 17 +-
.../tusky/settings/SettingsDSL.kt | 10 +-
.../tusky/util/EmojiCompatFont.kt | 364 ------------------
app/src/main/res/layout/activity_account.xml | 8 +-
app/src/main/res/layout/activity_compose.xml | 4 +-
.../main/res/layout/dialog_emojicompat.xml | 36 --
app/src/main/res/layout/item_account.xml | 2 +-
.../main/res/layout/item_account_field.xml | 4 +-
app/src/main/res/layout/item_announcement.xml | 2 +-
.../res/layout/item_autocomplete_account.xml | 2 +-
app/src/main/res/layout/item_blocked_user.xml | 2 +-
app/src/main/res/layout/item_conversation.xml | 8 +-
app/src/main/res/layout/item_draft.xml | 4 +-
app/src/main/res/layout/item_edit_field.xml | 4 +-
app/src/main/res/layout/item_follow.xml | 4 +-
.../main/res/layout/item_follow_request.xml | 4 +-
app/src/main/res/layout/item_muted_user.xml | 2 +-
app/src/main/res/layout/item_poll.xml | 2 +-
.../main/res/layout/item_report_status.xml | 12 +-
.../main/res/layout/item_scheduled_status.xml | 2 +-
app/src/main/res/layout/item_status.xml | 12 +-
.../main/res/layout/item_status_detailed.xml | 10 +-
.../res/layout/item_status_notification.xml | 8 +-
.../keylesspalace/tusky/TuskyApplication.kt | 6 +-
.../tusky/util/EmojiCompatFontTest.kt | 47 ---
32 files changed, 109 insertions(+), 782 deletions(-)
delete mode 100644 app/src/main/java/com/keylesspalace/tusky/components/preference/EmojiPreference.kt
delete mode 100644 app/src/main/java/com/keylesspalace/tusky/util/EmojiCompatFont.kt
delete mode 100644 app/src/main/res/layout/dialog_emojicompat.xml
delete mode 100644 app/src/test/java/com/keylesspalace/tusky/util/EmojiCompatFontTest.kt
diff --git a/app/build.gradle b/app/build.gradle
index 02d7907d..4484ff54 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -96,6 +96,8 @@ ext.okhttpVersion = '4.9.3'
ext.glideVersion = '4.13.1'
ext.daggerVersion = '2.41'
ext.materialdrawerVersion = '8.4.5'
+ext.emoji2_version = '1.1.0'
+ext.filemojicompat_version = '3.2.1'
// if libraries are changed here, they should also be changed in LicenseActivity
dependencies {
@@ -112,8 +114,9 @@ dependencies {
implementation "androidx.cardview:cardview:1.0.0"
implementation "androidx.preference:preference-ktx:1.2.0"
implementation "androidx.sharetarget:sharetarget:1.2.0-rc01"
- implementation "androidx.emoji:emoji:1.1.0"
- implementation "androidx.emoji:emoji-appcompat:1.1.0"
+ implementation "androidx.emoji2:emoji2:$emoji2_version"
+ implementation "androidx.emoji2:emoji2-views:$emoji2_version"
+ implementation "androidx.emoji2:emoji2-views-helper:$emoji2_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion"
@@ -170,7 +173,9 @@ dependencies {
implementation "com.github.CanHub:Android-Image-Cropper:4.1.0"
- implementation "de.c1710:filemojicompat:1.0.18"
+ implementation "de.c1710:filemojicompat-ui:$filemojicompat_version"
+ implementation "de.c1710:filemojicompat:$filemojicompat_version"
+ implementation "de.c1710:filemojicompat-defaults:$filemojicompat_version"
testImplementation "androidx.test.ext:junit:1.1.3"
testImplementation "org.robolectric:robolectric:4.4"
diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt
index 3f559399..3e203059 100644
--- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt
@@ -35,8 +35,8 @@ import androidx.appcompat.app.AlertDialog
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.ContextCompat
import androidx.core.content.pm.ShortcutManagerCompat
-import androidx.emoji.text.EmojiCompat
-import androidx.emoji.text.EmojiCompat.InitCallback
+import androidx.core.view.GravityCompat
+import androidx.emoji2.text.EmojiCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
@@ -114,6 +114,7 @@ import com.mikepenz.materialdrawer.util.updateBadge
import com.mikepenz.materialdrawer.widget.AccountHeaderView
import dagger.android.DispatchingAndroidInjector
import dagger.android.HasAndroidInjector
+import de.c1710.filemojicompat_ui.helpers.EMOJI_PREFERENCE
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers
import kotlinx.coroutines.launch
@@ -150,13 +151,8 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
private var accountLocked: Boolean = false
- private val emojiInitCallback = object : InitCallback() {
- override fun onInitialized() {
- if (!isDestroyed) {
- updateProfiles()
- }
- }
- }
+ // We need to know if the emoji pack has been changed
+ private var selectedEmojiPack: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -271,11 +267,31 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
// Flush old media that was cached for sharing
deleteStaleCachedMedia(applicationContext.getExternalFilesDir("Tusky"))
}
+
+ selectedEmojiPack = preferences.getString(EMOJI_PREFERENCE, "")
}
override fun onResume() {
super.onResume()
NotificationHelper.clearNotificationsForActiveAccount(this, accountManager)
+ val currentEmojiPack = preferences.getString(EMOJI_PREFERENCE, "")
+ if (currentEmojiPack != selectedEmojiPack) {
+ Log.d(
+ TAG,
+ "onResume: EmojiPack has been changed from %s to %s"
+ .format(selectedEmojiPack, currentEmojiPack)
+ )
+ selectedEmojiPack = currentEmojiPack
+ recreate()
+ }
+ }
+
+ override fun onStart() {
+ super.onStart()
+ // For some reason the navigation drawer is opened when the activity is recreated
+ if (binding.mainDrawerLayout.isOpen) {
+ binding.mainDrawerLayout.closeDrawer(GravityCompat.START, false)
+ }
}
override fun onBackPressed() {
@@ -333,11 +349,6 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
}
}
- override fun onDestroy() {
- super.onDestroy()
- EmojiCompat.get().unregisterInitCallback(emojiInitCallback)
- }
-
private fun forwardShare(intent: Intent) {
val composeIntent = Intent(this, ComposeActivity::class.java)
composeIntent.action = intent.action
@@ -530,7 +541,6 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
}
)
}
- EmojiCompat.get().registerInitCallback(emojiInitCallback)
}
override fun onSaveInstanceState(outState: Bundle) {
@@ -800,7 +810,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
private fun updateProfiles() {
val animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
val profiles: MutableList = accountManager.getAllAccountsOrderedByActive().map { acc ->
- val emojifiedName = EmojiCompat.get().process(acc.displayName.emojify(acc.emojis, header, animateEmojis))
+ val emojifiedName = EmojiCompat.get().process(acc.displayName.emojify(acc.emojis, header, animateEmojis))!!
ProfileDrawerItem().apply {
isSelected = acc.isActive
diff --git a/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.kt b/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.kt
index 0339a7bc..ded947a8 100644
--- a/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.kt
@@ -19,18 +19,18 @@ import android.app.Application
import android.content.Context
import android.content.res.Configuration
import android.util.Log
-import androidx.emoji.text.EmojiCompat
import androidx.preference.PreferenceManager
import androidx.work.WorkManager
import autodispose2.AutoDisposePlugins
import com.keylesspalace.tusky.components.notifications.NotificationWorkerFactory
import com.keylesspalace.tusky.di.AppInjector
-import com.keylesspalace.tusky.settings.PrefKeys
-import com.keylesspalace.tusky.util.EmojiCompatFont
import com.keylesspalace.tusky.util.LocaleManager
import com.keylesspalace.tusky.util.ThemeUtils
import dagger.android.DispatchingAndroidInjector
import dagger.android.HasAndroidInjector
+import de.c1710.filemojicompat_defaults.DefaultEmojiPackList
+import de.c1710.filemojicompat_ui.helpers.EmojiPackHelper
+import de.c1710.filemojicompat_ui.helpers.EmojiPreference
import io.reactivex.rxjava3.plugins.RxJavaPlugins
import org.conscrypt.Conscrypt
import java.security.Security
@@ -65,12 +65,10 @@ class TuskyApplication : Application(), HasAndroidInjector {
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
- // init the custom emoji fonts
- val emojiSelection = preferences.getInt(PrefKeys.EMOJI, 0)
- val emojiConfig = EmojiCompatFont.byId(emojiSelection)
- .getConfig(this)
- .setReplaceAll(true)
- EmojiCompat.init(emojiConfig)
+ // In this case, we want to have the emoji preferences merged with the other ones
+ // Copied from PreferenceManager.getDefaultSharedPreferenceName
+ EmojiPreference.sharedPreferenceName = packageName + "_preferences"
+ EmojiPackHelper.init(this, DefaultEmojiPackList.get(this), allowPackImports = false)
// init night mode
val theme = preferences.getString("appTheme", ThemeUtils.APP_THEME_DEFAULT)
diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt
index 1a60d860..9ffeca9e 100644
--- a/app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt
@@ -19,7 +19,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
-import androidx.emoji.text.EmojiCompat
+import androidx.emoji2.text.EmojiCompat
import androidx.recyclerview.widget.RecyclerView
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.ItemPollBinding
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt
index ddecd20c..7a7d5ecc 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt
@@ -37,7 +37,7 @@ import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsCompat.Type.systemBars
import androidx.core.view.updatePadding
-import androidx.emoji.text.EmojiCompat
+import androidx.emoji2.text.EmojiCompat
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.viewpager2.widget.MarginPageTransformer
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/view/EditTextTyped.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/view/EditTextTyped.kt
index dca696d8..2a1c7446 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/compose/view/EditTextTyped.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/view/EditTextTyped.kt
@@ -26,7 +26,7 @@ import androidx.core.view.OnReceiveContentListener
import androidx.core.view.ViewCompat
import androidx.core.view.inputmethod.EditorInfoCompat
import androidx.core.view.inputmethod.InputConnectionCompat
-import androidx.emoji.widget.EmojiEditTextHelper
+import androidx.emoji2.viewsintegration.EmojiEditTextHelper
class EditTextTyped @JvmOverloads constructor(
context: Context,
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/EmojiPreference.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/EmojiPreference.kt
deleted file mode 100644
index 47cb37ae..00000000
--- a/app/src/main/java/com/keylesspalace/tusky/components/preference/EmojiPreference.kt
+++ /dev/null
@@ -1,240 +0,0 @@
-package com.keylesspalace.tusky.components.preference
-
-import android.app.AlarmManager
-import android.app.PendingIntent
-import android.content.Context
-import android.content.Intent
-import android.os.Build
-import android.util.Log
-import android.view.LayoutInflater
-import android.view.View
-import android.widget.RadioButton
-import android.widget.Toast
-import androidx.appcompat.app.AlertDialog
-import androidx.preference.Preference
-import androidx.preference.PreferenceManager
-import com.keylesspalace.tusky.R
-import com.keylesspalace.tusky.SplashActivity
-import com.keylesspalace.tusky.components.notifications.NotificationHelper
-import com.keylesspalace.tusky.databinding.DialogEmojicompatBinding
-import com.keylesspalace.tusky.databinding.ItemEmojiPrefBinding
-import com.keylesspalace.tusky.util.EmojiCompatFont
-import com.keylesspalace.tusky.util.EmojiCompatFont.Companion.BLOBMOJI
-import com.keylesspalace.tusky.util.EmojiCompatFont.Companion.FONTS
-import com.keylesspalace.tusky.util.EmojiCompatFont.Companion.NOTOEMOJI
-import com.keylesspalace.tusky.util.EmojiCompatFont.Companion.SYSTEM_DEFAULT
-import com.keylesspalace.tusky.util.EmojiCompatFont.Companion.TWEMOJI
-import com.keylesspalace.tusky.util.hide
-import com.keylesspalace.tusky.util.show
-import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
-import io.reactivex.rxjava3.disposables.Disposable
-import okhttp3.OkHttpClient
-import kotlin.system.exitProcess
-
-/**
- * This Preference lets the user select their preferred emoji font
- */
-class EmojiPreference(
- context: Context,
- private val okHttpClient: OkHttpClient
-) : Preference(context) {
-
- private lateinit var selected: EmojiCompatFont
- private lateinit var original: EmojiCompatFont
- private val radioButtons = mutableListOf()
- private var updated = false
- private var currentNeedsUpdate = false
-
- private val downloadDisposables = MutableList(FONTS.size) { null }
-
- override fun onAttachedToHierarchy(preferenceManager: PreferenceManager) {
- super.onAttachedToHierarchy(preferenceManager)
-
- // Find out which font is currently active
- selected = EmojiCompatFont.byId(
- PreferenceManager.getDefaultSharedPreferences(context).getInt(key, 0)
- )
- // We'll use this later to determine if anything has changed
- original = selected
- summary = selected.getDisplay(context)
- }
-
- override fun onClick() {
- val binding = DialogEmojicompatBinding.inflate(LayoutInflater.from(context))
-
- setupItem(BLOBMOJI, binding.itemBlobmoji)
- setupItem(TWEMOJI, binding.itemTwemoji)
- setupItem(NOTOEMOJI, binding.itemNotoemoji)
- setupItem(SYSTEM_DEFAULT, binding.itemNomoji)
-
- AlertDialog.Builder(context)
- .setView(binding.root)
- .setPositiveButton(android.R.string.ok) { _, _ -> onDialogOk() }
- .setNegativeButton(android.R.string.cancel, null)
- .show()
- }
-
- private fun setupItem(font: EmojiCompatFont, binding: ItemEmojiPrefBinding) {
- // Initialize all the views
- binding.emojiName.text = font.getDisplay(context)
- binding.emojiCaption.setText(font.caption)
- binding.emojiThumbnail.setImageResource(font.img)
-
- // There needs to be a list of all the radio buttons in order to uncheck them when one is selected
- radioButtons.add(binding.emojiRadioButton)
- updateItem(font, binding)
-
- // Set actions
- binding.emojiDownload.setOnClickListener { startDownload(font, binding) }
- binding.emojiDownloadCancel.setOnClickListener { cancelDownload(font, binding) }
- binding.emojiRadioButton.setOnClickListener { radioButton: View -> select(font, radioButton as RadioButton) }
- binding.root.setOnClickListener {
- select(font, binding.emojiRadioButton)
- }
- }
-
- private fun startDownload(font: EmojiCompatFont, binding: ItemEmojiPrefBinding) {
- // Switch to downloading style
- binding.emojiDownload.hide()
- binding.emojiCaption.visibility = View.INVISIBLE
- binding.emojiProgress.show()
- binding.emojiProgress.progress = 0
- binding.emojiDownloadCancel.show()
- font.downloadFontFile(context, okHttpClient)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(
- { progress ->
- // The progress is returned as a float between 0 and 1, or -1 if it could not determined
- if (progress >= 0) {
- binding.emojiProgress.isIndeterminate = false
- val max = binding.emojiProgress.max.toFloat()
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- binding.emojiProgress.setProgress((max * progress).toInt(), true)
- } else {
- binding.emojiProgress.progress = (max * progress).toInt()
- }
- } else {
- binding.emojiProgress.isIndeterminate = true
- }
- },
- {
- Toast.makeText(context, R.string.download_failed, Toast.LENGTH_SHORT).show()
- updateItem(font, binding)
- },
- {
- finishDownload(font, binding)
- }
- ).also { downloadDisposables[font.id] = it }
- }
-
- private fun cancelDownload(font: EmojiCompatFont, binding: ItemEmojiPrefBinding) {
- font.deleteDownloadedFile(context)
- downloadDisposables[font.id]?.dispose()
- downloadDisposables[font.id] = null
- updateItem(font, binding)
- }
-
- private fun finishDownload(font: EmojiCompatFont, binding: ItemEmojiPrefBinding) {
- select(font, binding.emojiRadioButton)
- updateItem(font, binding)
- // Set the flag to restart the app (because an update has been downloaded)
- if (selected === original && currentNeedsUpdate) {
- updated = true
- currentNeedsUpdate = false
- }
- }
-
- /**
- * Select a font both visually and logically
- *
- * @param font The font to be selected
- * @param radio The radio button associated with it's visual item
- */
- private fun select(font: EmojiCompatFont, radio: RadioButton) {
- selected = font
- radioButtons.forEach { radioButton ->
- radioButton.isChecked = radioButton == radio
- }
- }
-
- /**
- * Called when a "consistent" state is reached, i.e. it's not downloading the font
- *
- * @param font The font to be displayed
- * @param binding The ItemEmojiPrefBinding to show the item in
- */
- private fun updateItem(font: EmojiCompatFont, binding: ItemEmojiPrefBinding) {
- // There's no download going on
- binding.emojiProgress.hide()
- binding.emojiDownloadCancel.hide()
- binding.emojiCaption.show()
- if (font.isDownloaded(context)) {
- // Make it selectable
- binding.emojiDownload.hide()
- binding.emojiRadioButton.show()
- binding.root.isClickable = true
- } else {
- // Make it downloadable
- binding.emojiDownload.show()
- binding.emojiRadioButton.hide()
- binding.root.isClickable = false
- }
-
- // Select it if necessary
- if (font === selected) {
- binding.emojiRadioButton.isChecked = true
- // Update available
- if (!font.isDownloaded(context)) {
- currentNeedsUpdate = true
- }
- } else {
- binding.emojiRadioButton.isChecked = false
- }
- }
-
- private fun saveSelectedFont() {
- val index = selected.id
- Log.i(TAG, "saveSelectedFont: Font ID: $index")
- PreferenceManager
- .getDefaultSharedPreferences(context)
- .edit()
- .putInt(key, index)
- .apply()
- summary = selected.getDisplay(context)
- }
-
- /**
- * User clicked ok -> save the selected font and offer to restart the app if something changed
- */
- private fun onDialogOk() {
- saveSelectedFont()
- if (selected !== original || updated) {
- AlertDialog.Builder(context)
- .setTitle(R.string.restart_required)
- .setMessage(R.string.restart_emoji)
- .setNegativeButton(R.string.later, null)
- .setPositiveButton(R.string.restart) { _, _ ->
- // Restart the app
- // From https://stackoverflow.com/a/17166729/5070653
- val launchIntent = Intent(context, SplashActivity::class.java)
- val mPendingIntent = PendingIntent.getActivity(
- context,
- 0x1f973, // This is the codepoint of the party face emoji :D
- launchIntent,
- NotificationHelper.pendingIntentFlags(false)
- )
- val mgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
- mgr.set(
- AlarmManager.RTC,
- System.currentTimeMillis() + 100,
- mPendingIntent
- )
- exitProcess(0)
- }.show()
- }
- }
-
- companion object {
- private const val TAG = "EmojiPreference"
- }
-}
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt
index 05438b72..74d8c815 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt
@@ -38,14 +38,11 @@ import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizePx
-import okhttp3.OkHttpClient
+import de.c1710.filemojicompat_ui.views.picker.preference.EmojiPickerPreference
import javax.inject.Inject
class PreferencesFragment : PreferenceFragmentCompat(), Injectable {
- @Inject
- lateinit var okhttpclient: OkHttpClient
-
@Inject
lateinit var accountManager: AccountManager
@@ -65,11 +62,7 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable {
icon = makeIcon(GoogleMaterial.Icon.gmd_palette)
}
- emojiPreference(okhttpclient) {
- setDefaultValue("system_default")
- setIcon(R.drawable.ic_emoji_24dp)
- key = PrefKeys.EMOJI
- setSummary(R.string.system_default)
+ emojiPreference(requireActivity()) {
setTitle(R.string.emoji_style)
icon = makeIcon(GoogleMaterial.Icon.gmd_sentiment_satisfied)
}
@@ -300,6 +293,12 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable {
}
}
+ override fun onDisplayPreferenceDialog(preference: Preference) {
+ if (!EmojiPickerPreference.onDisplayPreferenceDialog(this, preference)) {
+ super.onDisplayPreferenceDialog(preference)
+ }
+ }
+
companion object {
fun newInstance(): PreferencesFragment {
return PreferencesFragment()
diff --git a/app/src/main/java/com/keylesspalace/tusky/settings/SettingsDSL.kt b/app/src/main/java/com/keylesspalace/tusky/settings/SettingsDSL.kt
index 1569cb15..85270081 100644
--- a/app/src/main/java/com/keylesspalace/tusky/settings/SettingsDSL.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/settings/SettingsDSL.kt
@@ -1,7 +1,9 @@
package com.keylesspalace.tusky.settings
import android.content.Context
+import androidx.activity.result.ActivityResultRegistryOwner
import androidx.annotation.StringRes
+import androidx.lifecycle.LifecycleOwner
import androidx.preference.CheckBoxPreference
import androidx.preference.EditTextPreference
import androidx.preference.ListPreference
@@ -10,8 +12,7 @@ import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreference
-import com.keylesspalace.tusky.components.preference.EmojiPreference
-import okhttp3.OkHttpClient
+import de.c1710.filemojicompat_ui.views.picker.preference.EmojiPickerPreference
class PreferenceParent(
val context: Context,
@@ -32,8 +33,9 @@ inline fun PreferenceParent.listPreference(builder: ListPreference.() -> Unit):
return pref
}
-inline fun PreferenceParent.emojiPreference(okHttpClient: OkHttpClient, builder: EmojiPreference.() -> Unit): EmojiPreference {
- val pref = EmojiPreference(context, okHttpClient)
+inline fun PreferenceParent.emojiPreference(activity: A, builder: EmojiPickerPreference.() -> Unit): EmojiPickerPreference
+ where A : Context, A : ActivityResultRegistryOwner, A : LifecycleOwner {
+ val pref = EmojiPickerPreference.get(activity)
builder(pref)
addPref(pref)
return pref
diff --git a/app/src/main/java/com/keylesspalace/tusky/util/EmojiCompatFont.kt b/app/src/main/java/com/keylesspalace/tusky/util/EmojiCompatFont.kt
deleted file mode 100644
index 385be6c1..00000000
--- a/app/src/main/java/com/keylesspalace/tusky/util/EmojiCompatFont.kt
+++ /dev/null
@@ -1,364 +0,0 @@
-package com.keylesspalace.tusky.util
-
-import android.content.Context
-import android.util.Log
-import android.util.Pair
-import androidx.annotation.DrawableRes
-import androidx.annotation.StringRes
-import androidx.annotation.VisibleForTesting
-import com.keylesspalace.tusky.R
-import de.c1710.filemojicompat.FileEmojiCompatConfig
-import io.reactivex.rxjava3.core.Observable
-import io.reactivex.rxjava3.core.ObservableEmitter
-import io.reactivex.rxjava3.schedulers.Schedulers
-import okhttp3.OkHttpClient
-import okhttp3.Request
-import okhttp3.Response
-import okhttp3.ResponseBody
-import okhttp3.internal.toLongOrDefault
-import okio.Source
-import okio.buffer
-import okio.sink
-import java.io.EOFException
-import java.io.File
-import java.io.FilenameFilter
-import java.io.IOException
-import kotlin.math.max
-
-/**
- * This class bundles information about an emoji font as well as many convenient actions.
- */
-class EmojiCompatFont(
- val name: String,
- private val display: String,
- @StringRes val caption: Int,
- @DrawableRes val img: Int,
- val url: String,
- // The version is stored as a String in the x.xx.xx format (to be able to compare versions)
- val version: String
-) {
-
- private val versionCode = getVersionCode(version)
-
- // A list of all available font files and whether they are older than the current version or not
- // They are ordered by their version codes in ascending order
- private var existingFontFileCache: List>>? = null
-
- val id: Int
- get() = FONTS.indexOf(this)
-
- fun getDisplay(context: Context): String {
- return if (this !== SYSTEM_DEFAULT) display else context.getString(R.string.system_default)
- }
-
- /**
- * This method will return the actual font file (regardless of its existence) for
- * the current version (not necessarily the latest!).
- *
- * @return The font (TTF) file or null if called on SYSTEM_FONT
- */
- private fun getFontFile(context: Context): File? {
- return if (this !== SYSTEM_DEFAULT) {
- val directory = File(context.getExternalFilesDir(null), DIRECTORY)
- File(directory, "$name$version.ttf")
- } else {
- null
- }
- }
-
- fun getConfig(context: Context): FileEmojiCompatConfig {
- return FileEmojiCompatConfig(context, getLatestFontFile(context))
- }
-
- fun isDownloaded(context: Context): Boolean {
- return this === SYSTEM_DEFAULT || getFontFile(context)?.exists() == true || fontFileExists(context)
- }
-
- /**
- * Checks whether there is already a font version that satisfies the current version, i.e. it
- * has a higher or equal version code.
- *
- * @param context The Context
- * @return Whether there is a font file with a higher or equal version code to the current
- */
- private fun fontFileExists(context: Context): Boolean {
- val existingFontFiles = getExistingFontFiles(context)
- return if (existingFontFiles.isNotEmpty()) {
- compareVersions(existingFontFiles.last().second, versionCode) >= 0
- } else {
- false
- }
- }
-
- /**
- * Deletes any older version of a font
- *
- * @param context The current Context
- */
- private fun deleteOldVersions(context: Context) {
- val existingFontFiles = getExistingFontFiles(context)
- Log.d(TAG, "deleting old versions...")
- Log.d(TAG, String.format("deleteOldVersions: Found %d other font files", existingFontFiles.size))
- for (fileExists in existingFontFiles) {
- if (compareVersions(fileExists.second, versionCode) < 0) {
- val file = fileExists.first
- // Uses side effects!
- Log.d(
- TAG,
- String.format(
- "Deleted %s successfully: %s", file.absolutePath,
- file.delete()
- )
- )
- }
- }
- }
-
- /**
- * Loads all font files that are inside the files directory into an ArrayList with the information
- * on whether they are older than the currently available version or not.
- *
- * @param context The Context
- */
- private fun getExistingFontFiles(context: Context): List>> {
- // Only load it once
- existingFontFileCache?.let {
- return it
- }
- // If we call this on the system default font, just return nothing...
- if (this === SYSTEM_DEFAULT) {
- existingFontFileCache = emptyList()
- return emptyList()
- }
-
- val directory = File(context.getExternalFilesDir(null), DIRECTORY)
- // It will search for old versions using a regex that matches the font's name plus
- // (if present) a version code. No version code will be regarded as version 0.
- val fontRegex = "$name(\\d+(\\.\\d+)*)?\\.ttf".toPattern()
- val ttfFilter = FilenameFilter { _, name: String -> name.endsWith(".ttf") }
- val foundFontFiles = directory.listFiles(ttfFilter).orEmpty()
- Log.d(
- TAG,
- String.format(
- "loadExistingFontFiles: %d other font files found",
- foundFontFiles.size
- )
- )
-
- return foundFontFiles.map { file ->
- val matcher = fontRegex.matcher(file.name)
- val versionCode = if (matcher.matches()) {
- val version = matcher.group(1)
- getVersionCode(version)
- } else {
- listOf(0)
- }
- Pair(file, versionCode)
- }.sortedWith { a, b ->
- compareVersions(a.second, b.second)
- }.also {
- existingFontFileCache = it
- }
- }
-
- /**
- * Returns the current or latest version of this font file (if there is any)
- *
- * @param context The Context
- * @return The file for this font with the current or (if not existent) highest version code or null if there is no file for this font.
- */
- private fun getLatestFontFile(context: Context): File? {
- val current = getFontFile(context)
- if (current != null && current.exists()) return current
- val existingFontFiles = getExistingFontFiles(context)
- return existingFontFiles.firstOrNull()?.first
- }
-
- private fun getVersionCode(version: String?): List {
- if (version == null) return listOf(0)
- return version.split(".").map {
- it.toIntOrNull() ?: 0
- }
- }
-
- fun downloadFontFile(
- context: Context,
- okHttpClient: OkHttpClient
- ): Observable {
- return Observable.create { emitter: ObservableEmitter ->
- // It is possible (and very likely) that the file does not exist yet
- val downloadFile = getFontFile(context)!!
- if (!downloadFile.exists()) {
- downloadFile.parentFile?.mkdirs()
- downloadFile.createNewFile()
- }
- val request = Request.Builder().url(url)
- .build()
-
- val sink = downloadFile.sink().buffer()
- var source: Source? = null
- try {
- // Download!
- val response = okHttpClient.newCall(request).execute()
-
- val responseBody = response.body
- if (response.isSuccessful && responseBody != null) {
- val size = response.length()
- var progress = 0f
- source = responseBody.source()
- try {
- while (!emitter.isDisposed) {
- sink.write(source, CHUNK_SIZE)
- progress += CHUNK_SIZE.toFloat()
- if (size > 0) {
- emitter.onNext(progress / size)
- } else {
- emitter.onNext(-1f)
- }
- }
- } catch (ex: EOFException) {
- /*
- This means we've finished downloading the file since sink.write
- will throw an EOFException when the file to be read is empty.
- */
- }
- } else {
- Log.e(TAG, "Downloading $url failed. Status code: ${response.code}")
- emitter.tryOnError(Exception())
- }
- } catch (ex: IOException) {
- Log.e(TAG, "Downloading $url failed.", ex)
- downloadFile.deleteIfExists()
- emitter.tryOnError(ex)
- } finally {
- source?.close()
- sink.close()
- if (emitter.isDisposed) {
- downloadFile.deleteIfExists()
- } else {
- deleteOldVersions(context)
- emitter.onComplete()
- }
- }
- }
- .subscribeOn(Schedulers.io())
- }
-
- /**
- * Deletes the downloaded file, if it exists. Should be called when a download gets cancelled.
- */
- fun deleteDownloadedFile(context: Context) {
- getFontFile(context)?.deleteIfExists()
- }
-
- override fun toString(): String {
- return display
- }
-
- companion object {
- private const val TAG = "EmojiCompatFont"
-
- /**
- * This String represents the sub-directory the fonts are stored in.
- */
- private const val DIRECTORY = "emoji"
-
- private const val CHUNK_SIZE = 4096L
-
- // The system font gets some special behavior...
- val SYSTEM_DEFAULT = EmojiCompatFont(
- "system-default",
- "System Default",
- R.string.caption_systememoji,
- R.drawable.ic_emoji_34dp,
- "",
- "0"
- )
- val BLOBMOJI = EmojiCompatFont(
- "Blobmoji",
- "Blobmoji",
- R.string.caption_blobmoji,
- R.drawable.ic_blobmoji,
- "https://tusky.app/hosted/emoji/BlobmojiCompat.ttf",
- "14.0.1"
- )
- val TWEMOJI = EmojiCompatFont(
- "Twemoji",
- "Twemoji",
- R.string.caption_twemoji,
- R.drawable.ic_twemoji,
- "https://tusky.app/hosted/emoji/TwemojiCompat.ttf",
- "14.0.0"
- )
- val NOTOEMOJI = EmojiCompatFont(
- "NotoEmoji",
- "Noto Emoji",
- R.string.caption_notoemoji,
- R.drawable.ic_notoemoji,
- "https://tusky.app/hosted/emoji/NotoEmojiCompat.ttf",
- "14.0.0"
- )
-
- /**
- * This array stores all available EmojiCompat fonts.
- * References to them can simply be saved by saving their indices
- */
- val FONTS = listOf(SYSTEM_DEFAULT, BLOBMOJI, TWEMOJI, NOTOEMOJI)
-
- /**
- * Returns the Emoji font associated with this ID
- *
- * @param id the ID of this font
- * @return the corresponding font. Will default to SYSTEM_DEFAULT if not in range.
- */
- fun byId(id: Int): EmojiCompatFont = FONTS.getOrElse(id) { SYSTEM_DEFAULT }
-
- /**
- * Compares two version codes to each other
- *
- * @param versionA The first version
- * @param versionB The second version
- * @return -1 if versionA < versionB, 1 if versionA > versionB and 0 otherwise
- */
- @VisibleForTesting
- fun compareVersions(versionA: List, versionB: List): Int {
- val len = max(versionB.size, versionA.size)
- for (i in 0 until len) {
-
- val vA = versionA.getOrElse(i) { 0 }
- val vB = versionB.getOrElse(i) { 0 }
-
- // It needs to be decided on the next level
- if (vA == vB) continue
- // Okay, is version B newer or version A?
- return vA.compareTo(vB)
- }
-
- // The versions are equal
- return 0
- }
-
- /**
- * This method is needed because when transparent compression is used OkHttp reports
- * [ResponseBody.contentLength] as -1. We try to get the header which server sent
- * us manually here.
- *
- * @see [OkHttp issue 259](https://github.com/square/okhttp/issues/259)
- */
- private fun Response.length(): Long {
- networkResponse?.let {
- val header = it.header("Content-Length") ?: return -1
- return header.toLongOrDefault(-1)
- }
-
- // In case it's a fully cached response
- return body?.contentLength() ?: -1
- }
-
- private fun File.deleteIfExists() {
- if (exists() && !delete()) {
- Log.e(TAG, "Could not delete file $this")
- }
- }
- }
-}
diff --git a/app/src/main/res/layout/activity_account.xml b/app/src/main/res/layout/activity_account.xml
index 89a56d9e..31b37ad8 100644
--- a/app/src/main/res/layout/activity_account.xml
+++ b/app/src/main/res/layout/activity_account.xml
@@ -112,7 +112,7 @@
app:layout_constraintStart_toStartOf="@id/guideAvatar"
app:layout_constraintTop_toTopOf="@+id/accountFollowButton" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_account.xml b/app/src/main/res/layout/item_account.xml
index a7b3a0ef..c1565e09 100644
--- a/app/src/main/res/layout/item_account.xml
+++ b/app/src/main/res/layout/item_account.xml
@@ -32,7 +32,7 @@
tools:src="#000"
tools:visibility="visible" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Date: Wed, 27 Apr 2022 11:58:39 -0600
Subject: [PATCH 029/104] Remove Identity Proof Support (#2456)
* Remove IdentityProof.kt and refactor
* Remove accountFieldData from viewmodel
* Remove unused imports
---
.../components/account/AccountActivity.kt | 8 +----
.../components/account/AccountFieldAdapter.kt | 32 +++++--------------
.../components/account/AccountViewModel.kt | 28 ----------------
.../tusky/entity/IdentityProof.kt | 9 ------
.../tusky/network/MastodonApi.kt | 6 ----
5 files changed, 9 insertions(+), 74 deletions(-)
delete mode 100644 app/src/main/java/com/keylesspalace/tusky/entity/IdentityProof.kt
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt
index 7a7d5ecc..cc8719bb 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt
@@ -372,12 +372,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
.show()
}
}
- viewModel.accountFieldData.observe(
- this
- ) {
- accountFieldAdapter.fields = it
- accountFieldAdapter.notifyDataSetChanged()
- }
viewModel.noteSaved.observe(this) {
binding.saveNoteInfo.visible(it, View.INVISIBLE)
}
@@ -409,7 +403,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
val emojifiedNote = account.note.parseAsMastodonHtml().emojify(account.emojis, binding.accountNoteTextView, animateEmojis)
setClickableText(binding.accountNoteTextView, emojifiedNote, emptyList(), null, this)
- // accountFieldAdapter.fields = account.fields ?: emptyList()
+ accountFieldAdapter.fields = account.fields ?: emptyList()
accountFieldAdapter.emojis = account.emojis ?: emptyList()
accountFieldAdapter.notifyDataSetChanged()
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/account/AccountFieldAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/account/AccountFieldAdapter.kt
index d51bb145..86acb813 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/account/AccountFieldAdapter.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/account/AccountFieldAdapter.kt
@@ -15,7 +15,6 @@
package com.keylesspalace.tusky.components.account
-import android.text.method.LinkMovementMethod
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
@@ -23,11 +22,8 @@ import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.ItemAccountFieldBinding
import com.keylesspalace.tusky.entity.Emoji
import com.keylesspalace.tusky.entity.Field
-import com.keylesspalace.tusky.entity.IdentityProof
import com.keylesspalace.tusky.interfaces.LinkListener
import com.keylesspalace.tusky.util.BindingHolder
-import com.keylesspalace.tusky.util.Either
-import com.keylesspalace.tusky.util.createClickableText
import com.keylesspalace.tusky.util.emojify
import com.keylesspalace.tusky.util.parseAsMastodonHtml
import com.keylesspalace.tusky.util.setClickableText
@@ -38,7 +34,7 @@ class AccountFieldAdapter(
) : RecyclerView.Adapter>() {
var emojis: List = emptyList()
- var fields: List> = emptyList()
+ var fields: List = emptyList()
override fun getItemCount() = fields.size
@@ -48,32 +44,20 @@ class AccountFieldAdapter(
}
override fun onBindViewHolder(holder: BindingHolder, position: Int) {
- val proofOrField = fields[position]
+ val field = fields[position]
val nameTextView = holder.binding.accountFieldName
val valueTextView = holder.binding.accountFieldValue
- if (proofOrField.isLeft()) {
- val identityProof = proofOrField.asLeft()
+ val emojifiedName = field.name.emojify(emojis, nameTextView, animateEmojis)
+ nameTextView.text = emojifiedName
- nameTextView.text = identityProof.provider
- valueTextView.text = createClickableText(identityProof.username, identityProof.profileUrl)
-
- valueTextView.movementMethod = LinkMovementMethod.getInstance()
+ val emojifiedValue = field.value.parseAsMastodonHtml().emojify(emojis, valueTextView, animateEmojis)
+ setClickableText(valueTextView, emojifiedValue, emptyList(), null, linkListener)
+ if (field.verifiedAt != null) {
valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_check_circle, 0)
} else {
- val field = proofOrField.asRight()
- val emojifiedName = field.name.emojify(emojis, nameTextView, animateEmojis)
- nameTextView.text = emojifiedName
-
- val emojifiedValue = field.value.parseAsMastodonHtml().emojify(emojis, valueTextView, animateEmojis)
- setClickableText(valueTextView, emojifiedValue, emptyList(), null, linkListener)
-
- if (field.verifiedAt != null) {
- valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_check_circle, 0)
- } else {
- valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0)
- }
+ valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0)
}
}
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/account/AccountViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/account/AccountViewModel.kt
index 6fa988ac..664651eb 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/account/AccountViewModel.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/account/AccountViewModel.kt
@@ -10,17 +10,13 @@ import com.keylesspalace.tusky.appstore.ProfileEditedEvent
import com.keylesspalace.tusky.appstore.UnfollowEvent
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.entity.Account
-import com.keylesspalace.tusky.entity.Field
-import com.keylesspalace.tusky.entity.IdentityProof
import com.keylesspalace.tusky.entity.Relationship
import com.keylesspalace.tusky.network.MastodonApi
-import com.keylesspalace.tusky.util.Either
import com.keylesspalace.tusky.util.Error
import com.keylesspalace.tusky.util.Loading
import com.keylesspalace.tusky.util.Resource
import com.keylesspalace.tusky.util.RxAwareViewModel
import com.keylesspalace.tusky.util.Success
-import com.keylesspalace.tusky.util.combineOptionalLiveData
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.disposables.Disposable
import retrofit2.Call
@@ -40,13 +36,6 @@ class AccountViewModel @Inject constructor(
val noteSaved = MutableLiveData()
- private val identityProofData = MutableLiveData>()
-
- val accountFieldData = combineOptionalLiveData(accountData, identityProofData) { accountRes, identityProofs ->
- identityProofs.orEmpty().map { Either.Left(it) }
- .plus(accountRes?.data?.fields.orEmpty().map { Either.Right(it) })
- }
-
val isRefreshing = MutableLiveData().apply { value = false }
private var isDataLoading = false
@@ -106,22 +95,6 @@ class AccountViewModel @Inject constructor(
}
}
- private fun obtainIdentityProof(reload: Boolean = false) {
- if (identityProofData.value == null || reload) {
-
- mastodonApi.identityProofs(accountId)
- .subscribe(
- { proofs ->
- identityProofData.postValue(proofs)
- },
- { t ->
- Log.w(TAG, "failed obtaining identity proofs", t)
- }
- )
- .autoDispose()
- }
- }
-
fun changeFollowState() {
val relationship = relationshipData.value?.data
if (relationship?.following == true || relationship?.requested == true) {
@@ -314,7 +287,6 @@ class AccountViewModel @Inject constructor(
return
accountId.let {
obtainAccount(isReload)
- obtainIdentityProof()
if (!isSelf)
obtainRelationship(isReload)
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/IdentityProof.kt b/app/src/main/java/com/keylesspalace/tusky/entity/IdentityProof.kt
deleted file mode 100644
index 98af734b..00000000
--- a/app/src/main/java/com/keylesspalace/tusky/entity/IdentityProof.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package com.keylesspalace.tusky.entity
-
-import com.google.gson.annotations.SerializedName
-
-data class IdentityProof(
- val provider: String,
- @SerializedName("provider_username") val username: String,
- @SerializedName("profile_url") val profileUrl: String
-)
diff --git a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt
index 2340c5dd..02af5caa 100644
--- a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt
@@ -24,7 +24,6 @@ import com.keylesspalace.tusky.entity.Conversation
import com.keylesspalace.tusky.entity.DeletedStatus
import com.keylesspalace.tusky.entity.Emoji
import com.keylesspalace.tusky.entity.Filter
-import com.keylesspalace.tusky.entity.IdentityProof
import com.keylesspalace.tusky.entity.Instance
import com.keylesspalace.tusky.entity.Marker
import com.keylesspalace.tusky.entity.MastoList
@@ -367,11 +366,6 @@ interface MastodonApi {
@Query("id[]") accountIds: List
): Single>
- @GET("api/v1/accounts/{id}/identity_proofs")
- fun identityProofs(
- @Path("id") accountId: String
- ): Single>
-
@POST("api/v1/pleroma/accounts/{id}/subscribe")
fun subscribeAccount(
@Path("id") accountId: String
From f34aaa889d8663acb351e65d172bdfe5f806763e Mon Sep 17 00:00:00 2001
From: codl
Date: Thu, 28 Apr 2022 16:13:28 +0000
Subject: [PATCH 030/104] Translated using Weblate (French)
Currently translated at 100.0% (477 of 477 strings)
Co-authored-by: codl
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/fr/
Translation: Tusky/Tusky
---
app/src/main/res/values-fr/strings.xml | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 0490c87b..248d4f0d 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -544,4 +544,8 @@
Nouveaux comptes
Notifications quand quelqu\'un crée un nouveau compte
un nouveau compte a été créé
+ %s a modifié son message
+ un message avec lequel j\'ai interagi est modifié
+ Messages modifiés
+ Notifications quand un post avec lequel vous avez interagi est modifié
\ No newline at end of file
From 3a7f31f833eabe3ac9a460b3482ed642a62a160d Mon Sep 17 00:00:00 2001
From: "Gera, Zoltan"
Date: Thu, 28 Apr 2022 16:13:28 +0000
Subject: [PATCH 031/104] Translated using Weblate (Hungarian)
Currently translated at 100.0% (477 of 477 strings)
Translated using Weblate (Hungarian)
Currently translated at 100.0% (477 of 477 strings)
Co-authored-by: Gera, Zoltan
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/hu/
Translation: Tusky/Tusky
---
app/src/main/res/values-hu/strings.xml | 128 ++++++++++++++-----------
1 file changed, 71 insertions(+), 57 deletions(-)
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index 17e4e788..18a4bb8a 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -9,26 +9,26 @@
Azonosítatlan engedélyezési hiba történt.
Engedély megtagadva.
Bejelentkezési token megszerzése sikertelen.
- Túl hosszú a tülkölés!
+ Túl hosszú a bejegyzés!
A fájlnak kisebbnek kell lennie, mint 8 MB.
A videofájloknak kisebbnek kell lenniük, mint 40 MB.
Ilyen típusú fájlt nem lehet feltölteni.
Fájl megnyitása sikertelen.
Média olvasási engedély szükséges.
Média tárolási engedély szükséges.
- Képek és videók egyszerre nem csatolhatók ugyanazon tülköléshez.
+ Képek és videók egyszerre nem csatolhatóak ugyanazon bejegyzéshez.
Feltöltés sikertelen.
- Nem sikerült elküldeni a tülköt.
+ Nem sikerült elküldeni a bejegyzést.
Kezdőlap
Értesítések
Helyi
Föderációs
Közvetlen üzenetek
Fülek
- Tülk
- Tülkök
+ Szál
+ Bejegyzések
Válaszokkal
- Rögzített
+ Kitűzött
Követett
Követő
Kedvencek
@@ -40,17 +40,17 @@
Licenszek
\@%s
%s megtolta
- Kényes tartalom
+ Érzékeny tartalom
Rejtett média
- Kattints a megnézéshez
+ Kattints a megtekintéshez
Mutass többet
Mutass kevesebbet
Kibontás
Összecsukás
Nincs itt semmi.
Üres tartalom. Húzd le a frissítéshez!
- %s megtolta a tülködet
- %s kedvencnek jelölte tülködet
+ %s megtolta a bejegyzésedet
+ %s kedvencnek jelölte a bejegyzésedet
%s bekövetett
\@%s jelentése
Egyéb megjegyzés?
@@ -100,7 +100,7 @@
Elutasítás
Keresés
Piszkozatok
- Tülkök láthatósága
+ Bejegyzés láthatósága
Tartalom figyelmeztetés
Emoji billentyűzet
Fül hozzáadása
@@ -115,8 +115,8 @@
Link másolása
Megnyitás mint %s
Megosztás mint …
- Tülk URL megosztása…
- Tülk megosztása…
+ Bejegyzés URL megosztása…
+ Bejegyzés megosztása…
Elküldve!
Felhasználó letiltása feloldva
Felhasználó némítása feloldva
@@ -132,7 +132,7 @@
Válasz…
Profilkép
Fejléc
- Mi az a szerver\?
+ Mi az a példány\?
Csatlakozás…
Bármely példány címét vagy domain nevét beírhatod ide, mint a mastodon.social, az icosahedron.website, a social.tchncs.de és mások!
\n
@@ -146,11 +146,11 @@
Letöltés
Visszavonod a követési kérelmet?
Követés megszüntetése?
- Törlöd ezt a tülköt?
- Nyilvános: Tülkölés nyilvános idővonalra
+ Törlöd ezt a bejegyzést\?
+ Nyilvános: Bejegyzés nyilvános idővonalra
Listázatlan: Nem jelenik meg a nyilvános idővonalon
- Csak követőknek: Tülkölés csak követőknek
- Közvetlen: Tülkölés csak a megemlített felhasználóknak
+ Csak követőknek: Bejegyzés csak követőknek
+ Közvetlen: Bejegyzés csak a megemlített felhasználóknak
Értesítések
Értesítések
Figyelmeztetések
@@ -160,8 +160,8 @@
Értesítsen, ha
megemlítettek
bekövettek
- tülkömet megtolták
- tülkömet kedvenccé tették
+ bejegyzésemet megtolták
+ bejegyzésemet kedvencnek jelölték
Megjelenés
Idővonalak
Sötét
@@ -181,13 +181,13 @@
HTTP proxy engedélyezése
HTTP proxy szerver
HTTP Proxy port
- Tülkök alapértelmezett láthatósága
+ Bejegyzések alapértelmezett láthatósága
Minden média kényesnek jelölése
A beállítások szinkronizálása nem sikerült
Nyilvános
Listázatlan
Csak követőknek
- Tülkölés szöveg mérete
+ Bejegyzés szövegének mérete
Legkisebb
Kicsi
Közepes
@@ -198,9 +198,9 @@
Új követők
Értesítések új követőkről
Megtolások
- Értesítések tülkjeid megtolása esetén
+ Értesítések bejegyzéseid megtolása esetén
Kedvencek
- Értesítések mikor tülkjeidet kedvencnek jelölik
+ Értesítések amikor a bejegyzéseidet kedvencnek jelölik
%s megemlített téged
%1$s, %2$s, %3$s és még %4$d
%1$s, %2$s meg %3$s
@@ -224,10 +224,10 @@
Hibajelentés & új funkciók igénylése:
\n https://github.com/tuskyapp/Tusky/issues
Tusky profilja
- Tülk tartalmának megosztása
- Tülk linkjének megosztása
+ Bejegyzés tartalmának megosztása
+ Bejegyzés hivatkozásának megosztása
Képek
- Videók
+ Videó
Követés kérelmezve
Követ téged
@@ -241,19 +241,19 @@
Törlés
Fiók zárolása
Elmented a piszkozatot\?
- Tülk elküldése…
- A tülk elküldése nem sikerült
- Tülkök elküldése
+ Bejegyzés küldése…
+ A bejegyzés elküldése sikertelen
+ Bejegyzések elküldése
Küldés megszakítva
- A tülk másolatát elmentettük a piszkozataid közé
+ A bejegyzés másolatát elmentettük a piszkozataid közé
Szerkesztés
- A %s szervernek nincsenek egyedi emoji-jai
+ A %s példánynak nincsenek egyedi emoji-jai
Vágólapra másolva
Emoji stílus
Rendszer alapértelmezés
Először le kell töltened ezeket az emoji készleteket
Keresés…
- Tülk megnyitása
+ Bejegyzés megnyitása
Az app újraindítása szükséges
A beállítások érvényesítéséhez újra kell indítani a Tuskyt
Később
@@ -280,8 +280,7 @@
- elérted a fülek maximális számát (%1$d)
- elérted a fülek maximális számát (%1$d)
- Nincs leírás
-
+ Nincs leírás
Nyilvános
Követők
Kedvenc eltávolítása
@@ -290,7 +289,7 @@
Média letöltése
Média letöltése
Média megosztása következővel…
- Törlöd és újraírod ezt a tülköt\?
+ Törlöd és újraírod ezt a bejegyzést\?
befejeződött egy szavazás
Szűrők
Rendszer téma használata
@@ -342,7 +341,7 @@
Általad követettek keresése
Fiók hozzáadása a listához
Fiók eltávolítása a listából
- Tülkölés %1$s fiókkal
+ Bejegyzés %1$s fiókkal
Cím beállítása nem sikerült
- Leírás látássérülteknek
@@ -350,7 +349,7 @@
Cím beállítása
Minden követődet külön engedélyezned kell
- Minden tülk kibontása/összecsukása
+ Összes bejegyzés kibontása/összecsukása
A Google jelenlegi emodzsi készlete
Megtolás az eredeti közönségnek
Megtolás visszavonása
@@ -368,7 +367,7 @@
%1$s
%1$s, %2$s és még %3$d
Média: %s
- Tartalom figyelmeztetés: %s
+ Tartalomfigyelmeztetés: %s
Megtolt
Kedvelt
Listázatlan
@@ -379,7 +378,7 @@
Törlés
Szűrés
Alkalmaz
- Tülk szerkesztése
+ Bejegyzés létrehozása
Szerkesztés
Biztos, hogy minden értesítésedet véglegesen törlöd\?
Műveletek a(z) %s képpel
@@ -416,11 +415,11 @@
Egyéb megjegyzések
Továbbítás neki %s
Nem sikerült a bejelentés
- Nem sikerült a tülkök letöltése
+ Sikertelen a bejegyzések letöltése
A bejelentést a szervered moderátorának küldjük el. Alább megadhatsz egy magyarázatot arra, hogy miért jelented be ezt a fiókot:
A fiók egy másik szerverről származik. Küldjünk oda is egy anonimizált másolatot a bejelentésről\?
Értesítések szűrőjének mutatása
- Tartalom-figyelmeztetéssel ellátott tülkök kifejtése mindig
+ Tartalomfigyelmeztetéssel ellátott bejegyzések kinyitása mindig
Fiókok
Sikertelen keresés
Szavazás hozzáadása
@@ -436,12 +435,12 @@
Több lehetőség
Válasz %d
Szerkesztés
- Időzített tülkök
+ Időzített bejegyzések
Szerkesztés
- Időzített tülkök
- Tülk Időzítése
+ Időzített bejegyzések
+ Bejegyzés Időzítése
Visszaállítás
- Nem találjuk ezt a tülköt %s
+ Nem találjuk ezt a bejegyzést %s
Könyvjelzők
Könyvjelzőzés
Könyvjelzők
@@ -451,7 +450,7 @@
Lista
A hangfájloknak kisebbnek kell lenniük, mint 40 MB.
Nincs egy piszkozatod sem.
- Nincs egy ütemezett tülköd sem.
+ Nincs egy ütemezett bejegyzésed sem.
A Mastodonban a legrövidebb ütemezhető időintervallum 5 perc.
Követési kérelmek
Jóváhagyó ablak mutatása megtolás előtt
@@ -484,34 +483,34 @@
Saját, mások számára nem látható megjegyzés erről a fiókról
Nincsenek közlemények.
Közlemények
- A tülköt, melyre válaszul piszkozatot készítettél törölték
+ A bejegyzést, melyre válaszul piszkozatot készítettél törölték
Piszkozat törölve
Nem sikerült a Válasz információit betölteni
- Ez a tülk nem küldődött el!
+ Ezt a bejegyzést nem tudtuk elküldeni!
Tényleg le akarod törölni a %s listát\?
- Nem tölthetsz fel %1$d médiacsatolmányból többet.
- Nem tölthetsz fel %1$d médiacsatolmányból többet.
Profilok mérőszámainak elrejtése
- Tülkök mérőszámainak elrejtése
+ Bejegyzések mérőszámainak elrejtése
Idővonali értesítések korlátozása
Értesítések Áttekintése
- Pár információ, ami befolyásolhatja a mentális egészségedet rejtve marad. Ilyenek pl.:
+ Pár információ, ami befolyásolhatja a mentális jóllétedet rejtve marad. Ilyenek pl.:
\n
\n - Kedvenc/Megtolás/Bekövetés értesítései
-\n - Kedvenc/Megtolás számlálók a tülkökön
-\n - Követő/Tülk statisztikák a profilokon
+\n - Kedvenc/Megtolás számlálók a bejegyzéseken
+\n - Követő/Bejegyzés statisztikák a profilokon
\n
\nA Push-értesítéseket ez nem befolyásolja, de kézzel átállíthatod az értesítési beállításaidat.
Végtelen
Időtartam
Csatolmányok
Audio
- Értesítések általam követett személy új tülkjeiről
- Új tülkök
- valaki, akit követek újat tülkölt
- %s épp tülkölt
+ Értesítések általam követett személy új bejegyzéseiről
+ Új bejegyzések
+ valaki, akit követek új bejegyzést tett közzé
+ %s épp bejegyzést írt
Jóllét
Egyedi emojik animálása
Leiratkozás
@@ -521,4 +520,19 @@
Beszélgetés törlése
Könyvjelző törlése
Jóváhagyás mutatása kedvencnek jelölés előtt
+ %s szerkesztette a bejegyzését
+ szerkesztették a bejegyzést, mellyel dolgod volt
+ %s regisztrált
+ valaki regisztrált
+ Regisztrációk
+ Értesítések új felhasználókról
+ 14 nap
+ 30 nap
+ 60 nap
+ 90 nap
+ 180 nap
+ 365 nap
+ Bejegyzések szerkesztése
+ Értesítések olyan bejegyzések szerkesztéséről, melyekkel már dolgod volt
+ Bejegyzés Létrehozása
\ No newline at end of file
From bdb53a333bf63ac46ee0f393a60056d2ad1ea980 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=E1=BB=93=20Nh=E1=BA=A5t=20Duy?=
Date: Thu, 28 Apr 2022 16:13:28 +0000
Subject: [PATCH 032/104] Translated using Weblate (Vietnamese)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Currently translated at 100.0% (477 of 477 strings)
Co-authored-by: Hồ Nhất Duy
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/
Translation: Tusky/Tusky
---
app/src/main/res/values-vi/strings.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
index 16704821..0746ac6b 100644
--- a/app/src/main/res/values-vi/strings.xml
+++ b/app/src/main/res/values-vi/strings.xml
@@ -413,7 +413,7 @@
Tusky có sử dụng mã nguồn từ những dự án mã nguồn mở sau:
Hủy đăng lại
Đăng lại công khai
- %1$s đã dời sang:
+ %1$s đã chuyển sang:
Tài khoản Bot
Tải về thất bại
Emoji của Google
From 04e51554543f630e7d6e31b9a107c11442d4e0e7 Mon Sep 17 00:00:00 2001
From: GunChleoc
Date: Thu, 28 Apr 2022 16:13:28 +0000
Subject: [PATCH 033/104] Translated using Weblate (Gaelic)
Currently translated at 100.0% (477 of 477 strings)
Translated using Weblate (Gaelic)
Currently translated at 100.0% (477 of 477 strings)
Co-authored-by: GunChleoc
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/gd/
Translation: Tusky/Tusky
---
app/src/main/res/values-gd/strings.xml | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/app/src/main/res/values-gd/strings.xml b/app/src/main/res/values-gd/strings.xml
index 19ae1240..eb80f729 100644
--- a/app/src/main/res/values-gd/strings.xml
+++ b/app/src/main/res/values-gd/strings.xml
@@ -249,16 +249,13 @@
A bheil thu airson a shàbhaladh ’na dhreachd\?
Feumaidh tu gabhail ri luchd-leantainn ùr a làimh
Glais an cunntas
- Suidhidh am fo-thiotal
+ Suidhich am fo-thiotal
- Mìnich e dhan fheadhainn air a bheil cion-lèirsinn
\n(%d caractar(an) air a char as fhaide)
- - Mìnich e dhan fheadhainn air a bheil cion-lèirsinn
-\n(%d caractar(an) air a char as fhaide)
- - Mìnich e dhan fheadhainn air a bheil cion-lèirsinn
-\n(%d caractar(an) air a char as fhaide)
- - Mìnich e dhan fheadhainn air a bheil cion-lèirsinn
-\n(%d caractar(an) air a char as fhaide)
+
+
+
Cha deach leinn am fo-thiotal a shuidheachadh
A’ postadh leis a’ chunntas %1$s
@@ -552,4 +549,7 @@
Brathan mu cleachdaichean ùra
chlàraich cuideigin
Dheasaich %s am post aca
+ Deasachadh puist
+ Brathan nuair a thèid postaichean a rinn thu conaltradh leotha a dheasachadh
+ chaidh post a rinn mi conaltradh leis a deasachadh
\ No newline at end of file
From 3a11b9900e08ef89f74e6229b91be7550a9f5895 Mon Sep 17 00:00:00 2001
From: Constantin A <10349490+C1710@users.noreply.github.com>
Date: Thu, 28 Apr 2022 18:55:10 +0200
Subject: [PATCH 034/104] EmojiCompat fix (#2468)
* Add back the emojiInitCallback and move EmojiCompat init
* Small adjustments
* Make sure that we don't hit the IllegalStateException when EmojiCompat-ing the display names
* Add a TODO for when Material Drawer 9 can be used
* Remove EmojiCompat.process and initcallback
---
app/src/main/java/com/keylesspalace/tusky/MainActivity.kt | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt
index 3e203059..466dba16 100644
--- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt
@@ -36,7 +36,6 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.ContextCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.view.GravityCompat
-import androidx.emoji2.text.EmojiCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
@@ -810,11 +809,9 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
private fun updateProfiles() {
val animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
val profiles: MutableList = accountManager.getAllAccountsOrderedByActive().map { acc ->
- val emojifiedName = EmojiCompat.get().process(acc.displayName.emojify(acc.emojis, header, animateEmojis))!!
-
ProfileDrawerItem().apply {
isSelected = acc.isActive
- nameText = emojifiedName
+ nameText = acc.displayName.emojify(acc.emojis, header, animateEmojis)
iconUrl = acc.profilePictureUrl
isNameShown = true
identifier = acc.id
From a3603c2154d475cb5c6d3d8b6f4d465e4ba140a6 Mon Sep 17 00:00:00 2001
From: codl
Date: Thu, 28 Apr 2022 16:55:14 +0000
Subject: [PATCH 035/104] Translated using Weblate (French)
Currently translated at 100.0% (477 of 477 strings)
Co-authored-by: codl
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/fr/
Translation: Tusky/Tusky
---
app/src/main/res/values-fr/strings.xml | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 0490c87b..248d4f0d 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -544,4 +544,8 @@
Nouveaux comptes
Notifications quand quelqu\'un crée un nouveau compte
un nouveau compte a été créé
+ %s a modifié son message
+ un message avec lequel j\'ai interagi est modifié
+ Messages modifiés
+ Notifications quand un post avec lequel vous avez interagi est modifié
\ No newline at end of file
From 464f9d6412731029cf07bc53ea8cc85f63a9673b Mon Sep 17 00:00:00 2001
From: "Gera, Zoltan"
Date: Thu, 28 Apr 2022 16:55:14 +0000
Subject: [PATCH 036/104] Translated using Weblate (Hungarian)
Currently translated at 100.0% (477 of 477 strings)
Translated using Weblate (Hungarian)
Currently translated at 100.0% (477 of 477 strings)
Co-authored-by: Gera, Zoltan
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/hu/
Translation: Tusky/Tusky
---
app/src/main/res/values-hu/strings.xml | 128 ++++++++++++++-----------
1 file changed, 71 insertions(+), 57 deletions(-)
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index 17e4e788..18a4bb8a 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -9,26 +9,26 @@
Azonosítatlan engedélyezési hiba történt.
Engedély megtagadva.
Bejelentkezési token megszerzése sikertelen.
- Túl hosszú a tülkölés!
+ Túl hosszú a bejegyzés!
A fájlnak kisebbnek kell lennie, mint 8 MB.
A videofájloknak kisebbnek kell lenniük, mint 40 MB.
Ilyen típusú fájlt nem lehet feltölteni.
Fájl megnyitása sikertelen.
Média olvasási engedély szükséges.
Média tárolási engedély szükséges.
- Képek és videók egyszerre nem csatolhatók ugyanazon tülköléshez.
+ Képek és videók egyszerre nem csatolhatóak ugyanazon bejegyzéshez.
Feltöltés sikertelen.
- Nem sikerült elküldeni a tülköt.
+ Nem sikerült elküldeni a bejegyzést.
Kezdőlap
Értesítések
Helyi
Föderációs
Közvetlen üzenetek
Fülek
- Tülk
- Tülkök
+ Szál
+ Bejegyzések
Válaszokkal
- Rögzített
+ Kitűzött
Követett
Követő
Kedvencek
@@ -40,17 +40,17 @@
Licenszek
\@%s
%s megtolta
- Kényes tartalom
+ Érzékeny tartalom
Rejtett média
- Kattints a megnézéshez
+ Kattints a megtekintéshez
Mutass többet
Mutass kevesebbet
Kibontás
Összecsukás
Nincs itt semmi.
Üres tartalom. Húzd le a frissítéshez!
- %s megtolta a tülködet
- %s kedvencnek jelölte tülködet
+ %s megtolta a bejegyzésedet
+ %s kedvencnek jelölte a bejegyzésedet
%s bekövetett
\@%s jelentése
Egyéb megjegyzés?
@@ -100,7 +100,7 @@
Elutasítás
Keresés
Piszkozatok
- Tülkök láthatósága
+ Bejegyzés láthatósága
Tartalom figyelmeztetés
Emoji billentyűzet
Fül hozzáadása
@@ -115,8 +115,8 @@
Link másolása
Megnyitás mint %s
Megosztás mint …
- Tülk URL megosztása…
- Tülk megosztása…
+ Bejegyzés URL megosztása…
+ Bejegyzés megosztása…
Elküldve!
Felhasználó letiltása feloldva
Felhasználó némítása feloldva
@@ -132,7 +132,7 @@
Válasz…
Profilkép
Fejléc
- Mi az a szerver\?
+ Mi az a példány\?
Csatlakozás…
Bármely példány címét vagy domain nevét beírhatod ide, mint a mastodon.social, az icosahedron.website, a social.tchncs.de és mások!
\n
@@ -146,11 +146,11 @@
Letöltés
Visszavonod a követési kérelmet?
Követés megszüntetése?
- Törlöd ezt a tülköt?
- Nyilvános: Tülkölés nyilvános idővonalra
+ Törlöd ezt a bejegyzést\?
+ Nyilvános: Bejegyzés nyilvános idővonalra
Listázatlan: Nem jelenik meg a nyilvános idővonalon
- Csak követőknek: Tülkölés csak követőknek
- Közvetlen: Tülkölés csak a megemlített felhasználóknak
+ Csak követőknek: Bejegyzés csak követőknek
+ Közvetlen: Bejegyzés csak a megemlített felhasználóknak
Értesítések
Értesítések
Figyelmeztetések
@@ -160,8 +160,8 @@
Értesítsen, ha
megemlítettek
bekövettek
- tülkömet megtolták
- tülkömet kedvenccé tették
+ bejegyzésemet megtolták
+ bejegyzésemet kedvencnek jelölték
Megjelenés
Idővonalak
Sötét
@@ -181,13 +181,13 @@
HTTP proxy engedélyezése
HTTP proxy szerver
HTTP Proxy port
- Tülkök alapértelmezett láthatósága
+ Bejegyzések alapértelmezett láthatósága
Minden média kényesnek jelölése
A beállítások szinkronizálása nem sikerült
Nyilvános
Listázatlan
Csak követőknek
- Tülkölés szöveg mérete
+ Bejegyzés szövegének mérete
Legkisebb
Kicsi
Közepes
@@ -198,9 +198,9 @@
Új követők
Értesítések új követőkről
Megtolások
- Értesítések tülkjeid megtolása esetén
+ Értesítések bejegyzéseid megtolása esetén
Kedvencek
- Értesítések mikor tülkjeidet kedvencnek jelölik
+ Értesítések amikor a bejegyzéseidet kedvencnek jelölik
%s megemlített téged
%1$s, %2$s, %3$s és még %4$d
%1$s, %2$s meg %3$s
@@ -224,10 +224,10 @@
Hibajelentés & új funkciók igénylése:
\n https://github.com/tuskyapp/Tusky/issues
Tusky profilja
- Tülk tartalmának megosztása
- Tülk linkjének megosztása
+ Bejegyzés tartalmának megosztása
+ Bejegyzés hivatkozásának megosztása
Képek
- Videók
+ Videó
Követés kérelmezve
Követ téged
@@ -241,19 +241,19 @@
Törlés
Fiók zárolása
Elmented a piszkozatot\?
- Tülk elküldése…
- A tülk elküldése nem sikerült
- Tülkök elküldése
+ Bejegyzés küldése…
+ A bejegyzés elküldése sikertelen
+ Bejegyzések elküldése
Küldés megszakítva
- A tülk másolatát elmentettük a piszkozataid közé
+ A bejegyzés másolatát elmentettük a piszkozataid közé
Szerkesztés
- A %s szervernek nincsenek egyedi emoji-jai
+ A %s példánynak nincsenek egyedi emoji-jai
Vágólapra másolva
Emoji stílus
Rendszer alapértelmezés
Először le kell töltened ezeket az emoji készleteket
Keresés…
- Tülk megnyitása
+ Bejegyzés megnyitása
Az app újraindítása szükséges
A beállítások érvényesítéséhez újra kell indítani a Tuskyt
Később
@@ -280,8 +280,7 @@
- elérted a fülek maximális számát (%1$d)
- elérted a fülek maximális számát (%1$d)
- Nincs leírás
-
+ Nincs leírás
Nyilvános
Követők
Kedvenc eltávolítása
@@ -290,7 +289,7 @@
Média letöltése
Média letöltése
Média megosztása következővel…
- Törlöd és újraírod ezt a tülköt\?
+ Törlöd és újraírod ezt a bejegyzést\?
befejeződött egy szavazás
Szűrők
Rendszer téma használata
@@ -342,7 +341,7 @@
Általad követettek keresése
Fiók hozzáadása a listához
Fiók eltávolítása a listából
- Tülkölés %1$s fiókkal
+ Bejegyzés %1$s fiókkal
Cím beállítása nem sikerült
- Leírás látássérülteknek
@@ -350,7 +349,7 @@
Cím beállítása
Minden követődet külön engedélyezned kell
- Minden tülk kibontása/összecsukása
+ Összes bejegyzés kibontása/összecsukása
A Google jelenlegi emodzsi készlete
Megtolás az eredeti közönségnek
Megtolás visszavonása
@@ -368,7 +367,7 @@
%1$s
%1$s, %2$s és még %3$d
Média: %s
- Tartalom figyelmeztetés: %s
+ Tartalomfigyelmeztetés: %s
Megtolt
Kedvelt
Listázatlan
@@ -379,7 +378,7 @@
Törlés
Szűrés
Alkalmaz
- Tülk szerkesztése
+ Bejegyzés létrehozása
Szerkesztés
Biztos, hogy minden értesítésedet véglegesen törlöd\?
Műveletek a(z) %s képpel
@@ -416,11 +415,11 @@
Egyéb megjegyzések
Továbbítás neki %s
Nem sikerült a bejelentés
- Nem sikerült a tülkök letöltése
+ Sikertelen a bejegyzések letöltése
A bejelentést a szervered moderátorának küldjük el. Alább megadhatsz egy magyarázatot arra, hogy miért jelented be ezt a fiókot:
A fiók egy másik szerverről származik. Küldjünk oda is egy anonimizált másolatot a bejelentésről\?
Értesítések szűrőjének mutatása
- Tartalom-figyelmeztetéssel ellátott tülkök kifejtése mindig
+ Tartalomfigyelmeztetéssel ellátott bejegyzések kinyitása mindig
Fiókok
Sikertelen keresés
Szavazás hozzáadása
@@ -436,12 +435,12 @@
Több lehetőség
Válasz %d
Szerkesztés
- Időzített tülkök
+ Időzített bejegyzések
Szerkesztés
- Időzített tülkök
- Tülk Időzítése
+ Időzített bejegyzések
+ Bejegyzés Időzítése
Visszaállítás
- Nem találjuk ezt a tülköt %s
+ Nem találjuk ezt a bejegyzést %s
Könyvjelzők
Könyvjelzőzés
Könyvjelzők
@@ -451,7 +450,7 @@
Lista
A hangfájloknak kisebbnek kell lenniük, mint 40 MB.
Nincs egy piszkozatod sem.
- Nincs egy ütemezett tülköd sem.
+ Nincs egy ütemezett bejegyzésed sem.
A Mastodonban a legrövidebb ütemezhető időintervallum 5 perc.
Követési kérelmek
Jóváhagyó ablak mutatása megtolás előtt
@@ -484,34 +483,34 @@
Saját, mások számára nem látható megjegyzés erről a fiókról
Nincsenek közlemények.
Közlemények
- A tülköt, melyre válaszul piszkozatot készítettél törölték
+ A bejegyzést, melyre válaszul piszkozatot készítettél törölték
Piszkozat törölve
Nem sikerült a Válasz információit betölteni
- Ez a tülk nem küldődött el!
+ Ezt a bejegyzést nem tudtuk elküldeni!
Tényleg le akarod törölni a %s listát\?
- Nem tölthetsz fel %1$d médiacsatolmányból többet.
- Nem tölthetsz fel %1$d médiacsatolmányból többet.
Profilok mérőszámainak elrejtése
- Tülkök mérőszámainak elrejtése
+ Bejegyzések mérőszámainak elrejtése
Idővonali értesítések korlátozása
Értesítések Áttekintése
- Pár információ, ami befolyásolhatja a mentális egészségedet rejtve marad. Ilyenek pl.:
+ Pár információ, ami befolyásolhatja a mentális jóllétedet rejtve marad. Ilyenek pl.:
\n
\n - Kedvenc/Megtolás/Bekövetés értesítései
-\n - Kedvenc/Megtolás számlálók a tülkökön
-\n - Követő/Tülk statisztikák a profilokon
+\n - Kedvenc/Megtolás számlálók a bejegyzéseken
+\n - Követő/Bejegyzés statisztikák a profilokon
\n
\nA Push-értesítéseket ez nem befolyásolja, de kézzel átállíthatod az értesítési beállításaidat.
Végtelen
Időtartam
Csatolmányok
Audio
- Értesítések általam követett személy új tülkjeiről
- Új tülkök
- valaki, akit követek újat tülkölt
- %s épp tülkölt
+ Értesítések általam követett személy új bejegyzéseiről
+ Új bejegyzések
+ valaki, akit követek új bejegyzést tett közzé
+ %s épp bejegyzést írt
Jóllét
Egyedi emojik animálása
Leiratkozás
@@ -521,4 +520,19 @@
Beszélgetés törlése
Könyvjelző törlése
Jóváhagyás mutatása kedvencnek jelölés előtt
+ %s szerkesztette a bejegyzését
+ szerkesztették a bejegyzést, mellyel dolgod volt
+ %s regisztrált
+ valaki regisztrált
+ Regisztrációk
+ Értesítések új felhasználókról
+ 14 nap
+ 30 nap
+ 60 nap
+ 90 nap
+ 180 nap
+ 365 nap
+ Bejegyzések szerkesztése
+ Értesítések olyan bejegyzések szerkesztéséről, melyekkel már dolgod volt
+ Bejegyzés Létrehozása
\ No newline at end of file
From 384236f832d3dd0303d57240195ffcfce13858b1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=E1=BB=93=20Nh=E1=BA=A5t=20Duy?=
Date: Thu, 28 Apr 2022 16:55:14 +0000
Subject: [PATCH 037/104] Translated using Weblate (Vietnamese)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Currently translated at 100.0% (477 of 477 strings)
Co-authored-by: Hồ Nhất Duy
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/
Translation: Tusky/Tusky
---
app/src/main/res/values-vi/strings.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
index 16704821..0746ac6b 100644
--- a/app/src/main/res/values-vi/strings.xml
+++ b/app/src/main/res/values-vi/strings.xml
@@ -413,7 +413,7 @@
Tusky có sử dụng mã nguồn từ những dự án mã nguồn mở sau:
Hủy đăng lại
Đăng lại công khai
- %1$s đã dời sang:
+ %1$s đã chuyển sang:
Tài khoản Bot
Tải về thất bại
Emoji của Google
From b94d723663e600ae8d0a74f7eba1d1a314291a2c Mon Sep 17 00:00:00 2001
From: GunChleoc
Date: Thu, 28 Apr 2022 16:55:14 +0000
Subject: [PATCH 038/104] Translated using Weblate (Gaelic)
Currently translated at 100.0% (477 of 477 strings)
Translated using Weblate (Gaelic)
Currently translated at 100.0% (477 of 477 strings)
Translated using Weblate (Gaelic)
Currently translated at 100.0% (477 of 477 strings)
Co-authored-by: GunChleoc
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/gd/
Translation: Tusky/Tusky
---
app/src/main/res/values-gd/strings.xml | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/app/src/main/res/values-gd/strings.xml b/app/src/main/res/values-gd/strings.xml
index 19ae1240..cc46053b 100644
--- a/app/src/main/res/values-gd/strings.xml
+++ b/app/src/main/res/values-gd/strings.xml
@@ -249,16 +249,16 @@
A bheil thu airson a shàbhaladh ’na dhreachd\?
Feumaidh tu gabhail ri luchd-leantainn ùr a làimh
Glais an cunntas
- Suidhidh am fo-thiotal
+ Suidhich am fo-thiotal
- Mìnich e dhan fheadhainn air a bheil cion-lèirsinn
-\n(%d caractar(an) air a char as fhaide)
+\n(%d charactar air a char as fhaide)
- Mìnich e dhan fheadhainn air a bheil cion-lèirsinn
-\n(%d caractar(an) air a char as fhaide)
+\n(%d charactar air a char as fhaide)
- Mìnich e dhan fheadhainn air a bheil cion-lèirsinn
-\n(%d caractar(an) air a char as fhaide)
+\n(%d caractaran air a char as fhaide)
- Mìnich e dhan fheadhainn air a bheil cion-lèirsinn
-\n(%d caractar(an) air a char as fhaide)
+\n(%d caractar air a char as fhaide)
Cha deach leinn am fo-thiotal a shuidheachadh
A’ postadh leis a’ chunntas %1$s
@@ -552,4 +552,7 @@
Brathan mu cleachdaichean ùra
chlàraich cuideigin
Dheasaich %s am post aca
+ Deasachadh puist
+ Brathan nuair a thèid postaichean a rinn thu conaltradh leotha a dheasachadh
+ chaidh post a rinn mi conaltradh leis a deasachadh
\ No newline at end of file
From f7599db777f9ea1de6a514a4b77de02d287f841c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sveinn=20=C3=AD=20Felli?=
Date: Thu, 28 Apr 2022 16:55:14 +0000
Subject: [PATCH 039/104] Translated using Weblate (Icelandic)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Currently translated at 100.0% (477 of 477 strings)
Co-authored-by: Sveinn í Felli
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/is/
Translation: Tusky/Tusky
---
app/src/main/res/values-is/strings.xml | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/app/src/main/res/values-is/strings.xml b/app/src/main/res/values-is/strings.xml
index 0dc453c5..46948bf0 100644
--- a/app/src/main/res/values-is/strings.xml
+++ b/app/src/main/res/values-is/strings.xml
@@ -519,4 +519,12 @@
365 dagar
14 dagar
Semja færslu
+ %s skráði sig
+ einhver skráði sig
+ %s breytti færslunni sinni
+ færsla sem ég hef átt við er breytt
+ Nýskráningar
+ Tilkynningar um nýja notendur
+ Breytingar á færslum
+ Tilkynningar þegar færslum sem þú hefur átt við er breytt
\ No newline at end of file
From 516f75b643bcb3b804b13ea072ec76235c08a5b4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marek=20=C4=BDach?=
Date: Thu, 28 Apr 2022 16:55:14 +0000
Subject: [PATCH 040/104] Translated using Weblate (Slovak)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Currently translated at 26.4% (126 of 477 strings)
Co-authored-by: Marek Ľach
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/sk/
Translation: Tusky/Tusky
---
app/src/main/res/values-sk/strings.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
index 15d05945..e06555c7 100644
--- a/app/src/main/res/values-sk/strings.xml
+++ b/app/src/main/res/values-sk/strings.xml
@@ -77,7 +77,7 @@
Autentizácia servru zlyhala.
Nepodarilo sa nájsť použiteľný webový prehliadač.
Vyskytla sa neidentifikovaná chyba autorizácie.
- Toot je príliš dlhý!
+ Príspevok je príliš dlhý!
Tento typ súboru nemôže byť nahraný.
Chyba pri odosielaní tootu.
Toot
From 895130bd4b620100f18187eec902f2130dbbf84b Mon Sep 17 00:00:00 2001
From: Roj
Date: Thu, 28 Apr 2022 16:55:15 +0000
Subject: [PATCH 041/104] Translated using Weblate (Sorani)
Currently translated at 88.6% (423 of 477 strings)
Co-authored-by: Roj
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/ckb/
Translation: Tusky/Tusky
---
app/src/main/res/values-ckb/strings.xml | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/app/src/main/res/values-ckb/strings.xml b/app/src/main/res/values-ckb/strings.xml
index 8dd86758..ad91f902 100644
--- a/app/src/main/res/values-ckb/strings.xml
+++ b/app/src/main/res/values-ckb/strings.xml
@@ -123,11 +123,11 @@
نیشانەکان
دڵخوازەکان
شوێنکەوتوان
- بەدوادا
+ شوێنکەوتنەکان
چەسپا
لەگەڵ وەڵامەکان
بابەتەکان
- توت
+ زنجیرە
سەرخشتەکان
نامە ڕاستەوخۆکان
گشتی
@@ -140,19 +140,19 @@
مۆڵەت بۆ پاشکەوتکردنی میدیا پێویستە.
مۆڵەت بۆ خوێندنەوەی میدیا پێویستە.
ئەم فایلە ناتوانرێت بکرێتەوە.
- ناتوانرێت ئەو جۆرە فایلە باربکرێت.
- فایلەکانی دەنگ دەبێت کەمتر بێت لە ٤٠MB.
- پێویستە فایلەکانی ڤیدیۆ کەمتر لە 40 مێگابایت بن.
- فایلەکە دەبێت کەمتر بێت لە 8 مێگابایت.
- ڕەستە زۆر درێژە!
+ ناتوانیت لەم جۆرە فایلانە بەرز بکەیتەوە.
+ دەبێت فایلە دەنگییەکان لە 40 مێگابایت گەورەتر نەبن.
+ دەبێت ڤیدیۆکان لە 40 مێگابایت گەورەتر نەبن.
+ فایلەکە دەبێت لە 8 مێگابایت بچووکتر بێت.
+ ئەم نووسینە زۆر درێژە!
سەرکەوتوو نەبوو لە بەدەستهێنانی نیشانەی چوونەژوورەوە.
ڕێپێدان ڕەتکرایەوە.
هەڵەیەک بۆ مۆڵەتدانی نەناسراو ڕووی دا.
نەیتوانی وێبگەڕبدۆزێتەوە بۆ بەکارهێنان.
سەرکەوتوو نەبوو، ڕاستکردنەوە لەگەڵ ئەم نمونەیە.
- دۆمەینی نادروست تێنووسکرا
- ئەمە ناتوانێت بەتاڵ بێت.
- هەڵەیەک لە تۆڕ ڕوویدا! تکایە پەیوەندیت بپشکنە و دوبارە هەوڵ بدە!
+ دۆمەینێکی نادروستت نووسیوە
+ ناکرێت ئەمە بەتاڵ بێت.
+ هەڵەیەک لە پەیوەندییەکەدا ڕوویدا. تکایە دڵنیا ببەوە لە بەردەستبوونی هێڵی ئینتەرنێت.
هەڵەیەک ڕوویدا.
تایبەتمەندی بابەت گریمانەیی
دەرگای پرۆکسی HTTP
From 0fd51dbcec2730546da12326a3b9745916607467 Mon Sep 17 00:00:00 2001
From: "Gera, Zoltan"
Date: Sat, 23 Apr 2022 11:40:29 +0000
Subject: [PATCH 042/104] Translated using Weblate (Hungarian)
Currently translated at 100.0% (16 of 16 strings)
Translation: Tusky/Tusky description
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky-app/hu/
---
fastlane/metadata/android/hu/changelogs/89.txt | 7 +++++++
1 file changed, 7 insertions(+)
create mode 100644 fastlane/metadata/android/hu/changelogs/89.txt
diff --git a/fastlane/metadata/android/hu/changelogs/89.txt b/fastlane/metadata/android/hu/changelogs/89.txt
new file mode 100644
index 00000000..e7803769
--- /dev/null
+++ b/fastlane/metadata/android/hu/changelogs/89.txt
@@ -0,0 +1,7 @@
+Tusky v17.0
+
+- "Megnyit, mint..." már a fiókok profiljainak menüjében is elérhető, amikor több fiókot használsz
+- A bejelentkezés az appon belül már WebView-ban működik
+- Android 12 támogatása
+- új Mastodon szerverkonfigurációs API támogatása
+- sok más kisebb javítás és fejlesztés
From 671d2c6a45cc4b286f5382a1cb22e2c92982a851 Mon Sep 17 00:00:00 2001
From: Konrad Pozniak
Date: Thu, 28 Apr 2022 20:37:31 +0200
Subject: [PATCH 043/104] Check if media processing finished before sending
status (#2458)
* make MastodonApi.createStatus suspending
* check if media processing has finished before sending status
* add backoff for retrying processed media check
---
.../components/compose/ComposeViewModel.kt | 11 +-
.../tusky/network/MastodonApi.kt | 9 +-
.../receiver/SendStatusBroadcastReceiver.kt | 3 +-
.../tusky/service/SendStatusService.kt | 163 ++++++++++--------
4 files changed, 103 insertions(+), 83 deletions(-)
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt
index fce3d0bd..81500ee4 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt
@@ -251,13 +251,15 @@ class ComposeViewModel @Inject constructor(
val sendObservable = media
.filter { items -> items.all { it.uploadPercent == -1 } }
.map {
- val mediaIds = ArrayList()
- val mediaUris = ArrayList()
- val mediaDescriptions = ArrayList()
+ val mediaIds: MutableList = mutableListOf()
+ val mediaUris: MutableList = mutableListOf()
+ val mediaDescriptions: MutableList = mutableListOf()
+ val mediaProcessed: MutableList = mutableListOf()
for (item in media.value!!) {
mediaIds.add(item.id!!)
mediaUris.add(item.uri)
mediaDescriptions.add(item.description ?: "")
+ mediaProcessed.add(false)
}
val tootToSend = StatusToSend(
@@ -276,7 +278,8 @@ class ComposeViewModel @Inject constructor(
accountId = accountManager.activeAccount!!.id,
draftId = draftId,
idempotencyKey = randomAlphanumericString(16),
- retries = 0
+ retries = 0,
+ mediaProcessed = mediaProcessed
)
serviceClient.sendToot(tootToSend)
diff --git a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt
index 02af5caa..25fe03d4 100644
--- a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt
@@ -156,13 +156,18 @@ interface MastodonApi {
@Field("description") description: String
): Result
+ @GET("api/v1/media/{mediaId}")
+ suspend fun getMedia(
+ @Path("mediaId") mediaId: String
+ ): Response
+
@POST("api/v1/statuses")
- fun createStatus(
+ suspend fun createStatus(
@Header("Authorization") auth: String,
@Header(DOMAIN_HEADER) domain: String,
@Header("Idempotency-Key") idempotencyKey: String,
@Body status: NewStatus
- ): Call
+ ): Result
@GET("api/v1/statuses/{id}")
fun status(
diff --git a/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt b/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt
index c6df5df6..a0eac833 100644
--- a/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt
@@ -100,7 +100,8 @@ class SendStatusBroadcastReceiver : BroadcastReceiver() {
accountId = account.id,
draftId = -1,
idempotencyKey = randomAlphanumericString(16),
- retries = 0
+ retries = 0,
+ mediaProcessed = mutableListOf()
)
)
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 a2709f97..e50f4f4f 100644
--- a/app/src/main/java/com/keylesspalace/tusky/service/SendStatusService.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/service/SendStatusService.kt
@@ -11,6 +11,7 @@ import android.content.Intent
import android.os.Build
import android.os.IBinder
import android.os.Parcelable
+import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.app.ServiceCompat
import androidx.core.content.ContextCompat
@@ -29,13 +30,12 @@ import com.keylesspalace.tusky.network.MastodonApi
import dagger.android.AndroidInjection
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize
-import retrofit2.Call
-import retrofit2.Callback
-import retrofit2.Response
+import retrofit2.HttpException
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@@ -55,7 +55,7 @@ class SendStatusService : Service(), Injectable {
private val serviceScope = CoroutineScope(Dispatchers.Main + supervisorJob)
private val statusesToSend = ConcurrentHashMap()
- private val sendCalls = ConcurrentHashMap>()
+ private val sendJobs = ConcurrentHashMap()
private val notificationManager by lazy { getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager }
@@ -64,12 +64,9 @@ class SendStatusService : Service(), Injectable {
super.onCreate()
}
- override fun onBind(intent: Intent): IBinder? {
- return null
- }
+ override fun onBind(intent: Intent): IBinder? = null
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
-
if (intent.hasExtra(KEY_STATUS)) {
val statusToSend = intent.getParcelableExtra(KEY_STATUS)
?: throw IllegalStateException("SendStatusService started without $KEY_STATUS extra")
@@ -129,82 +126,94 @@ class SendStatusService : Service(), Injectable {
statusToSend.retries++
- val newStatus = NewStatus(
- statusToSend.text,
- statusToSend.warningText,
- statusToSend.inReplyToId,
- statusToSend.visibility,
- statusToSend.sensitive,
- statusToSend.mediaIds,
- statusToSend.scheduledAt,
- statusToSend.poll
- )
+ sendJobs[statusId] = serviceScope.launch {
+ try {
+ var mediaCheckRetries = 0
+ while (statusToSend.mediaProcessed.any { !it }) {
+ delay(1000L * mediaCheckRetries)
+ statusToSend.mediaProcessed.forEachIndexed { index, processed ->
+ if (!processed) {
+ // Mastodon returns 206 if the media was not yet processed
+ statusToSend.mediaProcessed[index] = mastodonApi.getMedia(statusToSend.mediaIds[index]).code() == 200
+ }
+ }
+ mediaCheckRetries ++
+ }
+ } catch (e: Exception) {
+ Log.w(TAG, "failed getting media status", e)
+ retrySending(statusId)
+ return@launch
+ }
- val sendCall = mastodonApi.createStatus(
- "Bearer " + account.accessToken,
- account.domain,
- statusToSend.idempotencyKey,
- newStatus
- )
+ val newStatus = NewStatus(
+ statusToSend.text,
+ statusToSend.warningText,
+ statusToSend.inReplyToId,
+ statusToSend.visibility,
+ statusToSend.sensitive,
+ statusToSend.mediaIds,
+ statusToSend.scheduledAt,
+ statusToSend.poll
+ )
- sendCalls[statusId] = sendCall
+ mastodonApi.createStatus(
+ "Bearer " + account.accessToken,
+ account.domain,
+ statusToSend.idempotencyKey,
+ newStatus
+ ).fold({ sentStatus ->
+ statusesToSend.remove(statusId)
+ // If the status was loaded from a draft, delete the draft and associated media files.
+ if (statusToSend.draftId != 0) {
+ draftHelper.deleteDraftAndAttachments(statusToSend.draftId)
+ }
- val callback = object : Callback {
- override fun onResponse(call: Call, response: Response) {
- serviceScope.launch {
+ val scheduled = !statusToSend.scheduledAt.isNullOrEmpty()
- val scheduled = !statusToSend.scheduledAt.isNullOrEmpty()
+ if (scheduled) {
+ eventHub.dispatch(StatusScheduledEvent(sentStatus))
+ } else {
+ eventHub.dispatch(StatusComposedEvent(sentStatus))
+ }
+
+ notificationManager.cancel(statusId)
+ }, { throwable ->
+ Log.w(TAG, "failed sending status", throwable)
+ if (throwable is HttpException) {
+ // the server refused to accept the status, save status & show error message
statusesToSend.remove(statusId)
+ saveStatusToDrafts(statusToSend)
- if (response.isSuccessful) {
- // If the status was loaded from a draft, delete the draft and associated media files.
- if (statusToSend.draftId != 0) {
- draftHelper.deleteDraftAndAttachments(statusToSend.draftId)
- }
-
- if (scheduled) {
- response.body()?.let(::StatusScheduledEvent)?.let(eventHub::dispatch)
- } else {
- response.body()?.let(::StatusComposedEvent)?.let(eventHub::dispatch)
- }
-
- notificationManager.cancel(statusId)
- } else {
- // the server refused to accept the status, save status & show error message
- saveStatusToDrafts(statusToSend)
-
- val builder = NotificationCompat.Builder(this@SendStatusService, CHANNEL_ID)
- .setSmallIcon(R.drawable.ic_notify)
- .setContentTitle(getString(R.string.send_post_notification_error_title))
- .setContentText(getString(R.string.send_post_notification_saved_content))
- .setColor(
- ContextCompat.getColor(
- this@SendStatusService,
- R.color.notification_color
- )
+ val builder = NotificationCompat.Builder(this@SendStatusService, CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_notify)
+ .setContentTitle(getString(R.string.send_post_notification_error_title))
+ .setContentText(getString(R.string.send_post_notification_saved_content))
+ .setColor(
+ ContextCompat.getColor(
+ this@SendStatusService,
+ R.color.notification_color
)
+ )
- notificationManager.cancel(statusId)
- notificationManager.notify(errorNotificationId--, builder.build())
- }
- stopSelfWhenDone()
+ notificationManager.cancel(statusId)
+ notificationManager.notify(errorNotificationId--, builder.build())
+ } else {
+ // a network problem occurred, let's retry sending the status
+ retrySending(statusId)
}
- }
-
- override fun onFailure(call: Call, t: Throwable) {
- serviceScope.launch {
- var backoff = TimeUnit.SECONDS.toMillis(statusToSend.retries.toLong())
- if (backoff > MAX_RETRY_INTERVAL) {
- backoff = MAX_RETRY_INTERVAL
- }
-
- delay(backoff)
- sendStatus(statusId)
- }
- }
+ })
+ stopSelfWhenDone()
}
+ }
- sendCall.enqueue(callback)
+ private suspend fun retrySending(statusId: Int) {
+ // when statusToSend == null, sending has been canceled
+ val statusToSend = statusesToSend[statusId] ?: return
+
+ val backoff = TimeUnit.SECONDS.toMillis(statusToSend.retries.toLong()).coerceAtMost(MAX_RETRY_INTERVAL)
+
+ delay(backoff)
+ sendStatus(statusId)
}
private fun stopSelfWhenDone() {
@@ -218,8 +227,8 @@ class SendStatusService : Service(), Injectable {
private fun cancelSending(statusId: Int) = serviceScope.launch {
val statusToCancel = statusesToSend.remove(statusId)
if (statusToCancel != null) {
- val sendCall = sendCalls.remove(statusId)
- sendCall?.cancel()
+ val sendJob = sendJobs.remove(statusId)
+ sendJob?.cancel()
saveStatusToDrafts(statusToCancel)
@@ -263,6 +272,7 @@ class SendStatusService : Service(), Injectable {
}
companion object {
+ private const val TAG = "SendStatusService"
private const val KEY_STATUS = "status"
private const val KEY_CANCEL = "cancel_id"
@@ -319,5 +329,6 @@ data class StatusToSend(
val accountId: Long,
val draftId: Int,
val idempotencyKey: String,
- var retries: Int
+ var retries: Int,
+ val mediaProcessed: MutableList
) : Parcelable
From 28ac190212ad989b1d53372957fe741299b9068a Mon Sep 17 00:00:00 2001
From: Konrad Pozniak
Date: Thu, 28 Apr 2022 20:37:46 +0200
Subject: [PATCH 044/104] fix SearchActivity transition animations (#2464)
---
.../tusky/components/search/SearchActivity.kt | 4 ++++
.../tusky/components/search/fragments/SearchFragment.kt | 8 ++++++--
.../components/search/fragments/SearchStatusesFragment.kt | 2 +-
.../tusky/interfaces/StatusActionListener.java | 2 +-
4 files changed, 12 insertions(+), 4 deletions(-)
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/SearchActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/SearchActivity.kt
index d833b432..8ca7248c 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/search/SearchActivity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/search/SearchActivity.kt
@@ -84,6 +84,10 @@ class SearchActivity : BottomSheetActivity(), HasAndroidInjector {
return true
}
+ override fun finish() {
+ super.finishWithoutSlideOutAnimation()
+ }
+
private fun getPageTitle(position: Int): CharSequence {
return when (position) {
0 -> getString(R.string.title_posts)
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchFragment.kt
index aeb989b0..a7707491 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchFragment.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchFragment.kt
@@ -111,9 +111,13 @@ abstract class SearchFragment :
}
}
- override fun onViewAccount(id: String) = startActivity(AccountActivity.getIntent(requireContext(), id))
+ override fun onViewAccount(id: String) {
+ bottomSheetActivity?.startActivityWithSlideInAnimation(AccountActivity.getIntent(requireContext(), id))
+ }
- override fun onViewTag(tag: String) = startActivity(StatusListActivity.newHashtagIntent(requireContext(), tag))
+ override fun onViewTag(tag: String) {
+ bottomSheetActivity?.startActivityWithSlideInAnimation(StatusListActivity.newHashtagIntent(requireContext(), tag))
+ }
override fun onViewUrl(url: String) {
bottomSheetActivity?.viewUrl(url)
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt
index 11d90da4..2e7849c1 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt
@@ -219,7 +219,7 @@ class SearchStatusesFragment : SearchFragment(), Status
replyingStatusContent = status.content.toString()
)
)
- startActivity(intent)
+ bottomSheetActivity?.startActivityWithSlideInAnimation(intent)
}
private fun more(status: Status, view: View, position: Int) {
diff --git a/app/src/main/java/com/keylesspalace/tusky/interfaces/StatusActionListener.java b/app/src/main/java/com/keylesspalace/tusky/interfaces/StatusActionListener.java
index 116e582c..ec37680c 100644
--- a/app/src/main/java/com/keylesspalace/tusky/interfaces/StatusActionListener.java
+++ b/app/src/main/java/com/keylesspalace/tusky/interfaces/StatusActionListener.java
@@ -38,7 +38,7 @@ public interface StatusActionListener extends LinkListener {
void onOpenReblog(int position);
void onExpandedChange(boolean expanded, int position);
void onContentHiddenChange(boolean isShowing, int position);
- void onLoadMore(int position);
+ void onLoadMore(int position);
/**
* Called when the status {@link android.widget.ToggleButton} responsible for collapsing long
From e5b58770ced893cb24733efe42425d4fb7bcdda8 Mon Sep 17 00:00:00 2001
From: Konrad Pozniak
Date: Thu, 28 Apr 2022 20:38:51 +0200
Subject: [PATCH 045/104] improve LoginWebViewActivityUX (#2465)
---
.../components/login/LoginWebViewActivity.kt | 18 +++++++++++++++---
app/src/main/res/layout/login_webview.xml | 6 ++++++
app/src/main/res/values/strings.xml | 1 +
3 files changed, 22 insertions(+), 3 deletions(-)
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/login/LoginWebViewActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/login/LoginWebViewActivity.kt
index 827b5620..58f745e7 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/login/LoginWebViewActivity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/login/LoginWebViewActivity.kt
@@ -19,8 +19,10 @@ import androidx.activity.result.contract.ActivityResultContract
import androidx.core.net.toUri
import com.keylesspalace.tusky.BaseActivity
import com.keylesspalace.tusky.BuildConfig
+import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.LoginWebviewBinding
import com.keylesspalace.tusky.di.Injectable
+import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.viewBinding
import kotlinx.parcelize.Parcelize
@@ -87,7 +89,9 @@ class LoginWebViewActivity : BaseActivity(), Injectable {
setSupportActionBar(binding.loginToolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
- supportActionBar?.setDisplayShowTitleEnabled(false)
+ supportActionBar?.setDisplayShowTitleEnabled(true)
+
+ setTitle(R.string.title_login)
val webView = binding.loginWebView
webView.settings.allowContentAccess = false
@@ -103,13 +107,17 @@ class LoginWebViewActivity : BaseActivity(), Injectable {
val oauthUrl = data.oauthRedirectUrl
webView.webViewClient = object : WebViewClient() {
+ override fun onPageFinished(view: WebView?, url: String?) {
+ binding.loginProgress.hide()
+ }
+
override fun onReceivedError(
view: WebView,
request: WebResourceRequest,
error: WebResourceError
) {
Log.d("LoginWeb", "Failed to load ${data.url}: $error")
- finish()
+ finishWithoutSlideOutAnimation()
}
override fun shouldOverrideUrlLoading(
@@ -165,10 +173,14 @@ class LoginWebViewActivity : BaseActivity(), Injectable {
super.onDestroy()
}
+ override fun finish() {
+ super.finishWithoutSlideOutAnimation()
+ }
+
override fun requiresLogin() = false
private fun sendResult(result: LoginResult) {
setResult(Activity.RESULT_OK, OauthLogin.makeResultIntent(result))
- finish()
+ finishWithoutSlideOutAnimation()
}
}
diff --git a/app/src/main/res/layout/login_webview.xml b/app/src/main/res/layout/login_webview.xml
index 22d7f3bd..67d47d6f 100644
--- a/app/src/main/res/layout/login_webview.xml
+++ b/app/src/main/res/layout/login_webview.xml
@@ -16,6 +16,12 @@
+
+
The upload failed.
Error sending post.
+ Login
Home
Notifications
Local
From e9b75119b3f02382327e66c4fb66bac3bd17409d Mon Sep 17 00:00:00 2001
From: Konrad Pozniak
Date: Thu, 28 Apr 2022 20:39:06 +0200
Subject: [PATCH 046/104] improve bot badge (#2466)
* improve bot badge
* change badge corner radius
---
.../tusky/adapter/AccountViewHolder.java | 3 +--
.../tusky/adapter/NotificationsAdapter.java | 3 +--
.../tusky/adapter/StatusBaseViewHolder.java | 7 ++++---
app/src/main/res/drawable/bot_badge.xml | 13 +++++++++++++
app/src/main/res/values-night/theme_colors.xml | 3 +++
app/src/main/res/values/theme_colors.xml | 3 +++
6 files changed, 25 insertions(+), 7 deletions(-)
create mode 100644 app/src/main/res/drawable/bot_badge.xml
diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountViewHolder.java
index f4824389..6672fff3 100644
--- a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountViewHolder.java
+++ b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountViewHolder.java
@@ -45,8 +45,7 @@ public class AccountViewHolder extends RecyclerView.ViewHolder {
ImageLoadingHelper.loadAvatar(account.getAvatar(), avatar, avatarRadius, animateAvatar);
if (showBotOverlay && account.getBot()) {
avatarInset.setVisibility(View.VISIBLE);
- avatarInset.setImageResource(R.drawable.ic_bot_24dp);
- avatarInset.setBackgroundColor(0x50ffffff);
+ avatarInset.setImageResource(R.drawable.bot_badge);
} else {
avatarInset.setVisibility(View.GONE);
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
index f8885f36..f681e64f 100644
--- a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
+++ b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
@@ -575,9 +575,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
if (statusDisplayOptions.showBotOverlay() && isBot) {
notificationAvatar.setVisibility(View.VISIBLE);
- notificationAvatar.setBackgroundColor(0x50ffffff);
Glide.with(notificationAvatar)
- .load(R.drawable.ic_bot_24dp)
+ .load(ContextCompat.getDrawable(notificationAvatar.getContext(), R.drawable.bot_badge))
.into(notificationAvatar);
} else {
diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java
index c7427450..2a5b3f2c 100644
--- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java
+++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java
@@ -21,6 +21,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.core.content.ContextCompat;
import androidx.core.text.HtmlCompat;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
@@ -28,6 +29,7 @@ import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestBuilder;
+import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners;
import com.google.android.material.button.MaterialButton;
@@ -287,11 +289,10 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
if (statusDisplayOptions.showBotOverlay() && isBot) {
avatarInset.setVisibility(View.VISIBLE);
- avatarInset.setBackgroundColor(0x50ffffff);
Glide.with(avatarInset)
- .load(R.drawable.ic_bot_24dp)
+ // passing the drawable id directly into .load() ignores night mode https://github.com/bumptech/glide/issues/4692
+ .load(ContextCompat.getDrawable(avatarInset.getContext(), R.drawable.bot_badge))
.into(avatarInset);
-
} else {
avatarInset.setVisibility(View.GONE);
}
diff --git a/app/src/main/res/drawable/bot_badge.xml b/app/src/main/res/drawable/bot_badge.xml
new file mode 100644
index 00000000..6f857df5
--- /dev/null
+++ b/app/src/main/res/drawable/bot_badge.xml
@@ -0,0 +1,13 @@
+
+
+
+
diff --git a/app/src/main/res/values-night/theme_colors.xml b/app/src/main/res/values-night/theme_colors.xml
index e45c45fb..21e56190 100644
--- a/app/src/main/res/values-night/theme_colors.xml
+++ b/app/src/main/res/values-night/theme_colors.xml
@@ -24,4 +24,7 @@
false
+ @color/white
+ @color/tusky_grey_10
+
\ No newline at end of file
diff --git a/app/src/main/res/values/theme_colors.xml b/app/src/main/res/values/theme_colors.xml
index bf5b79fc..657363d8 100644
--- a/app/src/main/res/values/theme_colors.xml
+++ b/app/src/main/res/values/theme_colors.xml
@@ -24,4 +24,7 @@
true
+ @color/tusky_grey_20
+ @color/white
+
\ No newline at end of file
From 9a8dfaa744ee0344a75aa2cfa381fa4c2c503206 Mon Sep 17 00:00:00 2001
From: Guntbert Reiter
Date: Fri, 29 Apr 2022 18:35:25 +0200
Subject: [PATCH 047/104] Remove code to check the server version (#2469)
solves #2439
---
.../components/compose/ComposeActivity.kt | 1 -
.../components/instanceinfo/InstanceInfo.kt | 3 +-
.../instanceinfo/InstanceInfoRepository.kt | 4 +-
.../tusky/util/VersionUtils.java | 44 -------------------
.../tusky/util/VersionUtilsTest.kt | 35 ---------------
5 files changed, 2 insertions(+), 85 deletions(-)
delete mode 100644 app/src/main/java/com/keylesspalace/tusky/util/VersionUtils.java
delete mode 100644 app/src/test/java/com/keylesspalace/tusky/util/VersionUtilsTest.kt
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt
index b7a65a7f..48e14def 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt
@@ -335,7 +335,6 @@ class ComposeActivity :
maximumTootCharacters = instanceData.maxChars
charactersReservedPerUrl = instanceData.charactersReservedPerUrl
updateVisibleCharactersLeft()
- binding.composeScheduleButton.visible(instanceData.supportsScheduled)
}
viewModel.emoji.observe { emoji -> setEmojiList(emoji) }
combineLiveData(viewModel.markMediaAsSensitive, viewModel.showContentWarning) { markSensitive, showContentWarning ->
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instanceinfo/InstanceInfo.kt b/app/src/main/java/com/keylesspalace/tusky/components/instanceinfo/InstanceInfo.kt
index db6ec0e1..05e10b6b 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/instanceinfo/InstanceInfo.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/instanceinfo/InstanceInfo.kt
@@ -21,6 +21,5 @@ data class InstanceInfo(
val pollMaxLength: Int,
val pollMinDuration: Int,
val pollMaxDuration: Int,
- val charactersReservedPerUrl: Int,
- val supportsScheduled: Boolean
+ val charactersReservedPerUrl: Int
)
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instanceinfo/InstanceInfoRepository.kt b/app/src/main/java/com/keylesspalace/tusky/components/instanceinfo/InstanceInfoRepository.kt
index 287d5499..8ed26d7b 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/instanceinfo/InstanceInfoRepository.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/instanceinfo/InstanceInfoRepository.kt
@@ -22,7 +22,6 @@ import com.keylesspalace.tusky.db.EmojisEntity
import com.keylesspalace.tusky.db.InstanceInfoEntity
import com.keylesspalace.tusky.entity.Emoji
import com.keylesspalace.tusky.network.MastodonApi
-import com.keylesspalace.tusky.util.VersionUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import javax.inject.Inject
@@ -83,8 +82,7 @@ class InstanceInfoRepository @Inject constructor(
pollMaxLength = instanceInfo?.maxPollOptionLength ?: DEFAULT_MAX_OPTION_LENGTH,
pollMinDuration = instanceInfo?.minPollDuration ?: DEFAULT_MIN_POLL_DURATION,
pollMaxDuration = instanceInfo?.maxPollDuration ?: DEFAULT_MAX_POLL_DURATION,
- charactersReservedPerUrl = instanceInfo?.charactersReservedPerUrl ?: DEFAULT_CHARACTERS_RESERVED_PER_URL,
- supportsScheduled = instanceInfo?.version?.let { VersionUtils(it).supportsScheduledToots() } ?: false
+ charactersReservedPerUrl = instanceInfo?.charactersReservedPerUrl ?: DEFAULT_CHARACTERS_RESERVED_PER_URL
)
}
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/util/VersionUtils.java b/app/src/main/java/com/keylesspalace/tusky/util/VersionUtils.java
deleted file mode 100644
index dceef0f3..00000000
--- a/app/src/main/java/com/keylesspalace/tusky/util/VersionUtils.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/* Copyright 2019 kyori19
- *
- * 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.util;
-
-import androidx.annotation.NonNull;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class VersionUtils {
-
- private int major;
- private int minor;
- private int patch;
-
- public VersionUtils(@NonNull String versionString) {
- String regex = "([0-9]+)\\.([0-9]+)\\.([0-9]+).*";
- Pattern pattern = Pattern.compile(regex);
- Matcher matcher = pattern.matcher(versionString);
- if (matcher.find()) {
- major = Integer.parseInt(matcher.group(1));
- minor = Integer.parseInt(matcher.group(2));
- patch = Integer.parseInt(matcher.group(3));
- }
- }
-
- public boolean supportsScheduledToots() {
- return (major == 2) ? ( (minor == 7) ? (patch >= 0) : (minor > 7) ) : (major > 2);
- }
-
-}
diff --git a/app/src/test/java/com/keylesspalace/tusky/util/VersionUtilsTest.kt b/app/src/test/java/com/keylesspalace/tusky/util/VersionUtilsTest.kt
deleted file mode 100644
index 2731228a..00000000
--- a/app/src/test/java/com/keylesspalace/tusky/util/VersionUtilsTest.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.keylesspalace.tusky.util
-
-import org.junit.Assert.assertEquals
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-class VersionUtilsTest(
- private val versionString: String,
- private val supportsScheduledToots: Boolean
-) {
-
- companion object {
- @JvmStatic
- @Parameterized.Parameters
- fun data() = listOf(
- arrayOf("2.0.0", false),
- arrayOf("2a9a0", false),
- arrayOf("1.0", false),
- arrayOf("error", false),
- arrayOf("", false),
- arrayOf("2.6.9", false),
- arrayOf("2.7.0", true),
- arrayOf("2.00008.0", true),
- arrayOf("2.7.2 (compatible; Pleroma 1.0.0-1168-ge18c7866-pleroma-dot-site)", true),
- arrayOf("3.0.1", true)
- )
- }
-
- @Test
- fun testVersionUtils() {
- assertEquals(VersionUtils(versionString).supportsScheduledToots(), supportsScheduledToots)
- }
-}
From 7fd54e3b4f4c03d7e2b898a1e57fb7ce7c94c5ff Mon Sep 17 00:00:00 2001
From: Konrad Pozniak
Date: Sat, 30 Apr 2022 08:09:59 +0200
Subject: [PATCH 048/104] fix timeline refresh spinner finishing before updates
are visible
---
.../keylesspalace/tusky/components/timeline/TimelineFragment.kt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineFragment.kt
index f9175052..54183888 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineFragment.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineFragment.kt
@@ -172,7 +172,7 @@ class TimelineFragment :
setupRecyclerView()
adapter.addLoadStateListener { loadState ->
- if (loadState.refresh != LoadState.Loading) {
+ if (loadState.refresh != LoadState.Loading && loadState.source.refresh != LoadState.Loading) {
binding.swipeRefreshLayout.isRefreshing = false
}
From 0b46e6bc35d6f6bbe5168e0cedfff90fe05b3752 Mon Sep 17 00:00:00 2001
From: Luna
Date: Sat, 30 Apr 2022 09:17:54 +0000
Subject: [PATCH 049/104] Translated using Weblate (Polish)
Currently translated at 100.0% (478 of 478 strings)
Co-authored-by: Luna
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/pl/
Translation: Tusky/Tusky
---
app/src/main/res/values-pl/strings.xml | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index d468ee00..eb1bf573 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -519,7 +519,7 @@
ktoś kogo zasubskrybowałem/zasubskrybowałam opublikował nowy wpis
Wysłano prośbę o obserwowanie
Ogłoszenia
- Samopoczucie
+ Zdrowie
Anuluj subskrypcję
Zasubskrybuj
Mimo tego, że twoje konto nie jest zablokowane, administracja %1$s uznała, że możesz chcieć ręcznie przejrzeć te prośby o możliwość śledzenia od tych kont.
@@ -530,7 +530,7 @@
Przejrzyj powiadomienia
Zapisano!
Twoja prywatna notatka o tym koncie
- Czas nieokreślony
+ Nieskończona
Dźwięk
Powiadomienia o opublikowaniu nowego wpisu przez kogoś, kogo obserwujesz
Pozycja głównego paska nawigacji
@@ -549,4 +549,13 @@
180 dni
365 dni
Utwórz wpis
+ Login
+ %s zarejestrował(a) się
+ Rejestracje
+ Powiadomienia o nowych użytkownikach
+ Powiadomienia o edycji wpisów z którymi interaktowałeś/aś
+ ktoś zarejestrował się
+ wpis, z którym interaktowałem/am został edytowany
+ %s edytował(a) swój wpis
+ Edycje wpisów
\ No newline at end of file
From ddf3a5992baa8b1ecca4b1b784c6ceb037e64909 Mon Sep 17 00:00:00 2001
From: Eric
Date: Sat, 30 Apr 2022 09:17:55 +0000
Subject: [PATCH 050/104] Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (478 of 478 strings)
Co-authored-by: Eric
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/zh_Hans/
Translation: Tusky/Tusky
---
app/src/main/res/values-zh-rCN/strings.xml | 39 ++++++++++++----------
1 file changed, 21 insertions(+), 18 deletions(-)
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index c2fd66d4..6467706a 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -27,7 +27,7 @@
标签页
嘟文
嘟文
- 嘟文和回复
+ 有回复
已置顶
正在关注
关注者
@@ -42,7 +42,7 @@
%s 转嘟了
敏感内容
已隐藏的照片或视频
- 点击显示
+ 点击查看
显示更多
折叠内容
展开
@@ -156,7 +156,7 @@
移除关注请求?
不再关注此用户?
删除这条嘟文?
- 删除并重新编辑这条嘟文?
+ 删除并重新起草这条嘟文?
公开:所有人可见,并会出现在公共时间轴上
不公开:所有人可见,但不会出现在公共时间轴上
仅关注者:只有经过你确认后关注你的用户可见
@@ -203,7 +203,7 @@
公开
不公开
仅关注者
- 字体大小
+ 嘟文字体大小
最小
小
标准
@@ -299,11 +299,11 @@
保护你的帐户(锁嘟)
你需要手动审核所有关注请求
保存为草稿?
- 正在发送…
- 发送失败
+ 正在发送嘟文…
+ 嘟文发送出错
嘟文发送中
已取消发送
- 嘟文已保存为草稿
+ 嘟文副本已保存为草稿
发表嘟文
当前实例 %s 没有自定义表情符号
已复制到剪贴板
@@ -353,16 +353,10 @@
- 标签页不能超过 %1$d 个
媒体:%s
- 内容提醒:%s
-
- 没有媒体描述信息
-
-
- 被转嘟
-
-
- 被收藏
-
+ 内容警告:%s
+ 没有描述信息
+ 被转嘟
+ 被收藏
公开
@@ -435,7 +429,7 @@
附加留言
转发到 %s
举报失败
- 无法获取状态
+ 无法获取嘟文
该报告将发送给给您的服务器管理员。您可以在下面提供有关回报此帐户的原因的说明:
该帐户来自其他服务器。向那里发送一份匿名的报告副本?
账户
@@ -533,4 +527,13 @@
14 天
365 天
撰写嘟文
+ %s 已注册
+ 某人进行了注册
+ 新用户通知
+ 注册
+ 登录
+ %s 编辑了他们的嘟文
+ 我进行过互动的嘟文被编辑了
+ 嘟文编辑
+ 当你进行过互动的嘟文被编辑时发出通知
\ No newline at end of file
From 4c612107a3e0a0248b8cae24909379c1ae38050d Mon Sep 17 00:00:00 2001
From: GunChleoc
Date: Sat, 30 Apr 2022 09:17:55 +0000
Subject: [PATCH 051/104] Translated using Weblate (Gaelic)
Currently translated at 100.0% (478 of 478 strings)
Co-authored-by: GunChleoc
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/gd/
Translation: Tusky/Tusky
---
app/src/main/res/values-gd/strings.xml | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/app/src/main/res/values-gd/strings.xml b/app/src/main/res/values-gd/strings.xml
index cc46053b..70720c2a 100644
--- a/app/src/main/res/values-gd/strings.xml
+++ b/app/src/main/res/values-gd/strings.xml
@@ -329,7 +329,7 @@
an ceann %du
an ceann %dl
an ceann %db
- Iarrar leantainn orm
+ Iarrtas leantainn air
Videothan
Dealbhan
Pròifil Tusky
@@ -555,4 +555,5 @@
Deasachadh puist
Brathan nuair a thèid postaichean a rinn thu conaltradh leotha a dheasachadh
chaidh post a rinn mi conaltradh leis a deasachadh
+ Clàraich a-steach
\ No newline at end of file
From 260e25a0a4810fc6dc0dc5747f6aba975aa56d7c Mon Sep 17 00:00:00 2001
From: Martin Marconcini
Date: Sat, 30 Apr 2022 19:05:44 +0200
Subject: [PATCH 052/104] Issue 2379: Make it possible to select text in posts.
(#2472)
* Set TextIsSelectable on the corresponding view holders and remove a longpress listener to let Android select text.
* Revert changes, and make selectable text only in detailed status.
Remove long press listener to copy to clipboard (as it interferes with natural text selection on Android).
* Remove unused string (copy_to_clipboard_success) from all translations.
Co-authored-by: Martin Marconcini
---
.../tusky/adapter/StatusDetailedViewHolder.java | 16 ----------------
app/src/main/res/layout/item_status_detailed.xml | 1 +
app/src/main/res/values-ar/strings.xml | 1 -
app/src/main/res/values-bg/strings.xml | 1 -
app/src/main/res/values-bn-rBD/strings.xml | 1 -
app/src/main/res/values-bn-rIN/strings.xml | 1 -
app/src/main/res/values-ca/strings.xml | 1 -
app/src/main/res/values-ckb/strings.xml | 1 -
app/src/main/res/values-cs/strings.xml | 1 -
app/src/main/res/values-cy/strings.xml | 1 -
app/src/main/res/values-de/strings.xml | 1 -
app/src/main/res/values-eo/strings.xml | 1 -
app/src/main/res/values-es/strings.xml | 1 -
app/src/main/res/values-eu/strings.xml | 1 -
app/src/main/res/values-fa/strings.xml | 1 -
app/src/main/res/values-fr/strings.xml | 1 -
app/src/main/res/values-fy/strings.xml | 1 -
app/src/main/res/values-ga/strings.xml | 1 -
app/src/main/res/values-gd/strings.xml | 1 -
app/src/main/res/values-gl/strings.xml | 1 -
app/src/main/res/values-hi/strings.xml | 1 -
app/src/main/res/values-hu/strings.xml | 1 -
app/src/main/res/values-is/strings.xml | 1 -
app/src/main/res/values-it/strings.xml | 1 -
app/src/main/res/values-ja/strings.xml | 1 -
app/src/main/res/values-ko/strings.xml | 1 -
app/src/main/res/values-nl/strings.xml | 1 -
app/src/main/res/values-no-rNB/strings.xml | 1 -
app/src/main/res/values-oc/strings.xml | 1 -
app/src/main/res/values-pl/strings.xml | 1 -
app/src/main/res/values-pt-rBR/strings.xml | 1 -
app/src/main/res/values-ru/strings.xml | 1 -
app/src/main/res/values-sa/strings.xml | 1 -
app/src/main/res/values-si/strings.xml | 1 -
app/src/main/res/values-sl/strings.xml | 1 -
app/src/main/res/values-sv/strings.xml | 1 -
app/src/main/res/values-ta/strings.xml | 1 -
app/src/main/res/values-th/strings.xml | 1 -
app/src/main/res/values-tr/strings.xml | 1 -
app/src/main/res/values-uk/strings.xml | 1 -
app/src/main/res/values-vi/strings.xml | 1 -
app/src/main/res/values-zh-rCN/strings.xml | 1 -
app/src/main/res/values-zh-rHK/strings.xml | 1 -
app/src/main/res/values-zh-rMO/strings.xml | 1 -
app/src/main/res/values-zh-rSG/strings.xml | 1 -
app/src/main/res/values-zh-rTW/strings.xml | 1 -
app/src/main/res/values/strings.xml | 1 -
47 files changed, 1 insertion(+), 61 deletions(-)
diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusDetailedViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusDetailedViewHolder.java
index 1aebf1a7..bf2c05e0 100644
--- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusDetailedViewHolder.java
+++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusDetailedViewHolder.java
@@ -1,13 +1,10 @@
package com.keylesspalace.tusky.adapter;
-import android.content.ClipData;
-import android.content.ClipboardManager;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.text.method.LinkMovementMethod;
import android.view.View;
import android.widget.TextView;
-import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -119,19 +116,6 @@ class StatusDetailedViewHolder extends StatusBaseViewHolder {
setApplication(status.getActionable().getApplication());
- View.OnLongClickListener longClickListener = view -> {
- TextView textView = (TextView) view;
- ClipboardManager clipboard = (ClipboardManager) view.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
- ClipData clip = ClipData.newPlainText("toot", textView.getText());
- clipboard.setPrimaryClip(clip);
-
- Toast.makeText(view.getContext(), R.string.copy_to_clipboard_success, Toast.LENGTH_SHORT).show();
-
- return true;
- };
-
- content.setOnLongClickListener(longClickListener);
- contentWarningDescription.setOnLongClickListener(longClickListener);
setStatusVisibility(status.getActionable().getVisibility());
}
}
diff --git a/app/src/main/res/layout/item_status_detailed.xml b/app/src/main/res/layout/item_status_detailed.xml
index 6301641c..28669d23 100644
--- a/app/src/main/res/layout/item_status_detailed.xml
+++ b/app/src/main/res/layout/item_status_detailed.xml
@@ -123,6 +123,7 @@
android:hyphenationFrequency="full"
android:importantForAccessibility="no"
android:lineSpacingMultiplier="1.1"
+ android:textIsSelectable="true"
android:textColor="?android:textColorPrimary"
android:textSize="?attr/status_text_large"
app:layout_constraintEnd_toEndOf="parent"
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index 07cb8395..cb693670 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -298,7 +298,6 @@
تم الاحتفاظ بنسخة مِن التبويق في مسوداتك
حرر
لا يحتوي مثيل خادومكم %s على أية حزمة إيموجي مخصصة
- تم نسخه إلى الحافظة
نوع الإيموجي
الإفتراضي في النظام
يجب عليك أولا تنزيل حزمة الإيموجي هذه
diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml
index a6ca7a65..092db218 100644
--- a/app/src/main/res/values-bg/strings.xml
+++ b/app/src/main/res/values-bg/strings.xml
@@ -123,7 +123,6 @@
Извършва се търсене…
По подразбиране от системата
Стил на емоджи
- Копирано в клипборда
Инстанцията ви %s няма персонализирани емоджита
Композиране
Копие от публикацията е запазено във вашите чернови
diff --git a/app/src/main/res/values-bn-rBD/strings.xml b/app/src/main/res/values-bn-rBD/strings.xml
index 082f6dea..7d87e3ed 100644
--- a/app/src/main/res/values-bn-rBD/strings.xml
+++ b/app/src/main/res/values-bn-rBD/strings.xml
@@ -48,7 +48,6 @@
আপনাকে প্রথমে এই ইমোজি সেটগুলি ডাউনলোড করতে হবে
সিস্টেমের ডিফল্ট
ইমোজি স্টাইল
- ক্লিপবোর্ডে অনুলিপি করা হয়েছে
আপনার ইনস্ট্যান্স %s এর কোনো কাস্টম ইমোজিস নেই
রচনা
টুট এর একটি কপি আপনার ড্রাফটে সংরক্ষণ করা হয়েছে
diff --git a/app/src/main/res/values-bn-rIN/strings.xml b/app/src/main/res/values-bn-rIN/strings.xml
index 4baa89d0..839e5969 100644
--- a/app/src/main/res/values-bn-rIN/strings.xml
+++ b/app/src/main/res/values-bn-rIN/strings.xml
@@ -304,7 +304,6 @@
টুট এর একটি কপি আপনার ড্রাফটে সংরক্ষণ করা হয়েছে
রচনা
আপনার ইনস্ট্যান্স %s এর কোনো কাস্টম ইমোজিস নেই
- ক্লিপবোর্ডে অনুলিপি করা হয়েছে
ইমোজি স্টাইল
সিস্টেমের ডিফল্ট
আপনাকে প্রথমে এই ইমোজি সেটগুলি ডাউনলোড করতে হবে
diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml
index 44f928ff..8b966061 100644
--- a/app/src/main/res/values-ca/strings.xml
+++ b/app/src/main/res/values-ca/strings.xml
@@ -304,7 +304,6 @@
Una copia del toot s\'ha guardat a esborranys
Escriure
La teva instància %s no te emojis personalitzats
- Copia al porta papers
Estil dels emojis
Sistema per defecte
Hauràs de descarregar el joc d\'emojis
diff --git a/app/src/main/res/values-ckb/strings.xml b/app/src/main/res/values-ckb/strings.xml
index ad91f902..783bbcff 100644
--- a/app/src/main/res/values-ckb/strings.xml
+++ b/app/src/main/res/values-ckb/strings.xml
@@ -375,7 +375,6 @@
تۆ پێویستە سەرەتا ئەم سێتە ئیمۆجییانە دابگریت
سیستەمی بنەڕەت
شێوازی ئیمۆجی
- ڕوونووسکراوە بۆ کلیپ بۆرد
نموونەکەت %s هیچ ئیمۆجییەکی ئاسایی نییە
دروستکردن
کۆپیەکی دەستنووسەکە خەزن کراوە بۆ ڕەشنووسەکانت
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index 5f5716c0..b045aafe 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -302,7 +302,6 @@
Kopie vašeho tootu byla uložena do vašich konceptů
Napsat
Vaše instance %s nemá žádná vlastní emoji
- Zkopírováno do schránky
Styl emoji
Výchozí nastavení systému
Musíte si nejprve stáhnout tyto sady emoji
diff --git a/app/src/main/res/values-cy/strings.xml b/app/src/main/res/values-cy/strings.xml
index 80365d40..78986395 100644
--- a/app/src/main/res/values-cy/strings.xml
+++ b/app/src/main/res/values-cy/strings.xml
@@ -251,7 +251,6 @@
Cadwyd copi o\'r tŵt i\'ch drafftiau
Creu
Nid oes gan eich achos %s emoji bersonol
- Copïwyd i\'r clipfwrdd
Arddull emoji
Rhagosodiad system
Bydd angen i chi lawrlwytho\'r setiau emoji hyn yn gyntaf
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index b82f4b1d..254a6116 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -279,7 +279,6 @@
Eine Kopie des Beitrags wurde in deine Entwürfe gespeichert
Beitrag erstellen
Deine Instanz %s hat keine Emojis definiert
- In die Zwischenablage kopiert
Emoji-Stil
System-Standard
Du musst diese Emoji-Sets zunächst herunterladen
diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml
index 2c03011b..c0cfef4b 100644
--- a/app/src/main/res/values-eo/strings.xml
+++ b/app/src/main/res/values-eo/strings.xml
@@ -299,7 +299,6 @@
Kopio de la mesaĝo estis konservita en viaj malnetoj
Verki
Via nodo %s ne havas proprajn emoĝiojn
- Kopiita en tondujo
Stilo de emoĝioj
Sistema valoro
Vi unue devos elŝuti ĉi tiujn emoĝiarojn
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 95f82051..db50c17e 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -269,7 +269,6 @@
Una copia del estado se ha guardado en borradores
Redactar
Su instancia %s no ofrece emojis personalizados
- Copiado al portapapeles
Estilo de los emojis
Sistema
Tendrás que descargarlos primero
diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml
index 10caf926..19ac2ebd 100644
--- a/app/src/main/res/values-eu/strings.xml
+++ b/app/src/main/res/values-eu/strings.xml
@@ -253,7 +253,6 @@
Tutaren kopia zirriborroetan sartu da
Idatzi
%s instantziak ez ditu emoji pertsonalizatuak eskaintzen
- Arbelean kopiatua
Emojien estiloa
Sistema
Lehenago jaitsi beharko dituzu
diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml
index 62da30e2..31ae0f9a 100644
--- a/app/src/main/res/values-fa/strings.xml
+++ b/app/src/main/res/values-fa/strings.xml
@@ -248,7 +248,6 @@
رونوشتی از بوق در پیشنویسهایتان ذخیره شد
ایجاد
نمونهتان %s هیچ اموجی سفارشیای ندارد
- در تختهگیره رونوشت شد
سبک اموجی
پیشگزیدهٔ سامانه
نخست باید این مجموعههای اموجی را بارگیری کنید
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 248d4f0d..37f8b984 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -304,7 +304,6 @@
Une copie du pouet a été sauvegardée dans vos brouillons
Écrire
Votre instance %s n’a pas d’émojis personnalisés
- Copié dans le presse-papier
Style d’émojis
Par défaut du système
Vous devez commencer par télécharger ces jeux d’émojis
diff --git a/app/src/main/res/values-fy/strings.xml b/app/src/main/res/values-fy/strings.xml
index 97ad4052..f902d742 100644
--- a/app/src/main/res/values-fy/strings.xml
+++ b/app/src/main/res/values-fy/strings.xml
@@ -4,7 +4,6 @@
Dit mei net leech wêze.
Systeem standert
Emoji styl
- Nei it klemboerd kopiearre
Gearstelle
Ferstjoeren ôfbrutsen
Toots oan it ferstjoeren
diff --git a/app/src/main/res/values-ga/strings.xml b/app/src/main/res/values-ga/strings.xml
index 5cdf52f4..2ea95dc9 100644
--- a/app/src/main/res/values-ga/strings.xml
+++ b/app/src/main/res/values-ga/strings.xml
@@ -339,7 +339,6 @@
Sábháladh cóip den tút ar do dhréachtaí
Cum
Níl aon emojis saincheaptha ag do shampla %s
- Cóipeáladh chuig an gearrthaisce
Stíl Emoji
Réamhshocrú an chórais
Beidh ort na tacair emoji seo a íoslódáil ar dtús
diff --git a/app/src/main/res/values-gd/strings.xml b/app/src/main/res/values-gd/strings.xml
index 70720c2a..10d64e0c 100644
--- a/app/src/main/res/values-gd/strings.xml
+++ b/app/src/main/res/values-gd/strings.xml
@@ -239,7 +239,6 @@
Feumaidh tu na seataichean seo de dh’Emojis a luchdadh a-nuas an toiseach
Bun-roghainn an t-siostaim
Stoidhle nan Emojis
- Chaidh lethbhreac dheth a chur air an stòr-bhòrd
Chan eil Emojis gnàthaichte aig an ionstans %s agad
Chaidh lethbhreac dhen phost agad a shàbhaladh ’na dhreachd
Chaidh sgur dhen chur
diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml
index cf0a5083..5b488df3 100644
--- a/app/src/main/res/values-gl/strings.xml
+++ b/app/src/main/res/values-gl/strings.xml
@@ -247,7 +247,6 @@
Deberás descargar primeiro estos conxuntos de emojis
Por defecto no sistema
Estilo dos emoji
- Copiado ao portapapeis
A túa instancia %s non ten emojis personalizados
Redactar
Gardouse unha copia do toot nos borradores
diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml
index d6b7f8ca..2ecee541 100644
--- a/app/src/main/res/values-hi/strings.xml
+++ b/app/src/main/res/values-hi/strings.xml
@@ -306,7 +306,6 @@
बाद में
एप्लिकेशन को पुनः आरंभ की आवश्यकता है
आपको पहले इस इमोजी सेट को डाउनलोड करना होगा
- क्लिपबोर्ड पर कॉपी किया गया
लिखें
टूट की एक प्रति आपके ड्राफ्ट में सहेज ली गई है
टूट भेजने में त्रुटि
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index 18a4bb8a..31911d65 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -248,7 +248,6 @@
A bejegyzés másolatát elmentettük a piszkozataid közé
Szerkesztés
A %s példánynak nincsenek egyedi emoji-jai
- Vágólapra másolva
Emoji stílus
Rendszer alapértelmezés
Először le kell töltened ezeket az emoji készleteket
diff --git a/app/src/main/res/values-is/strings.xml b/app/src/main/res/values-is/strings.xml
index 46948bf0..14a38b27 100644
--- a/app/src/main/res/values-is/strings.xml
+++ b/app/src/main/res/values-is/strings.xml
@@ -317,7 +317,6 @@
Afrit af tístinu þínu hefur verið vistað drögunum þínum
Semja skilaboð
Tilvikið þitt %s er ekki með nein sérsniðin tjáningartákn
- Afritað á klippispjald
Stíll tjáningartákna
Sjálfgefið í kerfinu
Þú þarft fyrst að ná í þessi táknmyndasett
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 9a5f3173..b1a3e130 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -297,7 +297,6 @@
Una copia del toot è stata salvata nelle tue bozze
Componi
La tua istanza %s non ha nessuna emoji personalizzata
- Copiato negli appunti
Stile di emoji
Predefiniti del sistema
Dovrai prima scaricare questo pacchetto di emoji
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index 1b9a2f16..179b7e8b 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -275,7 +275,6 @@
トゥートのコピーが下書きに保存されました
新規投稿
インスタンス %s にはカスタム絵文字がありません
- クリップボードにコピーされました
絵文字スタイル
システムのデフォルト
最初にこれらの絵文字セットをダウンロードする必要があります
diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
index 24cfbdad..fc81f2d3 100644
--- a/app/src/main/res/values-ko/strings.xml
+++ b/app/src/main/res/values-ko/strings.xml
@@ -311,7 +311,6 @@
복사본이 임시 저장에 저장되었습니다
글쓰기
이 인스턴스 %s 은(는) 커스텀 이모지가 없습니다.
- 클립보드에 복사되었습니다
이모지 스타일
시스템 기본
시스템 기본 외의 이모지를 설정하시려면 우선 다운로드해야 합니다
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index 6f4ebea9..fc3c1705 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -277,7 +277,6 @@
Een kopie van de toot werd opgeslagen als concept
Toot schrijven
Jouw server %s heeft geen lokale emojis
- Naar het klembord gekopieerd
Emojistijl
Systeemstandaard
Je moet eerst deze emoji-sets downloaden
diff --git a/app/src/main/res/values-no-rNB/strings.xml b/app/src/main/res/values-no-rNB/strings.xml
index 351c0386..2974e98d 100644
--- a/app/src/main/res/values-no-rNB/strings.xml
+++ b/app/src/main/res/values-no-rNB/strings.xml
@@ -278,7 +278,6 @@
En kopi av tootet er lagret i kladdene dine
Skriv
Instansen %s har ingen egendefinerte emojis
- Kopiert til utklippstavlen
Emoji-stil
Systemstandard
Du må laste ned emoji-samlingene før de kan brukes
diff --git a/app/src/main/res/values-oc/strings.xml b/app/src/main/res/values-oc/strings.xml
index 4dc4370f..b7b6624f 100644
--- a/app/src/main/res/values-oc/strings.xml
+++ b/app/src/main/res/values-oc/strings.xml
@@ -243,7 +243,6 @@
Una còpia del tut es estat salvat dins los borrolhons
Redactar
L’instància %s es pas compatibla amb los emoji personalizats
- Copiat al quichapapièr
Estil dels Emoji
Çò del sistèma
D’en primièr vos cal telecargar los emojis seguents
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index eb1bf573..a1424b3a 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -244,7 +244,6 @@
Kopia wpisu została zapisana jako szkic
Nowy wpis
Twoja instancja %s nie używa żadnych niestandardowych emoji
- Skopiowano do schowka
Styl emoji
Domyślny systemu
Musisz najpierw pobrać te zestawy emoji
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index e4994c04..0cd82676 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -261,7 +261,6 @@
Uma cópia do toot foi salva nos seus rascunhos
Compor
A sua instância %s não possui emojis personalizados
- Copiado para a área de transferência
Estilo de emoji
Padrão do sistema
É necessário baixar estes pacotes de emojis primeiro
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index b5ca508b..2d49aac5 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -324,7 +324,6 @@
Копия поста сохранена в ваши черновики
Сочинить
У вашего узла %s нет собственных эмодзи
- Скопировано в буфер обмена
Стиль эмодзи
Системный
Сперва эти наборы эмодзи нужно скачать
diff --git a/app/src/main/res/values-sa/strings.xml b/app/src/main/res/values-sa/strings.xml
index b69336ed..32b651bf 100644
--- a/app/src/main/res/values-sa/strings.xml
+++ b/app/src/main/res/values-sa/strings.xml
@@ -317,7 +317,6 @@
प्राक्तु भावचिह्नसमूहोऽयमवारोप्यः
प्रणाल्यां पूर्वनिविष्टम्
भावचिह्नशैली
- अंशफलकेऽनुसृतम्
भवदीयं विशिष्टस्थलं %s स्वीयानुकूलभावचिह्नरहितं वर्तते
लिख्यताम्
दौत्यप्रतिलिपिस्तत्र विकर्षेसु रक्षिता
diff --git a/app/src/main/res/values-si/strings.xml b/app/src/main/res/values-si/strings.xml
index df654a29..7ba70498 100644
--- a/app/src/main/res/values-si/strings.xml
+++ b/app/src/main/res/values-si/strings.xml
@@ -171,7 +171,6 @@
\n https://tusky.app
පිළිගන්න
පැ. %d කින්
- පසුරුපුවරුවට පිටපත් විය
මතවිමසුම
ඉවත් කරන්න
මාධ්ය එකතු කරන්න
diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml
index 8281dff3..4aa29e3d 100644
--- a/app/src/main/res/values-sl/strings.xml
+++ b/app/src/main/res/values-sl/strings.xml
@@ -276,7 +276,6 @@
Kopija tuta je bila shranjena v osnutke
Sestavi
Vaše vozlišče %s nima emotikonov po meri
- Kopirano v odložišče
Slog emotikonov
Privzete nastavitve sistema
Najprej boste morali prenesti te emotikone
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
index 2dbc0aa5..5ee0f086 100644
--- a/app/src/main/res/values-sv/strings.xml
+++ b/app/src/main/res/values-sv/strings.xml
@@ -297,7 +297,6 @@
En kopia av tooten har sparats i dina utkast
Skriv
Din instans %s har inga anpassade emojis
- Kopierat till urklipp
Emojis
Systemstandard
Du behöver ladda ned dessa emojis först
diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml
index 3b21fa71..3bedb002 100644
--- a/app/src/main/res/values-ta/strings.xml
+++ b/app/src/main/res/values-ta/strings.xml
@@ -231,7 +231,6 @@
நகலெடுக்கபட்ட toot வரைவில் சேமிக்கபட்டது
எழுது
தங்கள் %s instance(களம்)-ல் எந்தவொரு custom emojis-ம் இல்லை
- பிடிப்புப்பலகையில் நகலெடுக்க
Emoji பாணி
அமைப்பின் இயல்புநிலை
தாங்கள் முதலில் இந்த Emoji sets-னை பதிவிறக்கவேண்டும்
diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml
index b45f9658..98e6ab1e 100644
--- a/app/src/main/res/values-th/strings.xml
+++ b/app/src/main/res/values-th/strings.xml
@@ -116,7 +116,6 @@
ต้องดาวน์โหลดชุดเอโมจิเหล่านี้ก่อน
ค่าปริยายของระบบ
รูปแบบเอโมจิ
- คัดลอกไปยังคลิบบอร์ดแล้ว
Instance %s ไม่มีเอโมจิแบบกำหนดเอง
เขียน
สำเนา Toot บันทึกเป็นฉบับร่างแล้ว
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index 42998606..b097977a 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -261,7 +261,6 @@
Tootun bir kopyası taslaklara kaydedildi
Oluştur
%s örneğinizin herhangi bir özel ifadesi yok
- Panoya kopyalandı
İfade stili
Sistem varsayılanı
Önce bu ifade paketini indirmeniz gerekecek
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index d9fab3a3..5f101944 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -181,7 +181,6 @@
Спочатку потрібно буде завантажити ці набори емодзі
Типовий системний
Стиль емодзі
- Скопійовано до буфера обміну
Ваш сервер %s не має власних емодзі
Зберегти чернетку\?
Вимагає затвердження підписників власноруч
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
index 0746ac6b..1ec9ef80 100644
--- a/app/src/main/res/values-vi/strings.xml
+++ b/app/src/main/res/values-vi/strings.xml
@@ -430,7 +430,6 @@
Bạn cần tải về bộ emoji này trước
Mặc định của thiết bị
Emoji
- Đã chép vào clipboard
Viết
Lưu nháp\?
Tự bạn sẽ phê duyệt người theo dõi
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 6467706a..5dd1975f 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -306,7 +306,6 @@
嘟文副本已保存为草稿
发表嘟文
当前实例 %s 没有自定义表情符号
- 已复制到剪贴板
表情符号风格
系统默认
需要下载表情符号数据
diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml
index f6c9032a..89bd90de 100644
--- a/app/src/main/res/values-zh-rHK/strings.xml
+++ b/app/src/main/res/values-zh-rHK/strings.xml
@@ -304,7 +304,6 @@
嘟文已儲存為草稿
新嘟文
當前站點 %s 沒有自訂表情符號
- 已複製到剪貼簿
表情符號風格
系統預設
你需要先下載這些表情符號包
diff --git a/app/src/main/res/values-zh-rMO/strings.xml b/app/src/main/res/values-zh-rMO/strings.xml
index 878018dd..288af1b3 100644
--- a/app/src/main/res/values-zh-rMO/strings.xml
+++ b/app/src/main/res/values-zh-rMO/strings.xml
@@ -298,7 +298,6 @@
嘟文已儲存為草稿
發表新嘟文
當前站點 %s 沒有自訂表情符號
- 已複製到剪貼簿
表情符號風格
系統預設
你需要先下載這些表情符號包
diff --git a/app/src/main/res/values-zh-rSG/strings.xml b/app/src/main/res/values-zh-rSG/strings.xml
index aa22205b..bc377597 100644
--- a/app/src/main/res/values-zh-rSG/strings.xml
+++ b/app/src/main/res/values-zh-rSG/strings.xml
@@ -302,7 +302,6 @@
嘟文已保存为草稿
新嘟文
当前实例 %s 没有自定义表情符号
- 已复制到剪贴板
表情符号风格
系统默认
需要下载表情符号数据
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index 7acfa268..9fd135f4 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -304,7 +304,6 @@
嘟文已儲存為草稿
發表新嘟文
當前站點 %s 沒有自訂表情符號
- 已複製到剪貼簿
表情符號風格
系統預設
你需要先下載這些表情符號包
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 64ab3a1c..2a209d9a 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -412,7 +412,6 @@
Compose
Your instance %s does not have any custom emojis
- Copied to clipboard
Emoji style
System default
You\'ll need to download these emoji sets first
From f80994956ada0a2be000974c464331153c19dfec Mon Sep 17 00:00:00 2001
From: Vegard Skjefstad
Date: Sat, 30 Apr 2022 17:19:26 +0000
Subject: [PATCH 053/104] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegi?=
=?UTF-8?q?an=20Bokm=C3=A5l)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Currently translated at 100.0% (478 of 478 strings)
Co-authored-by: Vegard Skjefstad
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/nb_NO/
Translation: Tusky/Tusky
---
app/src/main/res/values-no-rNB/strings.xml | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/src/main/res/values-no-rNB/strings.xml b/app/src/main/res/values-no-rNB/strings.xml
index 2974e98d..3512b480 100644
--- a/app/src/main/res/values-no-rNB/strings.xml
+++ b/app/src/main/res/values-no-rNB/strings.xml
@@ -526,4 +526,5 @@
et innlegg jeg har hatt en interaksjon med er redigert
Redigerte innlegg
Varslinger når et innlegg du har hatt en interaksjon med er redigert
+ Innlogging
\ No newline at end of file
From 40a6d6a08a7d879315a544d20596c1446bc534f2 Mon Sep 17 00:00:00 2001
From: Ihor Hordiichuk
Date: Sat, 30 Apr 2022 17:19:26 +0000
Subject: [PATCH 054/104] Translated using Weblate (Ukrainian)
Currently translated at 100.0% (478 of 478 strings)
Co-authored-by: Ihor Hordiichuk
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/uk/
Translation: Tusky/Tusky
---
app/src/main/res/values-uk/strings.xml | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 5f101944..7d4b806e 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -548,4 +548,5 @@
допис, з яким у мене була взаємодія, відредаговано
Сповіщення, коли редагується повідомлення, з яким ви взаємодіяли
Редакції допису
+ Вхід
\ No newline at end of file
From ba8747a99af9f126e7f53670fd07160a33ca723f Mon Sep 17 00:00:00 2001
From: Roj
Date: Sat, 30 Apr 2022 17:19:26 +0000
Subject: [PATCH 055/104] Translated using Weblate (Sorani)
Currently translated at 88.4% (423 of 478 strings)
Co-authored-by: Roj
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/ckb/
Translation: Tusky/Tusky
---
app/src/main/res/values-ckb/strings.xml | 28 ++++++++++++-------------
1 file changed, 14 insertions(+), 14 deletions(-)
diff --git a/app/src/main/res/values-ckb/strings.xml b/app/src/main/res/values-ckb/strings.xml
index 783bbcff..ba275b0b 100644
--- a/app/src/main/res/values-ckb/strings.xml
+++ b/app/src/main/res/values-ckb/strings.xml
@@ -23,7 +23,7 @@
هاشتاگی
پیشاندانی دڵخوازەکان
پیشاندانی بەهێزکردنەکان
- کردنەوەی بەهێزکردنی نووسەر
+ پۆستکەرەوەکە ببینە
هاشتاگ
ئاماژەکان
بەستەرەکان
@@ -57,14 +57,14 @@
وێنە بگرە
زیادکردنی ڕاپرسی
زیادکردنی میدیا
- کردنەوە لە وێبگەڕ
+ لە وێبگەڕ بیکەوە
میدیا
بەدواداچونی داواکاریەکان بکە
دۆمەینە شاراوەکان
بەکارهێنەرە بلۆککراوەکان
بەکارهێنەرە گۆڕاوەکان
نیشانەکان
- دڵخوازەکان
+ بەدڵبوونەکان
پەسەندکراوەکانی ئەژمێر
پەسەندەکان
پرۆفایل
@@ -76,12 +76,12 @@
سڕینەوە
دەستکاری
گوزارشەکان
- پیشاندانی بەهێزکردنەکان
+ پۆستکردنەوەکان نیشان بدە
شاردنەوەی بەهێزکردنەکان
بەربەست کردن لاببە
بلۆک
بەدوادانەچو
- بەدواداکەوتن
+ شوێنی بکەوە
ئایا دڵنیایت لەوەی دەتەوێت بچیتەدەرەوە لە هەژماری %1$s؟
چوونەدەرەوە
چوونەژوورەوە لەگەڵ ماستۆدۆن
@@ -90,7 +90,7 @@
لابردنی دڵخوازەکان
نیشانه
دڵخواز
- لابردنی بەهێزکردن
+ پۆستکردنەوەکە بگەڕێنەوە
بەهێزکردن
وەڵام
وەڵامدانەوەی خێرا
@@ -110,7 +110,7 @@
کرتە بکە بۆ بینین
میدیا شاراوە
ناوەڕۆکی هەستیار
- %s بەرزکرا
+ %s پۆستی کردەوە
\@%s
مۆڵەتەکان
ڕاگه یه نراوەکان
@@ -122,11 +122,11 @@
بەکارهێنەرە بێدەنگ
نیشانەکان
دڵخوازەکان
- شوێنکەوتوان
+ شوێنکەوتوو
شوێنکەوتنەکان
چەسپا
لەگەڵ وەڵامەکان
- بابەتەکان
+ پۆست
زنجیرە
سەرخشتەکان
نامە ڕاستەوخۆکان
@@ -249,7 +249,7 @@
\n
\nکارتێکردنی ئاگانامەکانی پاڵپێوەنان، بەڵام دەتوانیت بە پەسەندکردنە ئاگانامەکانت دا بخشێنیەوە بە دەستی.
ڕزگارکرا
- تێبینی تایبەتی تۆ دەربارەی ئەم ئەژمێرە
+ تێبینیی تایبەتیت بۆ ئەم هەژمارە
Wellbeing
شاردنەوەی ناونیشانی شریتی ئامڕازی سەرەوە
پیشاندانی دیالۆگی دووپاتکردنەوە پێش بەهێزکردن
@@ -294,7 +294,7 @@
ڕاپرسییەک کە دروستت کردووە کۆتایی هات
ڕاپرسییەک کە دەنگی پێداویت کۆتایی هات
دەنگ
- داخراوە
+ کۆتایی هاتووە
کۆتایی دێت لە %s
- %s کەس
@@ -336,10 +336,10 @@
%1$s و %2$s
%1$s
پەسەندکراوە لەلایەن
- بەرزکراوە لەلایەن
+ پۆست کراوەتەوە لەلایەن
- - %s بەهێزکردن
- - %s بەهێزکردن
+ - %s پۆستکردنەوە
+ - %s پۆستکردنەوە
- %1$s دڵخواز
From 8fa5141f27e063ad0ab1795eefc738beed54967d Mon Sep 17 00:00:00 2001
From: Connyduck
Date: Sat, 30 Apr 2022 17:19:26 +0000
Subject: [PATCH 056/104] Added translation using Weblate (Portuguese
(Portugal))
Co-authored-by: Connyduck
---
app/src/main/res/values-pt-rPT/strings.xml | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 app/src/main/res/values-pt-rPT/strings.xml
diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml
new file mode 100644
index 00000000..a6b3daec
--- /dev/null
+++ b/app/src/main/res/values-pt-rPT/strings.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
From 2ccc87231da9a05edcb34f8ef6d765e7af8f95bc Mon Sep 17 00:00:00 2001
From: Bruno Miguel
Date: Sat, 30 Apr 2022 17:19:26 +0000
Subject: [PATCH 057/104] Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (477 of 477 strings)
Co-authored-by: Bruno Miguel
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/pt_PT/
Translation: Tusky/Tusky
---
app/src/main/res/values-pt-rPT/strings.xml | 531 ++++++++++++++++++++-
1 file changed, 530 insertions(+), 1 deletion(-)
diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml
index a6b3daec..e501ed2a 100644
--- a/app/src/main/res/values-pt-rPT/strings.xml
+++ b/app/src/main/res/values-pt-rPT/strings.xml
@@ -1,2 +1,531 @@
-
\ No newline at end of file
+
+ Maior
+ Toots novos
+ Criação de contas
+ %1$s e %2$s
+
+ - %d nova interação
+ - %d novas interações
+
+ A responder a @%s
+ Editar a lista
+ Exige a aprovação manual de seguidores
+ Guardar rascunho\?
+ Depois
+ Desafixar
+ %1$s e %2$s
+ Bem-estar
+ Escrever Toot
+ Deseja excluir a lista %s\?
+ Apesar do seu perfil não ser trancado, %1$s exige que você revise a solicitação para te seguir destes perfis manualmente.
+ Notificar
+ Cancelar
+ Autorização negada.
+ Erro ao adquirir token de login.
+ O toot é muito longo!
+ O ficheiro deve ter menor de 8MB.
+ Os ficheiros de vídeo devem ter menor de 40MB.
+ Os ficheiros de áudio devem ter menor de 40MB.
+ Esse tipo de ficheiro não pode ser enviado.
+ Não foi possível abrir esse ficheiro.
+ É necessária permissão para ler o armazenamento.
+ É necessária permissão para escrever no armazenamento.
+ Não é possível anexar imagens e vídeos no mesmo toot.
+ Erro ao enviar.
+ Erro ao publicar o toot.
+ Página inicial
+ Notificações
+ Local
+ Federada
+ Mensagens Diretas
+ Separadores
+ Conversa
+ Toots
+ Com respostas
+ Fixado
+ Segue
+ Seguidores
+ Favoritos
+ Itens guardados
+ Utilizadors silenciados
+ Utilizadors bloqueados
+ Instâncias bloqueadas
+ Seguidores Pendentes
+ Conteúdo sensível
+ Editar perfil
+ Conteúdo ocultado
+ Rascunhos
+ Toque para ver
+ Mostrar Mais
+ Mostrar Menos
+ Expandir
+ Contrair
+ Toots agendados
+ Anúncios
+ Licenças
+ \@%s
+ %s fez boost
+ Nada aqui.
+ Nada aqui. Arraste para baixo para atualizar!
+ %s fez boost ao seu toot
+ %s adicionou o seu toot aos favoritos
+ %s está a seguir-te
+ %s pediu para te seguir
+ %s criou conta
+ %s acabou de publicar um toot
+ %s editou um toot
+ Denunciar @%s
+ Comentários adicionais\?
+ Resposta rápida
+ Responder
+ Fazer boost
+ Desfazer boost
+ Adicionar aos favoritos
+ Remover dos favoritos
+ Guardar
+ Remover dos itens guardados
+ Mais
+ Escrever
+ Entrar com Mastodon
+ Sair
+ Tem certeza de que deseja sair da conta %1$s\?
+ Seguir
+ Deixar de seguir
+ Bloquear
+ Desbloquear
+ Esconder boosts
+ Mostrar boosts
+ Denunciar
+ Editar
+ Apagar
+ Apagar conversa
+ Apagar e criar novo rascunho
+ TOOT
+ TOOT!
+ Tentar novamente
+ Fechar
+ Perfil
+ Preferências
+ Preferências da Conta
+ Favoritos
+ Guardados
+ utilizadors silenciados
+ utilizadors bloqueados
+ Instâncias bloqueadas
+ Seguidores Pendentes
+ Conteúdo multimédia
+ Abrir no navegador
+ Adicionar conteúdo multimédia
+ Adicionar votação
+ Tirar foto
+ Partilhar
+ Silenciar
+ Remover silêncio
+ Remover %s do silêncio
+ Remover notificações de %s do silêncio
+ Silencie notificações de %s
+ Silenciar %s
+ Remover %s do silêncio
+ Silenciar conversa
+ Remover conversa do silêncio
+ Mencionar
+ Esconder conteúdo multimédia
+ Abrir menu
+ Pesquisar
+ Rascunhos
+ Toots agendados
+ Privacidade do toot
+ Aviso de conteúdo
+ Teclado de emojis
+ Agendar toot
+ Redefinir
+ Adicionar Separador
+ Hiperligações
+ Menções
+ Hashtags
+ Ver quem fez boost
+ Mostrar boosts
+ Mostrar favoritos
+ Hashtags
+ Menções
+ Hiperligações
+ Abrir conteúdo multimédia #%d
+ A descarregar %1$s
+ Copiar hiperligação
+ Abrir como %s
+ Partilhar como…
+ Descarregar conteúdo multimédia
+ A descarregar conteúdo multimédia
+ Partilhar hiperligação do toot via…
+ Partilhar toot via…
+ Partilhar conteúdo multimédia via…
+ Enviado!
+ Utilizador desbloqueado
+ Utilizador removido do silêncio
+ %s desbloqueada
+ Enviado!
+ Resposta enviada com sucesso.
+ Que instância\?
+ Em que está a pensar\?
+ Aviso de conteúdo
+ Nome
+ Biografia
+ Pesquisar…
+ Sem resultados
+ Responder…
+ Avatar
+ Cabeçalho
+ O que é uma instância\?
+ A ligar…
+ O endereço IP ou domínio de qualquer instância pode ser inserido aqui, como por exemplo mastodon.social, masto.donte.com.br, colorid.es ou qualquer outro!
+\n
+\n Se ainda não tem uma conta, insira o nome da instância onde pretende participar e crie uma conta lá.
+\n
+\n Uma instância é um lugar onde sua conta é hospedada, mas pode facilmente seguir e comunicar com pessoas de outras instâncias como se todos estivessem no mesmo site.
+\n
+\nMais informações disponíveis em joinmastodon.org.
+ Envio de Conteúdo Multimédia Terminando
+ A enviar…
+ Descarregar
+ Cancelar pedido para seguir\?
+ Deixar de seguir esta conta\?
+ Apagar este toot\?
+ Apagar e criar novo rascunho\?
+ Apagar esta conversa\?
+ Tem certeza que pretende bloquear a instância %s\? Deixará de poder ver quaisquer conteúdos dessa instância em qualquer timeline pública ou nas suas notificações. Os seus seguidores dessa instância serão removidos.
+ Bloquear instância
+ Bloquear @%s\?
+ Silenciar @%s\?
+ Esconder notificações
+ Público: Publicar em timelines públicas
+ Não listado: Não publicar em timelines públicas
+ Privado: Publicar apenas para os seguidores
+ Direto: Publicar apenas para os utilizadores mencionados
+ Editar notificações
+ Notificações
+ Alertas
+ Notificar com som
+ Notificar com vibração
+ Notificar com luz
+ Notifique-me quando
+ for mencionado
+ for seguido
+ alguém para quem ativei os alertas publicar um toot novo
+ fizerem pedido para me seguir
+ derem boosts nos meus toots
+ adicionarem os meus toots aos favoritos
+ votações terminarem
+ alguém criar conta
+ um toot com o qual interagi for editado
+ Aparência
+ Temas
+ Timelines
+ Filtros
+ Noturno
+ Diurno
+ AMOLED
+ Automático ao pôr-do-sol
+ Usar o Tema do Sistema
+ Navegador
+ Usar Separadores Personalizados do Chrome
+ Esconder o botão de criação de toots ao fazer scroll
+ Idioma
+ Mostrar indicador para bots
+ Reproduzir avatares em GIFs
+ Mostrar desfocagem em conteúdo multimédia sensível
+ Animar emojis personalizados
+ Filtro da timeline
+ Separadors
+ Mostrar boosts
+ Mostrar respostas
+ Mostrar pré-visualização de conteúdo multimédia
+ Proxy
+ Proxy HTTP
+ Ativar proxy HTTP
+ Servidor da proxy HTTP
+ Privacidade padrão dos toots
+ Classificar sempre conteúdo multimédia como sensível
+ Toots (sincronizados com a instância)
+ Erro ao sincronizar configurações
+ Posição do menu principal
+ Superior
+ Inferior
+ Público
+ Porta da proxy HTTP
+ Não listado
+ Privado
+ Menor
+ Pequeno
+ Médio
+ Grande
+ Menções Novas
+ Notificações para menções novas
+ Novos Seguidores
+ Tamanho do texto do toot
+ Notificações para seguidores novos
+ Seguidores Pendentes
+ Notificações para seguidores pendentes
+ Boosts
+ Votações
+ Notificações para votações que terminaram
+ Notificações quando alguém para quem ativei os alertas publicar um toot novo
+ Notificações para novos utilizadores
+ Edições a toots
+ Notificações para boosts recebidos
+ Favoritos
+ Notificações quando os teus toots são adicionados aos favoritos
+ Notificações quando toots com os quais interagi foram editados
+ %s mencionou-te
+ %1$s, %2$s, %3$s e %4$d outros
+ %1$s, %2$s e %3$s
+ Perfil Bloqueado
+ Sobre
+ Tusky %s
+ A correr o Tusky
+ Atualizar
+ Tusky é um software livre e de código aberto e é ljcenciado com a versão 3 da GNU General Public License. Leia a licença aqui: https://www.gnu.org/licenses/gpl-3.0.pt-br.html
+ Página do projeto:
+\n https://tusky.app
+ Reporte de erros e pedidos de funcionalidades:
+\n https://github.com/tuskyapp/Tusky/issues
+ Perfil do Tusky
+ Partilhar conteúdo do toot
+ Partilhar hiperligação do toot
+ Imagens
+ Vídeo
+ Áudio
+ Anexos
+ Pedido enviado
+ em %dy
+ em %dd
+ em %dh
+ em %dm
+ em %ds
+ %da
+ %dd
+ %dh
+ %dm
+ %ds
+ Segue-te
+ Mostrar sempre conteúdo multimédia sensível
+ Expandir sempre toots com Aviso de Conteúdo
+ Palavra completa
+ Conteúdo Multimédia
+ carregar mais
+ Timelines públicas
+ Conversas
+ Criar filtro
+ Editar filtro
+ Remover
+ Se a palavra ou frase for alfanumérica, só será aplicado se corresponder à palavra completa
+ Frase para filtrar
+ Adicionar Conta
+ Adicionar nova Conta Mastodon
+ Listas
+ Não foi possível renomear a lista
+ Listas
+ Lista da timeline
+ Não foi possível criar a lista
+ Não foi possível apagar a lista
+ Criar uma lista
+ Renomear a lista
+ Apagar a lista
+ Pesquisar pessoas que você segue
+ Adicionar conta à lista
+ Remover conta da lista
+ Publicar com a conta %1$s
+ Erro ao incluir descrição
+
+ - Descrição para deficientes visuais
+\n(até %d caracteres)
+
+
+ Descrever
+ Remover
+ Bloquear perfil
+ A enviar o toot…
+ Erro ao enviar o toot
+ A Enviar os Toots
+ Envio cancelado
+ Uma cópia do toot foi guardada nos seus rascunhos
+ Escrever
+ A sua instância, %s, não tem emojis personalizados
+ Estilo de emoji
+ Padrão do sistema
+ É necessário descarregar estes pacotes de emojis primeiro
+ A fazer pesquisa…
+ Expandir/Contrair todos os toots
+ Abrir toot
+ É necessário reiniciar a aplicação
+ É necessário reiniciar o aplicativo para aplicar as alterações
+ Reiniciar
+ Pacote de emojis padrão do seu dispositivo
+ Emojis padrão do Android da versão 4.4 até 7.1
+ Pacote de emojis padrão do Mastodon
+ Pacote de emojis atual do Google
+ Erro ao baixar
+ Robô
+ %1$s mudou-se para:
+ Dar boost para o mesmo público
+ Desfazer boost
+ O Tusky contém código e recursos dos seguintes projetos de código aberto:
+ Licenciado sob a licença Apache (cópia separadorixo)
+ CC-BY 4.0
+ CC-BY-SA 4.0
+ Metadados do perfil
+ Adicionar
+ Rótulo
+ Conteúdo
+ Usar tempo absoluto
+ As informações separadorixo podem refletir incompletamente o perfil do utilizador. Toque aqui para abrir o perfil completo no navegador.
+ Fixar
+
+ - %1$s Favorito
+ - %1$s Favoritos
+
+
+ - %s Boost
+ - %s Boosts
+
+ Levou boost de
+ Favoritado por
+ %1$s
+ %1$s, %2$s e %3$d outros
+
+ - excedeu o máximo de %1$d separador
+ - excedeu o máximo de %1$d separadors
+
+ conteúdo multimédia: %s
+ Aviso de Conteúdo: %s
+ Sem descrição
+ Você fez boost
+ Favoritado
+ Salvo
+ Público
+ Não-listado
+ Privado
+ Direto
+ Enquete com as opções: %1$s, %2$s, %3$s, %4$s; %5$s
+ Nome da lista
+ Adicionar hashtag
+ Hashtag sem #
+ Hashtags
+ Selecionar lista
+ Lista
+ Limpar
+ Filtro
+ Salvar
+ Compor toot
+ Compor
+ Tem certeza de que deseja limpar permanentemente todas as suas notificações\?
+ Opções para imagem %s
+ %1$s • %2$s
+
+ - %s voto
+ - %s votos
+
+
+ - %s pessoa
+ - %s pessoas
+
+ termina em %s
+ Terminou
+ Votar
+ Uma enquete que você votou terminou
+ Sua enquete terminou
+
+ - %d dia restante
+ - %d dias restantes
+
+
+ - %d hora restante
+ - %d horas restantes
+
+
+ - %d minuto restante
+ - %d minutos restantes
+
+
+ - %d segundo restante
+ - %d segundos restantes
+
+ Continuar
+ Voltar
+ Ok
+ \@%s denunciado com sucesso
+ Comentários adicionais
+ Encaminhar para %s
+ Erro ao denunciar
+ Erro ao carregar toots
+ A denúncia será enviada aos moderadores da instância. Explique por que denunciou a conta:
+ A conta está em outra instância. Enviar uma cópia anônima da denúncia para lá\?
+ Contas
+ Erro ao pesquisar
+ Mostrar filtro de notificações
+ Ativar deslizar para alternar entre separadors
+ Enquete
+ Duração
+ Indefinido
+ 5 minutos
+ 30 minutos
+ 1 hora
+ 6 horas
+ 1 dia
+ 3 dias
+ 7 dias
+ 14 dias
+ 30 dias
+ 60 dias
+ 90 dias
+ 180 dias
+ 365 dias
+ Adicionar opção
+ Múltiplas opções
+ Opção %d
+ Editar
+ Erro ao pesquisar %s
+ Sem rascunhos.
+ Sem toots agendados.
+ Salvo!
+ Algumas informações que podem afetar seu bem-estar serão ocultadas. Isso inclui:
+\n
+\n- Notificações de favoritos, boosts e seguidores
+\n- Número de favoritos e boosts nos toots
+\n- Status de toots e seguidores nos perfis
+\n
+\nNotificações push não serão afetadas, mas é possível revisar sua preferência manualmente.
+ Revisar notificações
+ Limitar notificações da timeline
+ Sem comunicados.
+ Mastodon possui um intervalo mínimo de 5 minutos para agendar.
+ Mostrar prévias de Hiperligações nas linhas
+ Solicitar confirmação antes de dar boost
+ Solicitar confirmação antes de favoritar
+ Esconder o título da barra superior de tarefas
+ Nota pessoal sobre este perfil aqui
+ Esconder status dos toots
+ Esconder status dos perfis
+
+ - Não é possível anexar mais de %1$d arquivo de conteúdo multimédia.
+ - Não é possível anexar mais de %1$d arquivos de conteúdo multimédia.
+
+ Erro ao enviar o toot!
+ Erro ao carregar toot para responder
+ Rascunho excluído
+ O toot em que se rascunhou uma resposta foi excluído
+ Ocorreu um erro.
+ Ocorreu um erro de conetividade! Por favor, verifique a sua ligação e tente novamente!
+ Isto não pode estar vazio.
+ Instância inválida inserida
+ Erro ao autenticar com esta instância.
+ Nao foi possível encontrar um navegador.
+ Ocorreu um erro não identificado de autorização.
+ Entrar
+ Guardar
+ Editar perfil
+ Editar
+ Desfazer
+ Aceitar
+ Rejeitar
+
\ No newline at end of file
From ce5ec15ff13ba0b36b486edd192dbb6c103a2f6c Mon Sep 17 00:00:00 2001
From: Konrad Pozniak
Date: Sun, 1 May 2022 17:16:22 +0200
Subject: [PATCH 058/104] increase timeout for media uploads (#2489)
---
.../tusky/components/compose/MediaUploader.kt | 6 +++---
.../keylesspalace/tusky/di/NetworkModule.kt | 15 +++++++++++++++
.../tusky/network/MastodonApi.kt | 7 -------
.../tusky/network/MediaUploadApi.kt | 19 +++++++++++++++++++
4 files changed, 37 insertions(+), 10 deletions(-)
create mode 100644 app/src/main/java/com/keylesspalace/tusky/network/MediaUploadApi.kt
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaUploader.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaUploader.kt
index 85be146c..f1debc98 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaUploader.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaUploader.kt
@@ -26,7 +26,7 @@ import androidx.core.net.toUri
import com.keylesspalace.tusky.BuildConfig
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.components.compose.ComposeActivity.QueuedMedia
-import com.keylesspalace.tusky.network.MastodonApi
+import com.keylesspalace.tusky.network.MediaUploadApi
import com.keylesspalace.tusky.network.ProgressRequestBody
import com.keylesspalace.tusky.util.MEDIA_SIZE_UNKNOWN
import com.keylesspalace.tusky.util.getImageSquarePixels
@@ -75,7 +75,7 @@ class CouldNotOpenFileException : Exception()
class MediaUploader @Inject constructor(
private val context: Context,
- private val mastodonApi: MastodonApi
+ private val mediaUploadApi: MediaUploadApi
) {
@OptIn(ExperimentalCoroutinesApi::class)
@@ -222,7 +222,7 @@ class MediaUploader @Inject constructor(
null
}
- val result = mastodonApi.uploadMedia(body, description).getOrThrow()
+ val result = mediaUploadApi.uploadMedia(body, description).getOrThrow()
send(UploadEvent.FinishedEvent(result.id))
awaitClose()
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt
index d8b52ca3..90dd3026 100644
--- a/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt
@@ -24,6 +24,7 @@ import com.keylesspalace.tusky.BuildConfig
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.network.InstanceSwitchAuthInterceptor
import com.keylesspalace.tusky.network.MastodonApi
+import com.keylesspalace.tusky.network.MediaUploadApi
import com.keylesspalace.tusky.util.getNonNullString
import dagger.Module
import dagger.Provides
@@ -112,4 +113,18 @@ class NetworkModule {
@Provides
@Singleton
fun providesApi(retrofit: Retrofit): MastodonApi = retrofit.create()
+
+ @Provides
+ @Singleton
+ fun providesMediaUploadApi(retrofit: Retrofit, okHttpClient: OkHttpClient): MediaUploadApi {
+ val longTimeOutOkHttpClient = okHttpClient.newBuilder()
+ .readTimeout(100, TimeUnit.SECONDS)
+ .writeTimeout(100, TimeUnit.SECONDS)
+ .build()
+
+ return retrofit.newBuilder()
+ .client(longTimeOutOkHttpClient)
+ .build()
+ .create()
+ }
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt
index 25fe03d4..7357293b 100644
--- a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt
@@ -142,13 +142,6 @@ interface MastodonApi {
@POST("api/v1/notifications/clear")
fun clearNotifications(): Single
- @Multipart
- @POST("api/v2/media")
- suspend fun uploadMedia(
- @Part file: MultipartBody.Part,
- @Part description: MultipartBody.Part? = null
- ): Result
-
@FormUrlEncoded
@PUT("api/v1/media/{mediaId}")
suspend fun updateMedia(
diff --git a/app/src/main/java/com/keylesspalace/tusky/network/MediaUploadApi.kt b/app/src/main/java/com/keylesspalace/tusky/network/MediaUploadApi.kt
new file mode 100644
index 00000000..c7e9633f
--- /dev/null
+++ b/app/src/main/java/com/keylesspalace/tusky/network/MediaUploadApi.kt
@@ -0,0 +1,19 @@
+package com.keylesspalace.tusky.network
+
+import com.keylesspalace.tusky.entity.MediaUploadResult
+import okhttp3.MultipartBody
+import retrofit2.http.Multipart
+import retrofit2.http.POST
+import retrofit2.http.Part
+
+/** endpoints defined in this interface will be called with a higher timeout than usual
+ * which is necessary for media uploads to succeed on some servers
+ */
+interface MediaUploadApi {
+ @Multipart
+ @POST("api/v2/media")
+ suspend fun uploadMedia(
+ @Part file: MultipartBody.Part,
+ @Part description: MultipartBody.Part? = null
+ ): Result
+}
From 444e7365c972d79f54553b841d4a212a52f44a4e Mon Sep 17 00:00:00 2001
From: Konrad Pozniak
Date: Tue, 3 May 2022 19:12:35 +0200
Subject: [PATCH 059/104] fix race condition where multiple uploaded media can
get same internal id (#2479)
* fix race condition where multiple uploaded media can get same internal id
* atomically update media stateflow
* atomically update media stateflow
---
.../components/compose/ComposeActivity.kt | 24 +++--
.../components/compose/ComposeViewModel.kt | 99 +++++++++++--------
2 files changed, 72 insertions(+), 51 deletions(-)
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt
index 48e14def..32162614 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt
@@ -51,6 +51,7 @@ import androidx.core.view.ContentInfoCompat
import androidx.core.view.OnReceiveContentListener
import androidx.core.view.isGone
import androidx.core.view.isVisible
+import androidx.lifecycle.asLiveData
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager
@@ -344,14 +345,17 @@ class ComposeActivity :
viewModel.statusVisibility.observe { visibility ->
setStatusVisibility(visibility)
}
- viewModel.media.observe { media ->
- mediaAdapter.submitList(media)
- if (media.size != mediaCount) {
- mediaCount = media.size
- binding.composeMediaPreviewBar.visible(media.isNotEmpty())
- updateSensitiveMediaToggle(viewModel.markMediaAsSensitive.value != false, viewModel.showContentWarning.value != false)
+ lifecycleScope.launch {
+ viewModel.media.collect { media ->
+ mediaAdapter.submitList(media)
+ if (media.size != mediaCount) {
+ mediaCount = media.size
+ binding.composeMediaPreviewBar.visible(media.isNotEmpty())
+ updateSensitiveMediaToggle(viewModel.markMediaAsSensitive.value != false, viewModel.showContentWarning.value != false)
+ }
}
}
+
viewModel.poll.observe { poll ->
binding.pollPreview.visible(poll != null)
poll?.let(binding.pollPreview::setPoll)
@@ -364,7 +368,7 @@ class ComposeActivity :
}
updateScheduleButton()
}
- combineOptionalLiveData(viewModel.media, viewModel.poll) { media, poll ->
+ combineOptionalLiveData(viewModel.media.asLiveData(), viewModel.poll) { media, poll ->
val active = poll == null &&
media!!.size != 4 &&
(media.isEmpty() || media.first().type == QueuedMedia.Type.IMAGE)
@@ -781,11 +785,11 @@ class ComposeActivity :
spoilerText = binding.composeContentWarningField.text.toString()
}
val characterCount = calculateTextLength()
- if ((characterCount <= 0 || contentText.isBlank()) && viewModel.media.value!!.isEmpty()) {
+ if ((characterCount <= 0 || contentText.isBlank()) && viewModel.media.value.isEmpty()) {
binding.composeEditField.error = getString(R.string.error_empty)
enableButtons(true)
} else if (characterCount <= maximumTootCharacters) {
- if (viewModel.media.value!!.isNotEmpty()) {
+ if (viewModel.media.value.isNotEmpty()) {
finishingUploadDialog = ProgressDialog.show(
this, getString(R.string.dialog_title_finishing_media_upload),
getString(R.string.dialog_message_uploading_media), true, true
@@ -983,7 +987,7 @@ class ComposeActivity :
}
data class QueuedMedia(
- val localId: Long,
+ val localId: Int,
val uri: Uri,
val type: Type,
val mediaSize: Long,
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt
index 81500ee4..7b180532 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt
@@ -21,6 +21,7 @@ import androidx.core.net.toUri
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
+import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import com.keylesspalace.tusky.components.compose.ComposeActivity.QueuedMedia
import com.keylesspalace.tusky.components.drafts.DraftHelper
@@ -36,15 +37,17 @@ import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.service.ServiceClient
import com.keylesspalace.tusky.service.StatusToSend
import com.keylesspalace.tusky.util.combineLiveData
-import com.keylesspalace.tusky.util.filter
-import com.keylesspalace.tusky.util.map
import com.keylesspalace.tusky.util.randomAlphanumericString
import com.keylesspalace.tusky.util.toLiveData
-import com.keylesspalace.tusky.util.withoutFirstWhich
import io.reactivex.rxjava3.core.Observable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.flow.updateAndGet
import kotlinx.coroutines.launch
import kotlinx.coroutines.rx3.rxSingle
import kotlinx.coroutines.withContext
@@ -84,10 +87,10 @@ class ComposeViewModel @Inject constructor(
val poll: MutableLiveData = mutableLiveData(null)
val scheduledAt: MutableLiveData = mutableLiveData(null)
- val media = mutableLiveData>(listOf())
+ val media: MutableStateFlow> = MutableStateFlow(emptyList())
val uploadError = MutableLiveData()
- private val mediaToJob = mutableMapOf()
+ private val mediaToJob = mutableMapOf()
private val isEditingScheduledToot get() = !scheduledTootId.isNullOrEmpty()
@@ -103,7 +106,7 @@ class ComposeViewModel @Inject constructor(
suspend fun pickMedia(mediaUri: Uri, description: String? = null): Result = withContext(Dispatchers.IO) {
try {
val (type, uri, size) = mediaUploader.prepareMedia(mediaUri)
- val mediaItems = media.value!!
+ val mediaItems = media.value
if (type != QueuedMedia.Type.IMAGE &&
mediaItems.isNotEmpty() &&
mediaItems[0].type == QueuedMedia.Type.IMAGE
@@ -118,29 +121,31 @@ class ComposeViewModel @Inject constructor(
}
}
- private fun addMediaToQueue(
+ private suspend fun addMediaToQueue(
type: QueuedMedia.Type,
uri: Uri,
mediaSize: Long,
description: String? = null
): QueuedMedia {
- val mediaItem = QueuedMedia(
- localId = System.currentTimeMillis(),
- uri = uri,
- type = type,
- mediaSize = mediaSize,
- description = description
- )
- media.postValue(media.value!! + mediaItem)
+ val mediaItem = media.updateAndGet { mediaValue ->
+ val mediaItem = QueuedMedia(
+ localId = (mediaValue.maxOfOrNull { it.localId } ?: 0) + 1,
+ uri = uri,
+ type = type,
+ mediaSize = mediaSize,
+ description = description
+ )
+ mediaValue + mediaItem
+ }.last()
mediaToJob[mediaItem.localId] = viewModelScope.launch {
mediaUploader
.uploadMedia(mediaItem)
.catch { error ->
- media.postValue(media.value?.filter { it.localId != mediaItem.localId } ?: emptyList())
+ media.update { mediaValue -> mediaValue.filter { it.localId != mediaItem.localId } }
uploadError.postValue(error)
}
.collect { event ->
- val item = media.value?.find { it.localId == mediaItem.localId }
+ val item = media.value.find { it.localId == mediaItem.localId }
?: return@collect
val newMediaItem = when (event) {
is UploadEvent.ProgressEvent ->
@@ -148,16 +153,14 @@ class ComposeViewModel @Inject constructor(
is UploadEvent.FinishedEvent ->
item.copy(id = event.mediaId, uploadPercent = -1)
}
- synchronized(media) {
- val mediaValue = media.value!!
- val index = mediaValue.indexOfFirst { it.localId == newMediaItem.localId }
- media.postValue(
- if (index == -1) {
- mediaValue + newMediaItem
+ media.update { mediaValue ->
+ mediaValue.map { mediaItem ->
+ if (mediaItem.localId == newMediaItem.localId) {
+ newMediaItem
} else {
- mediaValue.toMutableList().also { it[index] = newMediaItem }
+ mediaItem
}
- )
+ }
}
}
}
@@ -165,13 +168,23 @@ class ComposeViewModel @Inject constructor(
}
private fun addUploadedMedia(id: String, type: QueuedMedia.Type, uri: Uri, description: String?) {
- val mediaItem = QueuedMedia(System.currentTimeMillis(), uri, type, 0, -1, id, description)
- media.value = media.value!! + mediaItem
+ media.update { mediaValue ->
+ val mediaItem = QueuedMedia(
+ localId = (mediaValue.maxOfOrNull { it.localId } ?: 0) + 1,
+ uri = uri,
+ type = type,
+ mediaSize = 0,
+ uploadPercent = -1,
+ id = id,
+ description = description
+ )
+ mediaValue + mediaItem
+ }
}
fun removeMediaFromQueue(item: QueuedMedia) {
mediaToJob[item.localId]?.cancel()
- media.value = media.value!!.withoutFirstWhich { it.localId == item.localId }
+ media.update { mediaValue -> mediaValue.filter { it.localId == item.localId } }
}
fun toggleMarkSensitive() {
@@ -211,7 +224,7 @@ class ComposeViewModel @Inject constructor(
viewModelScope.launch {
val mediaUris: MutableList = mutableListOf()
val mediaDescriptions: MutableList = mutableListOf()
- media.value?.forEach { item ->
+ media.value.forEach { item ->
mediaUris.add(item.uri.toString())
mediaDescriptions.add(item.description)
}
@@ -248,14 +261,14 @@ class ComposeViewModel @Inject constructor(
Observable.just(Unit)
}.toLiveData()
- val sendObservable = media
+ val sendFlow = media
.filter { items -> items.all { it.uploadPercent == -1 } }
.map {
val mediaIds: MutableList = mutableListOf()
val mediaUris: MutableList = mutableListOf()
val mediaDescriptions: MutableList = mutableListOf()
val mediaProcessed: MutableList = mutableListOf()
- for (item in media.value!!) {
+ for (item in media.value) {
mediaIds.add(item.id!!)
mediaUris.add(item.uri)
mediaDescriptions.add(item.description ?: "")
@@ -285,17 +298,21 @@ class ComposeViewModel @Inject constructor(
serviceClient.sendToot(tootToSend)
}
- return combineLiveData(deletionObservable, sendObservable) { _, _ -> }
+ return combineLiveData(deletionObservable, sendFlow.asLiveData()) { _, _ -> }
}
- suspend fun updateDescription(localId: Long, description: String): Boolean {
- val newList = media.value!!.toMutableList()
- val index = newList.indexOfFirst { it.localId == localId }
- if (index != -1) {
- newList[index] = newList[index].copy(description = description)
+ suspend fun updateDescription(localId: Int, description: String): Boolean {
+ val newMediaList = media.updateAndGet { mediaValue ->
+ mediaValue.map { mediaItem ->
+ if (mediaItem.localId == localId) {
+ mediaItem.copy(description = description)
+ } else {
+ mediaItem
+ }
+ }
}
- media.value = newList
- val updatedItem = newList.find { it.localId == localId }
+
+ val updatedItem = newMediaList.find { it.localId == localId }
if (updatedItem?.id != null) {
return api.updateMedia(updatedItem.id, description)
.fold({
@@ -387,8 +404,8 @@ class ComposeViewModel @Inject constructor(
val draftAttachments = composeOptions?.draftAttachments
if (draftAttachments != null) {
// when coming from DraftActivity
- draftAttachments.forEach { attachment ->
- viewModelScope.launch {
+ viewModelScope.launch {
+ draftAttachments.forEach { attachment ->
pickMedia(attachment.uri, attachment.description)
}
}
From 1eed0e1cc2fc8c1f5dae8c7e75fc00de118e2b7c Mon Sep 17 00:00:00 2001
From: Konrad Pozniak
Date: Tue, 3 May 2022 19:13:13 +0200
Subject: [PATCH 060/104] fix unparsed html when sharing status content (#2491)
---
.../main/java/com/keylesspalace/tusky/fragment/SFragment.java | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java
index bb02807d..ad81abe3 100644
--- a/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java
+++ b/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java
@@ -58,6 +58,7 @@ import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.network.MastodonApi;
import com.keylesspalace.tusky.network.TimelineCases;
import com.keylesspalace.tusky.util.LinkHelper;
+import com.keylesspalace.tusky.util.StatusParsingHelper;
import com.keylesspalace.tusky.view.MuteAccountDialog;
import com.keylesspalace.tusky.viewdata.AttachmentViewData;
@@ -228,7 +229,7 @@ public abstract class SFragment extends Fragment implements Injectable {
String stringToShare = statusToShare.getAccount().getUsername() +
" - " +
- statusToShare.getContent().toString();
+ StatusParsingHelper.parseAsMastodonHtml(statusToShare.getContent()).toString();
sendIntent.putExtra(Intent.EXTRA_TEXT, stringToShare);
sendIntent.putExtra(Intent.EXTRA_SUBJECT, statusUrl);
sendIntent.setType("text/plain");
From f3d79238034586d2fb81cff21d8ecdeb3a15ad54 Mon Sep 17 00:00:00 2001
From: Ivan Kupalov
Date: Tue, 3 May 2022 19:14:55 +0200
Subject: [PATCH 061/104] Improve UX when Login WebView fails to load the page
(#2492)
Previously we simply closed the screen with the login WebView which
could cause confusion. Now we specify that page could not be loaded.
As a side effect it will also show the error message which the server
returns (if any).
---
.../com/keylesspalace/tusky/components/login/LoginActivity.kt | 4 +++-
.../tusky/components/login/LoginWebViewActivity.kt | 2 +-
app/src/main/res/values/strings.xml | 1 +
3 files changed, 5 insertions(+), 2 deletions(-)
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 4df7abc1..bcbb4abf 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
@@ -68,7 +68,9 @@ class LoginActivity : BaseActivity(), Injectable {
// Authorization failed. Put the error response where the user can read it and they
// can try again.
setLoading(false)
- binding.domainTextInputLayout.error = getString(R.string.error_authorization_denied)
+ // 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(
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/login/LoginWebViewActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/login/LoginWebViewActivity.kt
index 58f745e7..a32a164c 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/login/LoginWebViewActivity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/login/LoginWebViewActivity.kt
@@ -117,7 +117,7 @@ class LoginWebViewActivity : BaseActivity(), Injectable {
error: WebResourceError
) {
Log.d("LoginWeb", "Failed to load ${data.url}: $error")
- finishWithoutSlideOutAnimation()
+ sendResult(LoginResult.Err(getString(R.string.error_could_not_load_login_page)))
}
override fun shouldOverrideUrlLoading(
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 2a209d9a..fc1ed743 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -9,6 +9,7 @@
An unidentified authorization error occurred.
Authorization was denied.
Failed getting a login token.
+ Could not load the login page.
The post is too long!
The file must be less than 8MB.
Video files must be less than 40MB.
From a2cc622683073862f8842ceb9ed764d32ccf2a47 Mon Sep 17 00:00:00 2001
From: Konrad Pozniak
Date: Tue, 3 May 2022 19:15:59 +0200
Subject: [PATCH 062/104] fix EmojiCompat.get().process crash in polls (#2494)
---
.../com/keylesspalace/tusky/adapter/PollAdapter.kt | 10 +++-------
.../tusky/components/account/AccountActivity.kt | 10 +---------
2 files changed, 4 insertions(+), 16 deletions(-)
diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt
index 9ffeca9e..ef366795 100644
--- a/app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt
@@ -19,7 +19,6 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
-import androidx.emoji2.text.EmojiCompat
import androidx.recyclerview.widget.RecyclerView
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.ItemPollBinding
@@ -87,9 +86,8 @@ class PollAdapter : RecyclerView.Adapter>() {
when (mode) {
RESULT -> {
val percent = calculatePercent(option.votesCount, votersCount, voteCount)
- val emojifiedPollOptionText = buildDescription(option.title, percent, option.voted, resultTextView.context)
+ resultTextView.text = buildDescription(option.title, percent, option.voted, resultTextView.context)
.emojify(emojis, resultTextView, animateEmojis)
- resultTextView.text = EmojiCompat.get().process(emojifiedPollOptionText)
val level = percent * 100
val optionColor = if (option.voted) {
@@ -103,8 +101,7 @@ class PollAdapter : RecyclerView.Adapter>() {
resultTextView.setOnClickListener(resultClickListener)
}
SINGLE -> {
- val emojifiedPollOptionText = option.title.emojify(emojis, radioButton, animateEmojis)
- radioButton.text = EmojiCompat.get().process(emojifiedPollOptionText)
+ radioButton.text = option.title.emojify(emojis, radioButton, animateEmojis)
radioButton.isChecked = option.selected
radioButton.setOnClickListener {
pollOptions.forEachIndexed { index, pollOption ->
@@ -114,8 +111,7 @@ class PollAdapter : RecyclerView.Adapter>() {
}
}
MULTIPLE -> {
- val emojifiedPollOptionText = option.title.emojify(emojis, checkBox, animateEmojis)
- checkBox.text = EmojiCompat.get().process(emojifiedPollOptionText)
+ checkBox.text = option.title.emojify(emojis, checkBox, animateEmojis)
checkBox.isChecked = option.selected
checkBox.setOnCheckedChangeListener { _, isChecked ->
pollOptions[holder.bindingAdapterPosition].selected = isChecked
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt
index cc8719bb..6a8bc7dc 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt
@@ -37,7 +37,6 @@ import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsCompat.Type.systemBars
import androidx.core.view.updatePadding
-import androidx.emoji2.text.EmojiCompat
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.viewpager2.widget.MarginPageTransformer
@@ -459,14 +458,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
*/
private fun updateToolbar() {
loadedAccount?.let { account ->
-
- val emojifiedName = account.name.emojify(account.emojis, binding.accountToolbar, animateEmojis)
-
- try {
- supportActionBar?.title = EmojiCompat.get().process(emojifiedName)
- } catch (e: IllegalStateException) {
- supportActionBar?.title = emojifiedName
- }
+ supportActionBar?.title = account.name.emojify(account.emojis, binding.accountToolbar, animateEmojis)
supportActionBar?.subtitle = String.format(getString(R.string.post_username_format), account.username)
}
}
From f693c5ea210d0e3b774ccc3627a6668cda9e4029 Mon Sep 17 00:00:00 2001
From: Luna
Date: Sun, 1 May 2022 17:40:32 +0000
Subject: [PATCH 063/104] Translated using Weblate (Polish)
Currently translated at 100.0% (16 of 16 strings)
Translation: Tusky/Tusky description
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky-app/pl/
---
fastlane/metadata/android/pl/changelogs/89.txt | 7 +++++++
1 file changed, 7 insertions(+)
create mode 100644 fastlane/metadata/android/pl/changelogs/89.txt
diff --git a/fastlane/metadata/android/pl/changelogs/89.txt b/fastlane/metadata/android/pl/changelogs/89.txt
new file mode 100644
index 00000000..edfc6f0a
--- /dev/null
+++ b/fastlane/metadata/android/pl/changelogs/89.txt
@@ -0,0 +1,7 @@
+Tusky v17.0
+
+- "Otwórz jako..." teraz jest także dostępne w menu na profilach kont gdy używane jest kilka kont
+- Login teraz jest obsługiwany w WebView w aplikacji
+- Wsparcie dla Androida 12
+- Wsparcie nowego API konfiguracji instancji Mastodon
+- i wiele innych małych poprawek i ulepszeń
From 90ff6544734bf49f00d89ea610346ef0bfc4672e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sveinn=20=C3=AD=20Felli?=
Date: Sun, 1 May 2022 17:40:32 +0000
Subject: [PATCH 064/104] Translated using Weblate (Icelandic)
Currently translated at 100.0% (16 of 16 strings)
Translation: Tusky/Tusky description
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky-app/is/
---
fastlane/metadata/android/is/changelogs/89.txt | 7 +++++++
1 file changed, 7 insertions(+)
create mode 100644 fastlane/metadata/android/is/changelogs/89.txt
diff --git a/fastlane/metadata/android/is/changelogs/89.txt b/fastlane/metadata/android/is/changelogs/89.txt
new file mode 100644
index 00000000..e379fc72
--- /dev/null
+++ b/fastlane/metadata/android/is/changelogs/89.txt
@@ -0,0 +1,7 @@
+Tusky útg.17.0
+
+- "Opna sem..." er núna líka á valmyndinni í notendasniðum þegar verið er að nota marga aðganga
+- Innskráning er núna meðhöndluð í WebView innan forritsins
+- Stuðningur við Android 12
+- Stuðningur við API-kerfisviðmót fyrir nýja uppsetningu Mastodon-tilvika
+- og mökkur af smærri endurbótum og lagfæringum
From a86391a7e928922ff726acb333de1212fd00c552 Mon Sep 17 00:00:00 2001
From: Bruno Miguel
Date: Sun, 1 May 2022 17:40:32 +0000
Subject: [PATCH 065/104] Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (16 of 16 strings)
Translation: Tusky/Tusky description
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky-app/pt_PT/
---
fastlane/metadata/android/pt-PT/changelogs/58.txt | 12 ++++++++++++
fastlane/metadata/android/pt-PT/changelogs/61.txt | 7 +++++++
fastlane/metadata/android/pt-PT/changelogs/67.txt | 9 +++++++++
fastlane/metadata/android/pt-PT/changelogs/68.txt | 3 +++
fastlane/metadata/android/pt-PT/changelogs/70.txt | 8 ++++++++
fastlane/metadata/android/pt-PT/changelogs/72.txt | 11 +++++++++++
fastlane/metadata/android/pt-PT/changelogs/74.txt | 8 ++++++++
fastlane/metadata/android/pt-PT/changelogs/77.txt | 10 ++++++++++
fastlane/metadata/android/pt-PT/changelogs/80.txt | 7 +++++++
fastlane/metadata/android/pt-PT/changelogs/82.txt | 5 +++++
fastlane/metadata/android/pt-PT/changelogs/83.txt | 3 +++
fastlane/metadata/android/pt-PT/changelogs/87.txt | 8 ++++++++
fastlane/metadata/android/pt-PT/changelogs/89.txt | 7 +++++++
fastlane/metadata/android/pt-PT/full_description.txt | 12 ++++++++++++
.../metadata/android/pt-PT/short_description.txt | 1 +
fastlane/metadata/android/pt-PT/title.txt | 1 +
16 files changed, 112 insertions(+)
create mode 100644 fastlane/metadata/android/pt-PT/changelogs/58.txt
create mode 100644 fastlane/metadata/android/pt-PT/changelogs/61.txt
create mode 100644 fastlane/metadata/android/pt-PT/changelogs/67.txt
create mode 100644 fastlane/metadata/android/pt-PT/changelogs/68.txt
create mode 100644 fastlane/metadata/android/pt-PT/changelogs/70.txt
create mode 100644 fastlane/metadata/android/pt-PT/changelogs/72.txt
create mode 100644 fastlane/metadata/android/pt-PT/changelogs/74.txt
create mode 100644 fastlane/metadata/android/pt-PT/changelogs/77.txt
create mode 100644 fastlane/metadata/android/pt-PT/changelogs/80.txt
create mode 100644 fastlane/metadata/android/pt-PT/changelogs/82.txt
create mode 100644 fastlane/metadata/android/pt-PT/changelogs/83.txt
create mode 100644 fastlane/metadata/android/pt-PT/changelogs/87.txt
create mode 100644 fastlane/metadata/android/pt-PT/changelogs/89.txt
create mode 100644 fastlane/metadata/android/pt-PT/full_description.txt
create mode 100644 fastlane/metadata/android/pt-PT/short_description.txt
create mode 100644 fastlane/metadata/android/pt-PT/title.txt
diff --git a/fastlane/metadata/android/pt-PT/changelogs/58.txt b/fastlane/metadata/android/pt-PT/changelogs/58.txt
new file mode 100644
index 00000000..24aad2f2
--- /dev/null
+++ b/fastlane/metadata/android/pt-PT/changelogs/58.txt
@@ -0,0 +1,12 @@
+Tusky v6.0
+
+- Os filtros de timeline passaram para "Preferências da Conta" e sincronizam com servidor
+- Pode ter uma hashtag personalizada como separador
+- Suporte a edição de listas
+- O editor sugere emojis personalizados ao escrever
+- Nova configuração: "seguir tema do sistema"
+- Melhor acessibilidade da timeline
+- O Tusky ignora notificações desconhecidas, deixando de crashar
+- Nova opção: trocar o idioma do sistema por outro
+- Novas traduções
+- Muitas outras melhorias e correções
diff --git a/fastlane/metadata/android/pt-PT/changelogs/61.txt b/fastlane/metadata/android/pt-PT/changelogs/61.txt
new file mode 100644
index 00000000..3cc7097e
--- /dev/null
+++ b/fastlane/metadata/android/pt-PT/changelogs/61.txt
@@ -0,0 +1,7 @@
+Tusky v7.0
+
+- Suporte para mostragem de votações, para votação e notificação de votações
+- Botões novos para filtrar notificações e excluí-las
+- Exclua e rascunhe os seus toots
+- Novo indicador que mostra, na foto de perfil, se uma conta é um bot (pode ser desativado nas preferências)
+- Novas traduções: Norueguês, Bokmål e Esloveno.
diff --git a/fastlane/metadata/android/pt-PT/changelogs/67.txt b/fastlane/metadata/android/pt-PT/changelogs/67.txt
new file mode 100644
index 00000000..5d3d3849
--- /dev/null
+++ b/fastlane/metadata/android/pt-PT/changelogs/67.txt
@@ -0,0 +1,9 @@
+Tusky v9.0
+
+- Agora pode criar votações no Tusky
+- Pesquisa melhorada
+- Nova opção em "Preferências da Conta": "Expandir sempre os toots com Aviso de Conteúdo"
+- Avatars em formato quadrado com cantos arredondados
+- Agora é possível denunciar utilizadores, mesmo que não tenham toots
+- O Tusky vai recusar a ligação através de ligações simples (não encriptadas) em Android 6+
+- Muitas outras pequenas melhorias e correções de bugs
diff --git a/fastlane/metadata/android/pt-PT/changelogs/68.txt b/fastlane/metadata/android/pt-PT/changelogs/68.txt
new file mode 100644
index 00000000..91792113
--- /dev/null
+++ b/fastlane/metadata/android/pt-PT/changelogs/68.txt
@@ -0,0 +1,3 @@
+Tusky v9.1
+
+Esta atualização garante compatibilidade com Mastodon 3 e melhora a performance e estabilidade.
diff --git a/fastlane/metadata/android/pt-PT/changelogs/70.txt b/fastlane/metadata/android/pt-PT/changelogs/70.txt
new file mode 100644
index 00000000..6ac528b1
--- /dev/null
+++ b/fastlane/metadata/android/pt-PT/changelogs/70.txt
@@ -0,0 +1,8 @@
+Tusky v10.0
+
+- Agora é possível adicionar toots aos favoritos e ver a lista de favoritos no Tusky.
+- Já pode agendar toots, no entanto é necessário agendá-los para pelo menos 5 minutos depois do momento da escrita.
+- Já pode adicionar listas na barra lateral do Tusky!
+- Já pode partilhar ficheiros de som nos teus toots!
+
+E muitas outras pequenas melhorias e correções de bugs!
diff --git a/fastlane/metadata/android/pt-PT/changelogs/72.txt b/fastlane/metadata/android/pt-PT/changelogs/72.txt
new file mode 100644
index 00000000..f42b0a8e
--- /dev/null
+++ b/fastlane/metadata/android/pt-PT/changelogs/72.txt
@@ -0,0 +1,11 @@
+Tusky v11.0
+
+- Notificações de seguidores pendentes quando a conta está trancada!
+- Novas funcionalidades nas "Preferências":
+ * desativação do gesto que alterna entre separadores
+ * diálogo de confirmação antes de dar boost
+ * mostragem da pré-visualização de links nas timelines
+- As conversas agora podem ser silenciadas
+- As votações passam a ser calculadas pelo número de votantes e não pelo número de votos
+- Várias correções relacionadas com a escrita de toots
+ - Traduções melhoradas
diff --git a/fastlane/metadata/android/pt-PT/changelogs/74.txt b/fastlane/metadata/android/pt-PT/changelogs/74.txt
new file mode 100644
index 00000000..9595cb1f
--- /dev/null
+++ b/fastlane/metadata/android/pt-PT/changelogs/74.txt
@@ -0,0 +1,8 @@
+Tusky v.12.0
+
+- Interface principal melhorada - passa a ser possível mover os separadores para baixo!
+- Ao silenciar um utilizador, pode também escolher se também pretende silenciar as notificações
+- Agora dá para seguir quantas hashtags quiser num único separador!
+- A exibição da descrição dos conteúdos multimédia foi melhorada para suportar descrições super longas
+
+Registo completo de alterações: https://github.com/tuskyapp/Tusky/releases
diff --git a/fastlane/metadata/android/pt-PT/changelogs/77.txt b/fastlane/metadata/android/pt-PT/changelogs/77.txt
new file mode 100644
index 00000000..01c57b33
--- /dev/null
+++ b/fastlane/metadata/android/pt-PT/changelogs/77.txt
@@ -0,0 +1,10 @@
+Tusky v13.0
+
+- Suporte para anotações em perfis (novidade do Mastodon 3.2.0)
+- Suporte para anúncios do(s) administrador(es) de instâncias (novidade do Mastodon 3.1.0)
+
+- O avatar da sua conta selecionada passa a ficar visível na barra de ferramentas principal (canto superior esquerdo)
+- Tocar no nome de utilizador na timeline abrirá o perfil em questão
+
+- Várias pequenas melhorias e correções
+- Traduções melhoradas
diff --git a/fastlane/metadata/android/pt-PT/changelogs/80.txt b/fastlane/metadata/android/pt-PT/changelogs/80.txt
new file mode 100644
index 00000000..866dc8e5
--- /dev/null
+++ b/fastlane/metadata/android/pt-PT/changelogs/80.txt
@@ -0,0 +1,7 @@
+Tusky v14.0
+
+- Receba notificações quando um utilizador que segue publicar um toot - basta clicar no ícone do sino (novidade do Mastodon 3.3.0)
+- O suporte para rascunhos do Tusky foi reescrito para ser mais rápido, simples e menos propenso a erros.
+- Foi adicionado uma funcionalidade de bem-estar, que permite limitar algumas funcionalidades no Tusky.
+- O Tusky já consegue animar os emojis personalizados
+Registo completo de alterações: https://github.com/tuskyapp/Tusky/releases
diff --git a/fastlane/metadata/android/pt-PT/changelogs/82.txt b/fastlane/metadata/android/pt-PT/changelogs/82.txt
new file mode 100644
index 00000000..0ee9e876
--- /dev/null
+++ b/fastlane/metadata/android/pt-PT/changelogs/82.txt
@@ -0,0 +1,5 @@
+Tusky v15.0
+
+- O menu principal passa a mostrar uma opção para ver os utilizadores que pediram para o seguir!
+- O relógio para agendar toots ganhou um aspeto mais consistente com o resto do Tusky
+Registo completo de alterações: https://github.com/tuskyapp/Tusky/releases
diff --git a/fastlane/metadata/android/pt-PT/changelogs/83.txt b/fastlane/metadata/android/pt-PT/changelogs/83.txt
new file mode 100644
index 00000000..4c71e64d
--- /dev/null
+++ b/fastlane/metadata/android/pt-PT/changelogs/83.txt
@@ -0,0 +1,3 @@
+Tusky v15.1
+
+O Tusky já não crasha ao adicionar descrição às imagens
diff --git a/fastlane/metadata/android/pt-PT/changelogs/87.txt b/fastlane/metadata/android/pt-PT/changelogs/87.txt
new file mode 100644
index 00000000..79a81157
--- /dev/null
+++ b/fastlane/metadata/android/pt-PT/changelogs/87.txt
@@ -0,0 +1,8 @@
+Tusky v16.0
+
+- O algoritmo de carregamento da timeline foi completamente reescrito para ser mais rápida, mais estável e mais fácil de manter.
+- O Tusky passa a poder animar emojis personalizados no formato APNG & WebP Animated.
+- Muitas correções de bugs
+- Suporte para Android 11
+- Novas traduções: gaélico escocês, galego, ucraniano
+- Traduções melhoradas
diff --git a/fastlane/metadata/android/pt-PT/changelogs/89.txt b/fastlane/metadata/android/pt-PT/changelogs/89.txt
new file mode 100644
index 00000000..28cebc1b
--- /dev/null
+++ b/fastlane/metadata/android/pt-PT/changelogs/89.txt
@@ -0,0 +1,7 @@
+Tusky v17.0
+
+- "Abrir como..." está disponível no menu de perfis de contas quando estão várias contas configuradas
+- O login passa a ser feito numa WebView dentro da aplicação
+- Suporte para Android 12
+- Suporte para a nova API de configuração de instâncias do Mastodon
+- Várias pequenas melhorias e correções
diff --git a/fastlane/metadata/android/pt-PT/full_description.txt b/fastlane/metadata/android/pt-PT/full_description.txt
new file mode 100644
index 00000000..52d67d81
--- /dev/null
+++ b/fastlane/metadata/android/pt-PT/full_description.txt
@@ -0,0 +1,12 @@
+Tusky é um cliente leve para Mastodon, um servidor de rede social de código aberto e livre.
+
+• Design Material
+• Maioria das APIs do Mastodon implementadas
+• Suporte para várias contas
+• Temas diurno e noturno, com possibilidade de troca automática de acordo com o horário
+• Rascunhos - Escreva os seus toots e guarde-os para mais tarde
+• Escolha entre estilos diferentes de emoji
+• Otimizado para todos os tamanhos de ecrã
+• Código totalmente aberto, sem dependências não-livres como Google Play Services
+
+Para ler mais sobre o Mastodon, visite o endereço https://joinmastodon.org/
diff --git a/fastlane/metadata/android/pt-PT/short_description.txt b/fastlane/metadata/android/pt-PT/short_description.txt
new file mode 100644
index 00000000..38a439d8
--- /dev/null
+++ b/fastlane/metadata/android/pt-PT/short_description.txt
@@ -0,0 +1 @@
+Um cliente multi-contas para a rede social Mastodon
diff --git a/fastlane/metadata/android/pt-PT/title.txt b/fastlane/metadata/android/pt-PT/title.txt
new file mode 100644
index 00000000..0238ffc0
--- /dev/null
+++ b/fastlane/metadata/android/pt-PT/title.txt
@@ -0,0 +1 @@
+Tusky
From 76accf0d0df051c3e4e1925494b26e49ed553125 Mon Sep 17 00:00:00 2001
From: ButterflyOfFire
Date: Tue, 3 May 2022 17:12:40 +0000
Subject: [PATCH 066/104] Translated using Weblate (French)
Currently translated at 100.0% (477 of 477 strings)
Translated using Weblate (Arabic)
Currently translated at 98.3% (469 of 477 strings)
Co-authored-by: ButterflyOfFire
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/ar/
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/fr/
Translation: Tusky/Tusky
---
app/src/main/res/values-ar/strings.xml | 6 ++++++
app/src/main/res/values-fr/strings.xml | 1 +
2 files changed, 7 insertions(+)
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index cb693670..d0160a60 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -551,4 +551,10 @@
180 يومًا
365 يومًا
تحرير منشور
+ حسابات جديدة
+ لِج
+ قام %s بإنشاء حساب
+ أحدهم أنشأ حسابا جديدا
+ منشورات تم تعديلها
+ قام %s بتعديل منشوره
\ No newline at end of file
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 37f8b984..dc30483b 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -547,4 +547,5 @@
un message avec lequel j\'ai interagi est modifié
Messages modifiés
Notifications quand un post avec lequel vous avez interagi est modifié
+ Se connecter
\ No newline at end of file
From 97e8a25a47aadd9da09d1bd22a6ae80dd7f424aa Mon Sep 17 00:00:00 2001
From: mondstern
Date: Tue, 3 May 2022 17:12:41 +0000
Subject: [PATCH 067/104] Translated using Weblate (German)
Currently translated at 99.1% (473 of 477 strings)
Co-authored-by: mondstern
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/de/
Translation: Tusky/Tusky
---
app/src/main/res/values-de/strings.xml | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 254a6116..b05ccc96 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -534,4 +534,5 @@
%s hat sich registriert
Jemand hat sich registriert
Benachrichtigungen, wenn Beiträge bearbeitet werden, mit denen du interagiert hast
+ Anmelden
\ No newline at end of file
From 95581fa0269b7872bc9fa23a9fe1ee2af814d04c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=E1=BB=93=20Nh=E1=BA=A5t=20Duy?=
Date: Tue, 3 May 2022 17:12:41 +0000
Subject: [PATCH 068/104] Translated using Weblate (Vietnamese)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Currently translated at 100.0% (477 of 477 strings)
Co-authored-by: Hồ Nhất Duy
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/
Translation: Tusky/Tusky
---
app/src/main/res/values-vi/strings.xml | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
index 1ec9ef80..5219775d 100644
--- a/app/src/main/res/values-vi/strings.xml
+++ b/app/src/main/res/values-vi/strings.xml
@@ -515,4 +515,5 @@
khi một tút mà tôi tương tác bị sửa
Sửa tút
Thông báo khi tút mà tôi tương tác bị sửa
+ Đăng nhập
\ No newline at end of file
From 58bd20edcae2c95c77755942d5aaf718271befa2 Mon Sep 17 00:00:00 2001
From: Makis Diakatos
Date: Tue, 3 May 2022 17:12:41 +0000
Subject: [PATCH 069/104] Translated using Weblate (Greek)
Currently translated at 22.0% (105 of 477 strings)
Co-authored-by: Makis Diakatos
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/el/
Translation: Tusky/Tusky
---
app/src/main/res/values-el/strings.xml | 102 +++++++++++++++++++++++++
1 file changed, 102 insertions(+)
diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml
index 4734fea9..85583a2b 100644
--- a/app/src/main/res/values-el/strings.xml
+++ b/app/src/main/res/values-el/strings.xml
@@ -3,4 +3,106 @@
Αυτό δεν μπορεί να είναι κενό.
Προέκυψε σφάλμα δικτύου! Παρακαλώ ελέγξτε τη σύνδεσή σας και προσπαθήστε ξανά!
Προέκυψε ένα σφάλμα.
+ Αποκλεισμένοι χρήστες
+ Ακύρωση αιτήματος ακολούθησης;
+ Διαγραφή αυτής της συζήτησης;
+ Δεν υπάρχουν αποτελέσματα
+ Επεργασία προφίλ
+ Ακολουθεί
+ Επαναφορά
+ ο/η %s σας ακολούθησε
+ Χρήστες σε σίγαση
+ Αποσύνδεση
+ Μην ακολουθείτε
+ Άρση σίγασης του %s
+ Διαγραφή και αναδιατύπωση
+ Επεξεργασία προφίλ
+ Κοινοποίηση
+ Άδειες
+ Ανοίξτε σε browser
+ Αιτήματα ακολούθησης
+ Προσθήκη σελιδοδείκτη
+ Περισσότερα
+ Σελιδοδείκτες
+ Σελιδοδείκτες
+ Ακόλουθοι
+ Άρση αποκλεισμού
+ Αγαπημένα
+ Η δημοσίευση είναι πολύ μεγάλη!
+ Πληκτρολόγιο emoji
+ Είστε σίγουροι ότι θέλετε να αποσυνδεθείτε από τον λογαριασμό %1$s;
+ Προσχέδια
+ Αγαπημένα
+ Απάντηση…
+ Απόρριψη
+ Αποκλεισμένοι χρήστες
+ Αφαίρεση προώθησης
+ Επεξεργασία
+ Σίγαση του %s
+ Αποκλεισμός
+ Αναίρεση
+ ο/η %s ζήτησε να σας ακολουθήσει
+ Απάντηση
+ Καρτέλες
+ ο/η %s προώθησε τη δημοσίευσή σας
+ στον/στην %s άρεσε η δημοσίευσή σας
+ Ακολουθήστε
+ Αναφορά
+ Σίγαση
+ Τα μουσικά αρχεία πρέπει να είναι μικρότερα από 40MB.
+ Αφαίρεση αγαπημένου
+ Αναφορά του/της %s
+ Προτιμήσεις Λογαριασμού
+ Προσθήκη καρτέλας
+ Αντιγραφή συνδέσμου
+ Αναζήτηση…
+ Αποδοχή
+ Εμφάνιση προωθήσεων
+ Προφίλ
+ Αιτήματα ακολούθησης
+ Αναζήτηση
+ Διαγραφή συζήτησης
+ Διαγραφή
+ ο/η %s μόλις δημοσίευσε
+ Αποθήκευση
+ Γρήγορη Απάντηση
+ Χρήστες σε σίγαση
+ Το αρχείο πρέπει να είναι μικρότερο από 8MB.
+ Απόκρυψη προωθήσεων
+ Προτιμήσεις
+ Σύνδεση
+ Ανακοινώσεις
+ Προσχέδια
+ ο/η %s έκανε εγγραφή
+ Προσπαθήστε ξανά
+ Διαγραφή αυτής της δημοσίευσης;
+ Άρση σίγασης
+ Αγαπημένο
+ Σύνδεσμοι
+ Κλείσιμο
+ Ειδοποιήσεις
+ Γράψτε
+ Σύνδεση με Mastodon
+ Επεξεργασία
+ Προώθηση
+ Άρση ακολούθησης αυτού του λογαριασμού;
+ Απόκρυψη ειδοποιήσεων
+ Αφαίρεση σελιδοδείκτη
+ Προειδοποίηση περιεχομένου
+ Σύνδεσμοι
+ Σύνδεση…
+ Προγραμματισμένες δημοσιεύσεις
+ Προγραμματισμός δημοσίευσης
+ Προγραμματισμένες δημοσιεύσεις
+ Δημοσιεύσεις
+ Καρφιτσωμένο
+ Ευαίσθητο περιεχόμενο
+ Κρυμμένα μέσα
+ ο/η %s το προώθησε
+ Με απαντήσεις
+ Δείτε περισσότερα
+ Δείτε λιγότερα
+ Κλικ για να δείτε
+ ο/η %s επεξεργάστηκε τη δημοσίευσή του/της
+ Διαγραφή και αναδιατύπωση αυτής της δημοσίευσης;
\ No newline at end of file
From 7b98adf1eed0f1b939c0f33afc159993a261a76e Mon Sep 17 00:00:00 2001
From: Bruno Miguel
Date: Tue, 3 May 2022 17:12:41 +0000
Subject: [PATCH 070/104] Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (477 of 477 strings)
Co-authored-by: Bruno Miguel
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/pt_PT/
Translation: Tusky/Tusky
---
app/src/main/res/values-pt-rPT/strings.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml
index e501ed2a..aa81160b 100644
--- a/app/src/main/res/values-pt-rPT/strings.xml
+++ b/app/src/main/res/values-pt-rPT/strings.xml
@@ -339,7 +339,7 @@
- Descrição para deficientes visuais
\n(até %d caracteres)
-
+
Descrever
Remover
From cb1f4898ba0d8815f0406e0bec7954faba0ffa30 Mon Sep 17 00:00:00 2001
From: Stefano Pigozzi
Date: Tue, 3 May 2022 17:12:41 +0000
Subject: [PATCH 071/104] Translated using Weblate (Italian)
Currently translated at 100.0% (477 of 477 strings)
Translated using Weblate (Italian)
Currently translated at 96.6% (461 of 477 strings)
Co-authored-by: Stefano Pigozzi
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/it/
Translation: Tusky/Tusky
---
app/src/main/res/values-it/strings.xml | 297 +++++++++++++------------
1 file changed, 152 insertions(+), 145 deletions(-)
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index b1a3e130..1a15d152 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -2,33 +2,33 @@
Si è verificato un errore.
Si è verificato un errore di rete! Per favore controlla la tua connessione e riprova!
- Questo non può esser vuoto.
- Inserito un dominio non valido
- Autenticazione fallita con quell\'istanza.
- Non riesco a trovare un browser web da usare.
+ Questo non può essere vuoto.
+ Inserito dominio non valido
+ Autenticazione con quell\'istanza fallita.
+ Nessun browser web utilizzabile trovato.
Si è verificato un errore di autenticazione non identificato.
- L\'autorizzazione è stata negata.
- Errore nell\'acquisizione del token di accesso.
- Lo stato è troppo lungo!
- La dimensione dei file immagine deve essere inferiore a 8 MB.
- La dimensione dei file video deve essere inferiore a 40 MB.
- Questo tipo di file non può essere caricato.
- Questo file non può essere aperto.
- Il permesso di lettura della scheda sd è richiesto.
- È richiesta l\'autorizzazione di archiviazione.
- Immagini e video non possono essere allegati allo stesso stato.
- Il caricamento non è riuscito.
- Errore nell\'invio del toot.
+ Autorizzazione negata.
+ Acquisizione token di accesso fallita.
+ Il post è troppo lungo!
+ Il file deve essere più piccolo di 8 MB.
+ I video devono essere più piccoli di 40 MB.
+ Quel tipo di file non può essere caricato.
+ Non è stato possibile aprire quel file.
+ È richiesto il permesso di leggere file.
+ È richiesto il permesso di salvare file.
+ Non è possibile allegare allo stesso post immagini e video.
+ Il caricamento è fallito.
+ Errore nell\'invio del post.
Home
Notifiche
Locale
Federata
- Messaggi Diretti
+ Messaggi diretti
Schede
- Toot
+ Conversazione
Post
Con risposte
- Fissati in alto
+ Fissati
Seguiti
Seguono
Preferiti
@@ -43,15 +43,15 @@
Contenuto sensibile
Media nascosto
Clicca per visualizzare
- Mostra di Più
- Mostra Meno
+ Mostra di più
+ Mostra di meno
Espandi
Riduci
- Qui non c\'è niente.
- Qui non c\'è niente. Trascina verso il basso per aggiornare!
- %s ha boostato il tuo toot
- %s ha messo il tuo toot nei preferiti
- %s ti segue
+ Qui non c\'è nulla.
+ Qui non c\'è nulla. Trascina verso il basso per aggiornare!
+ %s ha boostato il tuo post
+ %s ha messo il tuo post nei preferiti
+ %s ti ha seguito
Segnala @%s
Commenti aggiuntivi?
Risposta veloce
@@ -79,7 +79,7 @@
Chiudi
Profilo
Preferenze
- Preferenze Account
+ Preferenze account
Preferiti
Utenti silenziati
Utenti bloccati
@@ -102,14 +102,14 @@
Rifiuta
Cerca
Bozze
- Visibilità dei toot
- Avviso per il contenuto
+ Visibilità dei post
+ Avviso di contenuto sensibile
Tastiera emoji
- Aggiungi Scheda
+ Aggiungi scheda
Collegamenti
Menzioni
Hashtag
- Apri autore del boost
+ Vai all\'autore del boost
Mostra boost
Mostra preferiti
Hashtag
@@ -117,11 +117,11 @@
Collegamenti
Apri media #%d
Scaricando %1$s
- Copia il link
+ Copia link
Apri come %s
Condividi come …
- Condividi URL del toot su…
- Condividi toot su…
+ Condividi URL del post su…
+ Condividi post su…
Condividi media su…
Inviato!
Utente sbloccato
@@ -132,7 +132,7 @@
Cosa succede?
Avviso di contenuto sensibile
Mostra nome
- Bio
+ Biografia
Cerca…
Nessun risultato
Rispondi…
@@ -152,22 +152,22 @@
Scarica
Revocare la richiesta di seguire?
Smettere di seguire questo account?
- Eliminare questo toot?
+ Eliminare questo post\?
Pubblico: visibile sulla timeline pubblica
- Non Elencato: non visibile sulla timeline pubblica e locale
- Solo Follower: visibile solo dai tuoi follower
+ Non in elenco: non visibile sulla timeline pubblica e locale
+ Solo follower: visibile solo dai tuoi follower
Diretto: visibile solo agli utenti menzionati
- Modifica Notifiche
+ Notifiche
Notifiche
Allarmi
Notifica con suoneria
Notifica con vibrazione
Notifica con luce
Notificami quando
- sono stato menzionato
- sono stato seguito
- i miei post sono boostati
- i miei post sono messi nei preferiti
+ vengo menzionato
+ vengo seguito
+ i miei post vengono boostati
+ i miei post vengono messi nei preferiti
Aspetto
Tema dell\'app
Timeline
@@ -176,10 +176,10 @@
Chiaro
Nero
Automatico al tramonto
- Usa Tema di Sistema
+ Usa tema di sistema
Browser
- Usa Tab Personalizzate di Chrome
- Nascondi il pulsante componi mentre scorri
+ Usa Custom Tabs di Chrome
+ Nascondi il pulsante Componi mentre scorri
Lingua
Filtraggio della timeline
Schede
@@ -193,25 +193,25 @@
Porta proxy HTTP
Privacy di default dei post
Segna sempre media come contenuto sensibile
- Pubblicando (sincronizzato con il server)
+ Pubblicazione (sincronizzato con il server)
Sincronizzazione delle impostazioni fallita
Pubblico
- Non elencato
- Solo per chi ti segue
- Dimensione del testo degli stati
+ Non in elenco
+ Solo follower
+ Dimensione del testo dei post
Piccolissimo
Piccolo
Normale
Grande
Grandissimo
- Nuove Menzioni
- Notifiche quando qualcuno ti menziona
- Nuove persone che ti seguono
- Notifiche su nuove persone che ti seguono
+ Nuove menzioni
+ Notifiche di quando vieni menzionato da qualcuno
+ Nuovi follower
+ Notifiche su nuovi follower
Boost
- Notifiche quando i tuoi toot vengono boostati
+ Notifiche sui tuoi post che vengono boostati
Preferiti
- Notifiche quando i tuoi toot vengono segnati come preferiti
+ Notifiche sui tuoi post che vengono segnati come preferiti
%s ti ha menzionato
%1$s, %2$s, %3$s e %4$d altri
%1$s, %2$s e %3$s
@@ -234,14 +234,14 @@
-->
Sito web del progetto:\n
https://tusky.app
- Segnala problemi & richiedi funzionalità:\n
- https://github.com/tuskyapp/Tusky/issues
+ Segnala problemi e richiedi funzionalità:
+\n https://github.com/tuskyapp/Tusky/issues
Profilo di Tusky
- Condividi contenuto del toot
- Condividi link al toot
+ Condividi contenuto del post
+ Condividi link al post
Immagini
Video
- In attesa di approvazione
+ Richiesta inviata
in %d a
in %dg
@@ -250,14 +250,14 @@
in %ds
%da
%dg
- %d o
- %d min
- %d s
- Seguono te
- Mostra sempre tutto il contenuto sensibile
+ %do
+ %dmin
+ %ds
+ Ti segue
+ Mostra sempre tutti i contenuti sensibili
Media
Rispondendo a @%s
- carica di più
+ carica altri
Timeline pubbliche
Conversazioni
Aggiungi filtro
@@ -265,7 +265,7 @@
Rimuovi
Aggiorna
Frase da filtrare
- Aggiungi Account
+ Aggiungi account
Aggiungi un nuovo Account Mastodon
Liste
Liste
@@ -288,29 +288,29 @@
Inserisci descrizione
Rimuovi
Blocca account
- Richiede la tua approvazione manuale di chi ti segue
+ Richiedi una tua approvazione manuale per seguirti
Salvare bozza?
- Inviando il Toot…
+ Inviando il post…
Errore durante l\'invio
- Invio Toot
+ Invio post
Invio annullato
- Una copia del toot è stata salvata nelle tue bozze
+ Una copia del post è stata salvata nelle tue bozze
Componi
La tua istanza %s non ha nessuna emoji personalizzata
- Stile di emoji
- Predefiniti del sistema
+ Stile delle emoji
+ Predefinite del sistema
Dovrai prima scaricare questo pacchetto di emoji
- Eseguendo una ricerca…
- Espandi/Riduci tutti gli stati
- Apri toot
+ Ricerca in corso…
+ Espandi/riduci tutti i post
+ Apri post
Riavvio dell\'app richiesto
Devi riavviare Tusky per applicare queste modifiche
Più tardi
Riavvia
Le emoji predefinite del tuo dispositivo
- Le emoji Blob conosciute da Android 4.4-7.1
+ Le emoji Blob di Android 4.4-7.1
Le emoji standard di Mastodon
- Scaricamento fallito
+ Download fallito
Bot
%1$s si è spostato su:
Boost con la visibilità del post di origine
@@ -323,63 +323,60 @@
aggiungi dati
Etichetta
Contenuto
- Usa tempo assoluto
+ Usa ora assoluta
Il profilo dell\'utente mostrato qui sotto potrebbe essere incompleto. Premi per aprire il profilo completo nel browser.
- Non fissare
+ Smetti di fissare
Fissa
- - %1$s Mi piace
- - %1$s Mi piace
+ - %1$s Preferito
+ - %1$s Preferiti
- <b>%s</b> Boost
- <b>%s</b> Boost
Boostato da
- Preferito da
+ Aggiunto ai preferiti da
%1$s
%1$s e %2$s
%1$s, %2$s ed altri %3$d
- - limite massimo di %1$d tab raggiunto
- - limite massimo di %1$d tab raggiunto
+ - limite massimo di %1$d scheda raggiunto
+ - limite massimo di %1$d schede raggiunto
- Media: %s
-
+ Media: %s
Contenuto sensibile: %s
Nessuna descrizione
Ribloggato
- Apprezzato
-
+ Messo nei preferiti
Pubblico
- Non elencato
-
- Seguaci
+ Non in elenco
+ Solo follower
Diretti
Nome della lista
Scarica media
Scaricando media
- Componi Toot
+ Componi post
Hashtag senza #
Componi
- Pulisci
+ Svuota
Filtra
Applica
- Mostra indicatore per bot
+ Mostra indicatore bot
Sei sicuro di voler permanentemente eliminare tutte le tue notifiche\?
Cancella e riscrivi
- Cancellare e riscrivere questo toot\?
+ Cancellare e riscrivere questo post\?
- %s voto
- %s voti
- termina alle %s
- terminato
+ si conclude alle %s
+ concluso
Vota
Domini nascosti
Domini nascosti
@@ -387,37 +384,37 @@
%s mostrati
Sei sicuro di voler bloccare tutto %s\? Non vedrai nessun contenuto da quel dominio in nessuna timeline pubblica o nelle tue notifiche. I tuoi seguaci che stanno in quel dominio saranno rimossi.
Nascondi l\'intero dominio
- Le votazioni sono finite
- Mostra le animazioni delle GIF negli avatar
+ dei sondaggi si sono conclusi
+ Riproduci animazioni avatar
Votazioni
- Notifiche sulle votazioni che sono concluse
+ Notifiche sulle votazioni che si sono concluse
Parola intera
Quando la parola chiave o la frase sono composte da soli caratteri alfanumerici, sarà applicata solo se corrisponde alla parola completa
- Insieme di emoji di Google
+ Set di emoji di Google
Segnalibri
Segnalibro
Modifica
Segnalibri
Aggiungi sondaggio
- Fatto con Tusky
- Espandi sempre i toot segnalati come contenuto sensibile
- Messo nei segalibri
+ Fatto usando Tusky
+ Espandi sempre i post segnalati come contenuto sensibile
+ Messo nei segnalibri
Sondaggio con scelte: %1$s, %2$s, %3$s, %4$s; %5$s
Scegli lista
Lista
Azioni per l\'immagine %s
- Un sondaggio che hai votato è terminato
- Un sondaggio che hai creato è terminato
+ Un sondaggio che hai votato si è concluso
+ Un sondaggio che hai creato si è concluso
- - %d giorno rimasti
+ - %d giorno rimasto
- %d giorni rimasti
- - %d ora rimasti
+ - %d ora rimasta
- %d ore rimasti
- - %d minuto rimasti
+ - %d minuto rimasto
- %d minuti rimasti
@@ -427,12 +424,12 @@
Continua
Indietro
Fatto
- Inviato con successo @%s
+ Segnalato @%s con successo
Altri commenti
Inoltra a %s
- Errore durante l\'invio
- Errore durante lo scaricamento degli aggiornamenti
- La segnalazione sarà inviata al moderatore del tuo server. Puoi spiegare perchè vuoi segnalare questo utente qui sotto:
+ Segnalazione fallita
+ Scaricamento dei post fallito
+ La segnalazione sarà inviata al moderatore del tuo server. Puoi spiegare perchè stai segnalando questo utente qui sotto:
L\'utente è su un altro server. Mandare una copia della segnalazione anche lì\?
Utenti
Errore durante la ricerca
@@ -450,10 +447,10 @@
Scelta %d
Modifica
Errore nella ricerca del post %s
- Toot programmati
- Toot programmati
- Programma un toot
- RIpristina
+ Post programmati
+ Post programmati
+ Programma un post
+ Ripristina
%1$s • %2$s
Non hai bozze.
@@ -464,66 +461,66 @@
Aggiungi hashtag
Silenziare @%s\?
Bloccare @%s\?
- Non silenziare più %s
- Smetti di silenziare conversazione
+ Smetti di silenziare %s
+ Smetti di silenziare la conversazione
Silenzia conversazione
%s ha chiesto di seguirti
- La dimensione dei file audio deve essere inferiore a 40 MB.
+ I file audio devono essere più piccoli di 40 MB.
Smetti di silenziare %s
Richieste di seguirti
Salvato!
La tua nota privata su questo account
Nascondi il titolo della barra degli strumenti in alto
- Mostra la finestra di dialogo di conferma prima del boosting
+ Mostra la finestra di conferma prima di boostare
Mostra le anteprime dei collegamenti nelle timelines
- Mastodon ha un intervallo minimo di programmazione di 5 minuti.
+ Mastodon ha un intervallo di programmazione minimo di 5 minuti.
Non ci sono annunci.
- Non hai stati pianificati.
+ Non hai post pianificati.
Abilita il gesto di scorrimento per passare da una scheda all\'altra
Notifiche sulle richieste di essere seguiti
- Parte inferiore
+ In fondo
In cima
- Posizione di navigazione principale
- Mostra sfumature colorate per i media nascosti
+ Posizione barra di navigazione principale
+ Mostra gradienti colorati per i media nascosti
Nascondi notifiche
Disattiva le notifiche da %s
Riattiva le notifiche da %s
Annunci
- Richieste di seguirti
+ mi viene richiesto di seguirmi
Nascondi statistiche quantitative sui profili
Nascondi le statistiche quantitative sui post
- Limita le notifiche della timeline
- Revisiona le notifiche
+ Limita le notifiche dalla timeline
+ Rivedi le notifiche
Benessere
- Notifiche di quando qualcuno a cui sei iscritto ha pubblicato un nuovo toot
- Nuovi toots
- qualcuno a cui sono iscritto ha pubblicato un nuovo toot
- %s appena pubblicato
+ Notifiche di nuovi post di qualcuno a cui sei iscritto
+ Nuovi post
+ qualcuno che seguo ha pubblicato un nuovo post
+ %s ha appena pubblicato
- Non puoi caricare più di %1$d allegato multimediale.
- Non puoi caricare più di %1$d allegati multimediali.
- Il toot a cui hai scritto una risposta è stato rimosso
- Bozza cancellata
- L\'invio di questo toot è fallito!
+ Il post a cui hai scritto una risposta è stato rimosso
+ Bozza eliminata
+ L\'invio di questo post è fallito!
Sei sicuro di voler cancellare la lista %s\?
Indefinita
Durata
Allegati
Audio
- Mostra le animazioni delle emojis personalizzate
+ Riproduci emoji animate
Iscriviti
Rimuovere questa conversazione\?
- Errore nel recuperare le informazioni sulla risposta
+ Errore nel recupero delle informazioni sulla risposta
Disiscriviti
- Rimuovi conversazione
+ Elimina conversazione
Alcune informazioni che potrebbero influenzare il tuo benessere mentale saranno nascoste. Questo include:
\n
\n - Notifiche riguardo a Preferiti/Boost/Following
-\n - Conteggio dei Preferiti/Boost nei toot
-\n - Statistiche riguardo a Preferiti e Post nei profili
+\n - Conteggio dei Preferiti/Boost nei post
+\n - Statistiche riguardo a Preferiti/Post nei profili
\n
-\n Le notifiche push non saranno influenzate, ma puoi rivedere le tue impostazioni delle notifiche manualmente.
+\n Le notifiche push non saranno influenzate, ma puoi modificare le tue impostazioni delle notifiche manualmente.
Rimuovi segnalibro
Chiedi conferma prima di boostare
14 giorni
@@ -532,5 +529,15 @@
90 giorni
180 giorni
365 giorni
- Anche se il tuo account non è bloccato, lo staff di %1$s ha pensato che potresti voler controllare queste richieste di following da parte questi account manualmente.
+ Anche se il tuo account non è bloccato, lo staff di %1$s ha pensato che potresti voler verificare le richieste di seguirti da parte questi account manualmente.
+ %s si è registrato
+ qualcuno si è registrato
+ Login
+ %s ha modificato il suo post
+ un post con cui ho interagito è stato modificato
+ Componi post
+ Registrazioni
+ Notifiche di quando qualcuno si è registrato
+ Modifiche ai post
+ Notifiche di quando i post con cui hai interagito vengono modificati
\ No newline at end of file
From db81ede04a7309962b1328119448612df6d34c2c Mon Sep 17 00:00:00 2001
From: Konrad Pozniak
Date: Wed, 4 May 2022 18:40:29 +0200
Subject: [PATCH 072/104] fix login webview title color with light theme
(#2497)
---
.../tusky/components/login/LoginWebViewActivity.kt | 4 ++--
.../layout/{login_webview.xml => activity_login_webview.xml} | 3 +--
2 files changed, 3 insertions(+), 4 deletions(-)
rename app/src/main/res/layout/{login_webview.xml => activity_login_webview.xml} (88%)
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/login/LoginWebViewActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/login/LoginWebViewActivity.kt
index a32a164c..2ed38720 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/login/LoginWebViewActivity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/login/LoginWebViewActivity.kt
@@ -20,7 +20,7 @@ import androidx.core.net.toUri
import com.keylesspalace.tusky.BaseActivity
import com.keylesspalace.tusky.BuildConfig
import com.keylesspalace.tusky.R
-import com.keylesspalace.tusky.databinding.LoginWebviewBinding
+import com.keylesspalace.tusky.databinding.ActivityLoginWebviewBinding
import com.keylesspalace.tusky.di.Injectable
import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.viewBinding
@@ -78,7 +78,7 @@ sealed class LoginResult : Parcelable {
/** Activity to do Oauth process using WebView. */
class LoginWebViewActivity : BaseActivity(), Injectable {
- private val binding by viewBinding(LoginWebviewBinding::inflate)
+ private val binding by viewBinding(ActivityLoginWebviewBinding::inflate)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
diff --git a/app/src/main/res/layout/login_webview.xml b/app/src/main/res/layout/activity_login_webview.xml
similarity index 88%
rename from app/src/main/res/layout/login_webview.xml
rename to app/src/main/res/layout/activity_login_webview.xml
index 67d47d6f..1630239e 100644
--- a/app/src/main/res/layout/login_webview.xml
+++ b/app/src/main/res/layout/activity_login_webview.xml
@@ -11,8 +11,7 @@
+ android:layout_height="wrap_content" />
From b4eda5ea65e71419233b6a350ade04baf4c95e1f Mon Sep 17 00:00:00 2001
From: Levi Bard
Date: Thu, 5 May 2022 18:27:05 +0200
Subject: [PATCH 073/104] Unbreak link previews in timelines (#2506)
---
.../timeline/TimelineTypeMappers.kt | 12 ++++---
.../keylesspalace/tusky/db/AppDatabase.java | 9 +++++-
.../com/keylesspalace/tusky/db/TimelineDao.kt | 2 +-
.../tusky/db/TimelineStatusEntity.kt | 3 +-
.../com/keylesspalace/tusky/di/AppModule.kt | 2 +-
.../keylesspalace/tusky/db/TimelineDaoTest.kt | 32 +++++++++++++++++--
6 files changed, 50 insertions(+), 10 deletions(-)
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineTypeMappers.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineTypeMappers.kt
index 6ec95423..12422a95 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineTypeMappers.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineTypeMappers.kt
@@ -21,6 +21,7 @@ import com.keylesspalace.tusky.db.TimelineAccountEntity
import com.keylesspalace.tusky.db.TimelineStatusEntity
import com.keylesspalace.tusky.db.TimelineStatusWithAccount
import com.keylesspalace.tusky.entity.Attachment
+import com.keylesspalace.tusky.entity.Card
import com.keylesspalace.tusky.entity.Emoji
import com.keylesspalace.tusky.entity.HashTag
import com.keylesspalace.tusky.entity.Poll
@@ -96,7 +97,8 @@ fun Placeholder.toEntity(timelineUserId: Long): TimelineStatusEntity {
expanded = loading,
contentCollapsed = false,
contentShowing = false,
- pinned = false
+ pinned = false,
+ card = null,
)
}
@@ -136,7 +138,8 @@ fun Status.toEntity(
expanded = expanded,
contentShowing = contentShowing,
contentCollapsed = contentCollapsed,
- pinned = actionableStatus.pinned == true
+ pinned = actionableStatus.pinned == true,
+ card = actionableStatus.card?.let(gson::toJson),
)
}
@@ -151,6 +154,7 @@ fun TimelineStatusWithAccount.toViewData(gson: Gson): StatusViewData {
val application = gson.fromJson(status.application, Status.Application::class.java)
val emojis: List = gson.fromJson(status.emojis, emojisListType) ?: emptyList()
val poll: Poll? = gson.fromJson(status.poll, Poll::class.java)
+ val card: Card? = gson.fromJson(status.card, Card::class.java)
val reblog = status.reblogServerId?.let { id ->
Status(
@@ -178,7 +182,7 @@ fun TimelineStatusWithAccount.toViewData(gson: Gson): StatusViewData {
pinned = false,
muted = status.muted,
poll = poll,
- card = null
+ card = card,
)
}
val status = if (reblog != null) {
@@ -235,7 +239,7 @@ fun TimelineStatusWithAccount.toViewData(gson: Gson): StatusViewData {
pinned = status.pinned,
muted = status.muted,
poll = poll,
- card = null
+ card = card,
)
}
return StatusViewData.Concrete(
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 293db65e..d5f023e5 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 = 34)
+ }, version = 35)
public abstract class AppDatabase extends RoomDatabase {
public abstract AccountDao accountDao();
@@ -534,4 +534,11 @@ public abstract class AppDatabase extends RoomDatabase {
database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `notificationsUpdates` INTEGER NOT NULL DEFAULT 1");
}
};
+
+ public static final Migration MIGRATION_34_35 = new Migration(34, 35) {
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ database.execSQL("ALTER TABLE `TimelineStatusEntity` ADD COLUMN `card` TEXT");
+ }
+ };
}
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 dd59f2a3..2c6ef188 100644
--- a/app/src/main/java/com/keylesspalace/tusky/db/TimelineDao.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/db/TimelineDao.kt
@@ -36,7 +36,7 @@ SELECT s.serverId, s.url, s.timelineUserId,
s.authorServerId, s.inReplyToId, s.inReplyToAccountId, s.createdAt,
s.emojis, s.reblogsCount, s.favouritesCount, s.reblogged, s.favourited, s.bookmarked, s.sensitive,
s.spoilerText, s.visibility, s.mentions, s.tags, s.application, s.reblogServerId,s.reblogAccountId,
-s.content, s.attachments, s.poll, s.muted, s.expanded, s.contentShowing, s.contentCollapsed, s.pinned,
+s.content, s.attachments, s.poll, s.card, s.muted, s.expanded, s.contentShowing, s.contentCollapsed, s.pinned,
a.serverId as 'a_serverId', a.timelineUserId as 'a_timelineUserId',
a.localUsername as 'a_localUsername', a.username as 'a_username',
a.displayName as 'a_displayName', a.url as 'a_url', a.avatar as 'a_avatar',
diff --git a/app/src/main/java/com/keylesspalace/tusky/db/TimelineStatusEntity.kt b/app/src/main/java/com/keylesspalace/tusky/db/TimelineStatusEntity.kt
index 41b122c3..2c4d45c3 100644
--- a/app/src/main/java/com/keylesspalace/tusky/db/TimelineStatusEntity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/db/TimelineStatusEntity.kt
@@ -78,7 +78,8 @@ data class TimelineStatusEntity(
val expanded: Boolean, // used as the "loading" attribute when this TimelineStatusEntity is a placeholder
val contentCollapsed: Boolean,
val contentShowing: Boolean,
- val pinned: Boolean
+ val pinned: Boolean,
+ val card: String?,
)
@Entity(
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 c92d52ef..0861e9cf 100644
--- a/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt
@@ -63,7 +63,7 @@ class AppModule {
AppDatabase.Migration25_26(appContext.getExternalFilesDir("Tusky")),
AppDatabase.MIGRATION_26_27, AppDatabase.MIGRATION_27_28, AppDatabase.MIGRATION_28_29,
AppDatabase.MIGRATION_29_30, AppDatabase.MIGRATION_30_31, AppDatabase.MIGRATION_31_32,
- AppDatabase.MIGRATION_32_33, AppDatabase.MIGRATION_33_34
+ AppDatabase.MIGRATION_32_33, AppDatabase.MIGRATION_33_34, AppDatabase.MIGRATION_34_35,
)
.build()
}
diff --git a/app/src/test/java/com/keylesspalace/tusky/db/TimelineDaoTest.kt b/app/src/test/java/com/keylesspalace/tusky/db/TimelineDaoTest.kt
index 889e5f98..ed652418 100644
--- a/app/src/test/java/com/keylesspalace/tusky/db/TimelineDaoTest.kt
+++ b/app/src/test/java/com/keylesspalace/tusky/db/TimelineDaoTest.kt
@@ -369,13 +369,36 @@ class TimelineDaoTest {
assertEquals("99", timelineDao.getTopPlaceholderId(1))
}
+ @Test
+ fun `preview card survives roundtrip`() = runBlocking {
+ val setOne = makeStatus(statusId = 3, cardUrl = "https://foo.bar")
+
+ for ((status, author, reblogger) in listOf(setOne)) {
+ timelineDao.insertAccount(author)
+ reblogger?.let {
+ timelineDao.insertAccount(it)
+ }
+ timelineDao.insertStatus(status)
+ }
+
+ val pagingSource = timelineDao.getStatuses(setOne.first.timelineUserId)
+
+ val loadResult = pagingSource.load(PagingSource.LoadParams.Refresh(null, 2, false))
+
+ val loadedStatuses = (loadResult as PagingSource.LoadResult.Page).data
+
+ assertEquals(1, loadedStatuses.size)
+ assertStatuses(listOf(setOne), loadedStatuses)
+ }
+
private fun makeStatus(
accountId: Long = 1,
statusId: Long = 10,
reblog: Boolean = false,
createdAt: Long = statusId,
authorServerId: String = "20",
- domain: String = "mastodon.example"
+ domain: String = "mastodon.example",
+ cardUrl: String? = null,
): Triple {
val author = TimelineAccountEntity(
serverId = authorServerId,
@@ -403,6 +426,10 @@ class TimelineDaoTest {
)
} else null
+ val card = when (cardUrl) {
+ null -> null
+ else -> "{ url: \"$cardUrl\" }"
+ }
val even = accountId % 2 == 0L
val status = TimelineStatusEntity(
serverId = statusId.toString(),
@@ -433,7 +460,8 @@ class TimelineDaoTest {
expanded = false,
contentCollapsed = false,
contentShowing = true,
- pinned = false
+ pinned = false,
+ card = card,
)
return Triple(status, author, reblogAuthor)
}
From beaed6b8751e547564e5e40c6cb8f93c7a7df88f Mon Sep 17 00:00:00 2001
From: Konrad Pozniak
Date: Mon, 9 May 2022 19:39:43 +0200
Subject: [PATCH 074/104] Fix crash when saving redrafted media to drafts
(#2502)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* fix crash when saving draft from redraft
* fix crash when saving draft from redraft
* replace ... with …
---
.../components/compose/ComposeActivity.kt | 15 ++++-
.../components/compose/ComposeViewModel.kt | 49 ++++++++-------
.../tusky/components/drafts/DraftHelper.kt | 63 ++++++++++++++-----
app/src/main/res/values/strings.xml | 1 +
4 files changed, 87 insertions(+), 41 deletions(-)
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt
index 32162614..a6e8d677 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt
@@ -967,8 +967,19 @@ class ComposeActivity :
}
private fun saveDraftAndFinish(contentText: String, contentWarning: String) {
- viewModel.saveDraft(contentText, contentWarning)
- finishWithoutSlideOutAnimation()
+ lifecycleScope.launch {
+ val dialog = if (viewModel.shouldShowSaveDraftDialog()) {
+ ProgressDialog.show(
+ this@ComposeActivity, null,
+ getString(R.string.saving_draft), true, false
+ )
+ } else {
+ null
+ }
+ viewModel.saveDraft(contentText, contentWarning)
+ dialog?.cancel()
+ finishWithoutSlideOutAnimation()
+ }
}
override fun search(token: String): List {
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt
index 7b180532..abf2ff42 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt
@@ -220,31 +220,36 @@ class ComposeViewModel @Inject constructor(
}
}
- fun saveDraft(content: String, contentWarning: String) {
- viewModelScope.launch {
- val mediaUris: MutableList = mutableListOf()
- val mediaDescriptions: MutableList = mutableListOf()
- media.value.forEach { item ->
- mediaUris.add(item.uri.toString())
- mediaDescriptions.add(item.description)
- }
-
- draftHelper.saveDraft(
- draftId = draftId,
- accountId = accountManager.activeAccount?.id!!,
- inReplyToId = inReplyToId,
- content = content,
- contentWarning = contentWarning,
- sensitive = markMediaAsSensitive.value!!,
- visibility = statusVisibility.value!!,
- mediaUris = mediaUris,
- mediaDescriptions = mediaDescriptions,
- poll = poll.value,
- failedToSend = false
- )
+ fun shouldShowSaveDraftDialog(): Boolean {
+ // if any of the media files need to be downloaded first it could take a while, so show a loading dialog
+ return media.value.any { mediaValue ->
+ mediaValue.uri.scheme == "https"
}
}
+ suspend fun saveDraft(content: String, contentWarning: String) {
+ val mediaUris: MutableList = mutableListOf()
+ val mediaDescriptions: MutableList = mutableListOf()
+ media.value.forEach { item ->
+ mediaUris.add(item.uri.toString())
+ mediaDescriptions.add(item.description)
+ }
+
+ draftHelper.saveDraft(
+ draftId = draftId,
+ accountId = accountManager.activeAccount?.id!!,
+ inReplyToId = inReplyToId,
+ content = content,
+ contentWarning = contentWarning,
+ sensitive = markMediaAsSensitive.value!!,
+ visibility = statusVisibility.value!!,
+ mediaUris = mediaUris,
+ mediaDescriptions = mediaDescriptions,
+ poll = poll.value,
+ failedToSend = false
+ )
+ }
+
/**
* Send status to the server.
* Uses current state plus provided arguments.
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftHelper.kt b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftHelper.kt
index 7511dc3c..a6cd3fcd 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftHelper.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftHelper.kt
@@ -30,7 +30,12 @@ import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.util.IOUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import okio.buffer
+import okio.sink
import java.io.File
+import java.io.IOException
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
@@ -38,6 +43,7 @@ import javax.inject.Inject
class DraftHelper @Inject constructor(
val context: Context,
+ val okHttpClient: OkHttpClient,
db: AppDatabase
) {
@@ -71,11 +77,11 @@ class DraftHelper @Inject constructor(
val uris = mediaUris.map { uriString ->
uriString.toUri()
- }.map { uri ->
- if (uri.isNotInFolder(draftDirectory)) {
- uri.copyToFolder(draftDirectory)
- } else {
+ }.mapNotNull { uri ->
+ if (uri.isInFolder(draftDirectory)) {
uri
+ } else {
+ uri.copyToFolder(draftDirectory)
}
}
@@ -114,6 +120,7 @@ class DraftHelper @Inject constructor(
)
draftDao.insertOrReplace(draft)
+ Log.d("DraftHelper", "saved draft to db")
}
suspend fun deleteDraftAndAttachments(draftId: Int) {
@@ -133,33 +140,55 @@ class DraftHelper @Inject constructor(
}
}
- suspend fun deleteAttachments(draft: DraftEntity) {
- withContext(Dispatchers.IO) {
- draft.attachments.forEach { attachment ->
- if (context.contentResolver.delete(attachment.uri, null, null) == 0) {
- Log.e("DraftHelper", "Did not delete file ${attachment.uriString}")
- }
+ suspend fun deleteAttachments(draft: DraftEntity) = withContext(Dispatchers.IO) {
+ draft.attachments.forEach { attachment ->
+ if (context.contentResolver.delete(attachment.uri, null, null) == 0) {
+ Log.e("DraftHelper", "Did not delete file ${attachment.uriString}")
}
}
}
- private fun Uri.isNotInFolder(folder: File): Boolean {
+ private fun Uri.isInFolder(folder: File): Boolean {
val filePath = path ?: return true
return File(filePath).parentFile == folder
}
- private fun Uri.copyToFolder(folder: File): Uri {
+ private fun Uri.copyToFolder(folder: File): Uri? {
val contentResolver = context.contentResolver
-
val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
- val mimeType = contentResolver.getType(this)
- val map = MimeTypeMap.getSingleton()
- val fileExtension = map.getExtensionFromMimeType(mimeType)
+ val fileExtension = if (scheme == "https") {
+ lastPathSegment?.substringAfterLast('.', "tmp")
+ } else {
+ val mimeType = contentResolver.getType(this)
+ val map = MimeTypeMap.getSingleton()
+ map.getExtensionFromMimeType(mimeType)
+ }
val filename = String.format("Tusky_Draft_Media_%s.%s", timeStamp, fileExtension)
val file = File(folder, filename)
- IOUtils.copyToFile(contentResolver, this, file)
+
+ if (scheme == "https") {
+ // saving redrafted media
+ try {
+ val request = Request.Builder().url(toString()).build()
+
+ val response = okHttpClient.newCall(request).execute()
+
+ val sink = file.sink().buffer()
+
+ response.body?.source()?.use { input ->
+ sink.use { output ->
+ output.writeAll(input)
+ }
+ }
+ } catch (ex: IOException) {
+ Log.w("DraftHelper", "failed to save media", ex)
+ return null
+ }
+ } else {
+ IOUtils.copyToFile(contentResolver, this, file)
+ }
return FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileprovider", file)
}
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index fc1ed743..d8ce8394 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -640,5 +640,6 @@
Unsubscribe
Compose Post
+ Saving draft…
From 45ac280db768cb9bc25a0551d3e0326f56ab317a Mon Sep 17 00:00:00 2001
From: Konrad Pozniak
Date: Mon, 9 May 2022 19:40:32 +0200
Subject: [PATCH 075/104] Add Portuguese (Portugal) to the in-app language
picker (#2507)
---
app/src/main/res/values/donottranslate.xml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml
index e6f9f62a..ed1e30cb 100644
--- a/app/src/main/res/values/donottranslate.xml
+++ b/app/src/main/res/values/donottranslate.xml
@@ -57,6 +57,7 @@
- Occitan
- Polski
- Português (Brasil)
+ - Português (Portugal)
- Slovenščina
- Svenska
- Taqbaylit
@@ -106,6 +107,7 @@
- oc
- pl
- pt-BR
+ - pt-PT
- sl
- sv
- kab
From fc1e153cb3cfe2ede5e07c489ba7dadc7afd3b36 Mon Sep 17 00:00:00 2001
From: idontwanttohaveausername
Date: Mon, 9 May 2022 17:39:49 +0000
Subject: [PATCH 076/104] Translated using Weblate (Ukrainian)
Currently translated at 100.0% (478 of 478 strings)
Co-authored-by: idontwanttohaveausername
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/uk/
Translation: Tusky/Tusky
---
app/src/main/res/values-uk/strings.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 7d4b806e..67eb11cb 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -100,7 +100,7 @@
%s надсилає запит на підписку
%s підписується на вас
Тут нічого немає. Потягніть вниз, щоб оновити!
- Тут нічого немає.
+ Тут пусто.
Згорнути
Розгорнути
Натисніть для перегляду
From 8b840396fd2368dc69d660877127c525d759aaa4 Mon Sep 17 00:00:00 2001
From: Ihor Hordiichuk
Date: Mon, 9 May 2022 17:39:49 +0000
Subject: [PATCH 077/104] Translated using Weblate (Ukrainian)
Currently translated at 100.0% (478 of 478 strings)
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (478 of 478 strings)
Co-authored-by: Ihor Hordiichuk
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/uk/
Translation: Tusky/Tusky
---
app/src/main/res/values-uk/strings.xml | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 67eb11cb..6c510097 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -100,7 +100,7 @@
%s надсилає запит на підписку
%s підписується на вас
Тут нічого немає. Потягніть вниз, щоб оновити!
- Тут пусто.
+ Тут порожньо.
Згорнути
Розгорнути
Натисніть для перегляду
@@ -549,4 +549,5 @@
Сповіщення, коли редагується повідомлення, з яким ви взаємодіяли
Редакції допису
Вхід
+ Не вдалося завантажити сторінку входу.
\ No newline at end of file
From 078603819a2a86e557226bf929664b4f9f5832dc Mon Sep 17 00:00:00 2001
From: Stefano Pigozzi
Date: Mon, 9 May 2022 17:39:49 +0000
Subject: [PATCH 078/104] Translated using Weblate (Italian)
Currently translated at 100.0% (478 of 478 strings)
Co-authored-by: Stefano Pigozzi
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/it/
Translation: Tusky/Tusky
---
app/src/main/res/values-it/strings.xml | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 1a15d152..162846af 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -540,4 +540,5 @@
Notifiche di quando qualcuno si è registrato
Modifiche ai post
Notifiche di quando i post con cui hai interagito vengono modificati
+ Non è stato possibile caricare la pagina di login.
\ No newline at end of file
From 010a4372f9af4b5b3e0e844bd154a98cce834dbd Mon Sep 17 00:00:00 2001
From: Eric
Date: Mon, 9 May 2022 17:39:49 +0000
Subject: [PATCH 079/104] Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (478 of 478 strings)
Co-authored-by: Eric
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/zh_Hans/
Translation: Tusky/Tusky
---
app/src/main/res/values-zh-rCN/strings.xml | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 5dd1975f..f3d7b2aa 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -535,4 +535,5 @@
我进行过互动的嘟文被编辑了
嘟文编辑
当你进行过互动的嘟文被编辑时发出通知
+ 无法加载登录页。
\ No newline at end of file
From aa200404798ee66d3f578a097482b388849bd784 Mon Sep 17 00:00:00 2001
From: Vegard Skjefstad
Date: Mon, 9 May 2022 17:39:49 +0000
Subject: [PATCH 080/104] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegi?=
=?UTF-8?q?an=20Bokm=C3=A5l)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Currently translated at 100.0% (478 of 478 strings)
Co-authored-by: Vegard Skjefstad
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/nb_NO/
Translation: Tusky/Tusky
---
app/src/main/res/values-no-rNB/strings.xml | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/src/main/res/values-no-rNB/strings.xml b/app/src/main/res/values-no-rNB/strings.xml
index 3512b480..80b744bb 100644
--- a/app/src/main/res/values-no-rNB/strings.xml
+++ b/app/src/main/res/values-no-rNB/strings.xml
@@ -527,4 +527,5 @@
Redigerte innlegg
Varslinger når et innlegg du har hatt en interaksjon med er redigert
Innlogging
+ Klarte ikke å laste innloggingssiden.
\ No newline at end of file
From 9ec41903cfaeb52e0de38f3401fadfa11969066c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=E1=BB=93=20Nh=E1=BA=A5t=20Duy?=
Date: Mon, 9 May 2022 17:39:49 +0000
Subject: [PATCH 081/104] Translated using Weblate (Vietnamese)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Currently translated at 100.0% (478 of 478 strings)
Co-authored-by: Hồ Nhất Duy
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/
Translation: Tusky/Tusky
---
app/src/main/res/values-vi/strings.xml | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
index 5219775d..0c8109db 100644
--- a/app/src/main/res/values-vi/strings.xml
+++ b/app/src/main/res/values-vi/strings.xml
@@ -516,4 +516,5 @@
Sửa tút
Thông báo khi tút mà tôi tương tác bị sửa
Đăng nhập
+ Không thể tải trang đăng nhập.
\ No newline at end of file
From 529452c1937b3064d0e2c12fc7b29e3c40b3449a Mon Sep 17 00:00:00 2001
From: GunChleoc
Date: Mon, 9 May 2022 17:39:49 +0000
Subject: [PATCH 082/104] Translated using Weblate (Gaelic)
Currently translated at 100.0% (478 of 478 strings)
Co-authored-by: GunChleoc
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/gd/
Translation: Tusky/Tusky
---
app/src/main/res/values-gd/strings.xml | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/src/main/res/values-gd/strings.xml b/app/src/main/res/values-gd/strings.xml
index 10d64e0c..e27b677a 100644
--- a/app/src/main/res/values-gd/strings.xml
+++ b/app/src/main/res/values-gd/strings.xml
@@ -555,4 +555,5 @@
Brathan nuair a thèid postaichean a rinn thu conaltradh leotha a dheasachadh
chaidh post a rinn mi conaltradh leis a deasachadh
Clàraich a-steach
+ Cha b’ urrainn dhuinn duilleag a’ chlàraidh a-steach fhosgladh.
\ No newline at end of file
From d421de6ba8626a0c721c7b9c35e5646bbda81386 Mon Sep 17 00:00:00 2001
From: ruben
Date: Mon, 9 May 2022 17:39:49 +0000
Subject: [PATCH 083/104] Translated using Weblate (Catalan)
Currently translated at 93.3% (446 of 478 strings)
Co-authored-by: ruben
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/ca/
Translation: Tusky/Tusky
---
app/src/main/res/values-ca/strings.xml | 76 +++++++++++++-------------
1 file changed, 38 insertions(+), 38 deletions(-)
diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml
index 8b966061..65affe75 100644
--- a/app/src/main/res/values-ca/strings.xml
+++ b/app/src/main/res/values-ca/strings.xml
@@ -20,8 +20,8 @@
Notificacions
Local
Federació
- Toot
- Posts
+ Fil
+ Publicacions
Seguits
Seguidors
Preferits
@@ -31,19 +31,19 @@
Edita el perfil
Esborranys
\@%s
- %s tootejat
+ %s ha impulsat
Contingut sensible
Fes clic per a visualitzar-lo
Mostra\'n més
Mostra\'n menys
No hi res aquí. Llisca avall per a actualitzar!
- %s ha impulsat el teu toot
- %s ha marcat com a preferit el teu toot
+ %s ha impulsat la teva publicació
+ %s ha marcat com a preferida la teva publicació
%s et segueix
Denuncia @%s
Cap comentari addicional?
Respon
- Retooteja
+ Impulsa
Preferit
Més
Escriure
@@ -55,8 +55,8 @@
Deixa de blocar
Denuncia
Elimina
- TOOT
- TOOT!
+ PUBLICA
+ PUBLICA!
Torna a intentar-ho
Tanca
Perfil
@@ -83,8 +83,8 @@
Esborranys
S\'està baixant %1$s
Copia l\'enllaç
- Comparteix l\'URL del toot a…
- Comparteix el toot a…
+ Comparteix l\'URL de la publicació a…
+ Comparteix la publicació a…
Enviat!
Usuari desblocat
Usuari sense silenciar
@@ -132,10 +132,10 @@
Amaga el botó de redacció en desplaçament
Filtre de la cronologia
Pestanyes
- Mostra els retoots
+ Mostra els impulsos
Mostra les respostes
Mostra les previsualitzacions
- Privacitat predeterminada dels toots
+ Privacitat per defecte de les publicacions
Publicació
Pública
Sense llistar
@@ -145,7 +145,7 @@
Notificacions sobre mencions noves
Seguidors nous
Notificacions sobre nous seguidors
- Retoots
+ Impulsos
Notificacions si retootejents els teus toots
Preferits
Notificacions si marquen com a preferits els teus toots
@@ -172,8 +172,8 @@
https://github.com/tuskyapp/Tusky/issues
Perfil del Tusky
- Comparteix el contingut del toot
- Comparteix l\'enllaç al toot
+ Comparteix el contingut de la publicació
+ Comparteix l\'enllaç a la publicació
Imatges
Vídeo
@@ -193,7 +193,7 @@
En resposta a @%s
carrega\'n més
Vota
- S\'ha produït un error en enviar el tut.
+ S\'ha produït un error en publicar.
Pestanyes
Llicències
Amplia
@@ -209,11 +209,11 @@
Multimèdia amagada
Amaga
Estàs segur de tancar la sessió de %1$s\?
- Amaga els retoots
+ Amaga els impulsos
Mostra els impulsos
Elimina i reecririu
Obre el menú
- Visibilitat del toot
+ Visibilitat de la publicació
Contingut sensible
Afegir una pestanya
Enllaços
@@ -227,17 +227,17 @@
Baixa el fitxer
Compartir la imatge a …
Enviat!
- S\'ha enviat la petició de seguiment
+ Petició enviada
Amb respostes
Teclat d\'emojis
Obrir el media #%d
- Obrir com %s
+ Obre com a %s
S\'està Descarregant media
Resposta enviada correctament.
Resposta …
Revocar la petició de seguiment\?
Vols eliminar aquest toot\?
- Esborrar i reescriure aquest toot\?
+ Vols eliminar i reescriure aquesta publicació\?
Finalització de les enquetes
Tema
Cronologia
@@ -267,7 +267,7 @@
Eliminar
Afegir un compte
Obre l\'autor de l\'impuls
- Mostra els retoots
+ Mostra els impulsos
Notificacions d\'enquestes que han finalitzat
Línia de temps públiques
Actualització
@@ -297,11 +297,11 @@
Protegir el compte
S\'haurà d\'admetre els seguidors manualment
Guardar l\'esborrany\?
- Enviant toot…
- Error enviant el toot
- Enviant toots
+ S\'està publicant…
+ Error en publicar
+ S\'esatan enviant les publicacions
Envio anul·lat
- Una copia del toot s\'ha guardat a esborranys
+ S\'ha guardat una còpia de la publicació als esborranys
Escriure
La teva instància %s no te emojis personalitzats
Estil dels emojis
@@ -309,7 +309,7 @@
Hauràs de descarregar el joc d\'emojis
Cercant…
Expandir/ocultar tots els estats
- Obrir toot
+ Obre la publicació
Cal reiniciar l\'aplicació
Has de reiniciar l\'aplicació per tal d\'aplicar aquests canvis
Més tard
@@ -360,7 +360,7 @@
Netejar
Filtrar
Aplicar
- Escriure un toot
+ Escriure una publicació
Escriure
Mostra l\'indicador dels bots
Vols netejar totes les notificacions permanentment\?
@@ -374,8 +374,8 @@
L\'enquesta on has votat està tancada
La enquesta que heu creat ha finalitzat
Advertència: %s
- Toot fixat
- Toot no fixat
+ Fixat
+ No fixis
Fixar
Respost
Accions per a la imatge %s
@@ -385,7 +385,7 @@
Silenciar %s
%s visible
Amagar el domini sencer
- Obrir sempre els toots marcats amb contingut sensible
+ Mostra sempre obertes les publicacions marcades amb avisos de contingut
Paraula sencera
Ventall actual d\'emojis de Google
Enquesta amb opcions: %1$s, %2$s, %3$s, %4$s; %5$s
@@ -418,12 +418,12 @@
Múltiples tries
Tria %d
Preferits
- Toots programats
+ Publicacions programades
Preferit
Edita
Preferits
- Toots programats
- Programar el toot
+ Publicacions programades
+ Programa la publicació
Reiniciar
Desenvolupat per Tusky
S\'ha afegit a les adreces d\'interès
@@ -449,7 +449,7 @@
Silenciar @%s\?
Bloquejar @%s\?
No silenciar la conversació
- Conversació muda
+ Silencia la conversa
%s ha sol·licitat seguir-te
A baix
A dalt
@@ -487,15 +487,15 @@
Adjuncions
Àudio
Notificacions quan algú a qui esteu subscrit publica un tut nou
- Tuts nous
+ Publicacions noves
emojis personalitzats animats
algú a qui estic subscrit acaba de publicar un tut nou
%s acaba de fer una publicació
Avisos
- S\'ha esborrat el tut del qual en vau fer un esborrany de resposta
+ S\'ha eliminat la publicació a la qual vau fer un esborrany de resposta
S\'ha eliminat l\'esborrany
No s\'ha pogut carregar la informació de la resposta
- No s\'ha pogut enviar aquest tut!
+ No s\'ha pogut publicar!
Segur que voleu esborrar la llista %s\?
- No podeu pujar més de %1$d adjunts multimèdia.
From 3d3287cc42dbeb5630c5bc037044fb3ef26abf53 Mon Sep 17 00:00:00 2001
From: Christian Schmidt
Date: Mon, 9 May 2022 17:39:49 +0000
Subject: [PATCH 084/104] Translated using Weblate (German)
Currently translated at 100.0% (478 of 478 strings)
Co-authored-by: Christian Schmidt
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/de/
Translation: Tusky/Tusky
---
app/src/main/res/values-de/strings.xml | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index b05ccc96..c8d64750 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -25,7 +25,7 @@
Föderiert
Direktnachrichten
Tabs
- Beitrag
+ Konversation
Beiträge
mit Antworten
Angeheftet
@@ -490,7 +490,7 @@
Für immer
Anhänge
Audio
- Benachrichtigungen, wenn jemand, den ich abonniert habe, etwas Neues veröffentlicht
+ Benachrichtigungen, wenn jemand, den ich abonniert habe, eine neue Nachricht veröffentlicht
Neue Beiträge
GIF-Emojis animieren
Jemand, den ich abonniert habe, hat etwas Neues veröffentlicht
@@ -535,4 +535,6 @@
Jemand hat sich registriert
Benachrichtigungen, wenn Beiträge bearbeitet werden, mit denen du interagiert hast
Anmelden
+ Die Anmeldeseite konnte nicht geladen werden.
+ Beitragsbearbeitungen
\ No newline at end of file
From 23d6d04b399711cdb07a67028f96cd15c6efcedc Mon Sep 17 00:00:00 2001
From: Bruno Miguel
Date: Mon, 9 May 2022 17:39:50 +0000
Subject: [PATCH 085/104] Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (478 of 478 strings)
Co-authored-by: Bruno Miguel
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/pt_PT/
Translation: Tusky/Tusky
---
app/src/main/res/values-pt-rPT/strings.xml | 212 +++++++++++----------
1 file changed, 107 insertions(+), 105 deletions(-)
diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml
index aa81160b..6be06b0c 100644
--- a/app/src/main/res/values-pt-rPT/strings.xml
+++ b/app/src/main/res/values-pt-rPT/strings.xml
@@ -10,20 +10,20 @@
A responder a @%s
Editar a lista
- Exige a aprovação manual de seguidores
+ Necessita de aprovar manualmente os seguidores
Guardar rascunho\?
Depois
Desafixar
%1$s e %2$s
Bem-estar
Escrever Toot
- Deseja excluir a lista %s\?
- Apesar do seu perfil não ser trancado, %1$s exige que você revise a solicitação para te seguir destes perfis manualmente.
- Notificar
- Cancelar
+ Pretende remover a lista %s\?
+ Apesar do seu perfil não ser privado, %1$s exige que você reveja manualmente as solicitações para te seguir destes perfis.
+ Subscrever
+ Remover subscrição
Autorização negada.
Erro ao adquirir token de login.
- O toot é muito longo!
+ O toot é muito extenso!
O ficheiro deve ter menor de 8MB.
Os ficheiros de vídeo devem ter menor de 40MB.
Os ficheiros de áudio devem ter menor de 40MB.
@@ -48,13 +48,13 @@
Seguidores
Favoritos
Itens guardados
- Utilizadors silenciados
- Utilizadors bloqueados
+ Utilizadores silenciados
+ Utilizadores bloqueados
Instâncias bloqueadas
Seguidores Pendentes
Conteúdo sensível
Editar perfil
- Conteúdo ocultado
+ Conteúdo multimédia ocultado
Rascunhos
Toque para ver
Mostrar Mais
@@ -67,7 +67,7 @@
\@%s
%s fez boost
Nada aqui.
- Nada aqui. Arraste para baixo para atualizar!
+ Nada para ver aqui. Arraste para baixo para atualizar!
%s fez boost ao seu toot
%s adicionou o seu toot aos favoritos
%s está a seguir-te
@@ -77,7 +77,7 @@
%s editou um toot
Denunciar @%s
Comentários adicionais\?
- Resposta rápida
+ Resposta Rápida
Responder
Fazer boost
Desfazer boost
@@ -89,7 +89,7 @@
Escrever
Entrar com Mastodon
Sair
- Tem certeza de que deseja sair da conta %1$s\?
+ Tem a certeza que deseja sair da conta %1$s\?
Seguir
Deixar de seguir
Bloquear
@@ -106,12 +106,12 @@
Tentar novamente
Fechar
Perfil
- Preferências
- Preferências da Conta
+ Configurações
+ Configurações da Conta
Favoritos
- Guardados
- utilizadors silenciados
- utilizadors bloqueados
+ Itens Guardados
+ Utilizadores silenciados
+ Utilizadores bloqueados
Instâncias bloqueadas
Seguidores Pendentes
Conteúdo multimédia
@@ -138,13 +138,13 @@
Privacidade do toot
Aviso de conteúdo
Teclado de emojis
- Agendar toot
+ Agendar Toot
Redefinir
Adicionar Separador
Hiperligações
Menções
Hashtags
- Ver quem fez boost
+ Ver autor do boost
Mostrar boosts
Mostrar favoritos
Hashtags
@@ -152,7 +152,7 @@
Hiperligações
Abrir conteúdo multimédia #%d
A descarregar %1$s
- Copiar hiperligação
+ Copiar a hiperligação
Abrir como %s
Partilhar como…
Descarregar conteúdo multimédia
@@ -178,22 +178,22 @@
Cabeçalho
O que é uma instância\?
A ligar…
- O endereço IP ou domínio de qualquer instância pode ser inserido aqui, como por exemplo mastodon.social, masto.donte.com.br, colorid.es ou qualquer outro!
+ O endereço IP ou domínio de qualquer instância pode ser inserido aqui, como por exemplo mastodon.social, masto.pt, pleroma.pt ou qualquer outro!
\n
-\n Se ainda não tem uma conta, insira o nome da instância onde pretende participar e crie uma conta lá.
+\nSe ainda não tem uma conta, insira o nome da instância onde pretende participar e crie uma conta lá.
\n
-\n Uma instância é um lugar onde sua conta é hospedada, mas pode facilmente seguir e comunicar com pessoas de outras instâncias como se todos estivessem no mesmo site.
+\nUma instância é o local onde sua conta é criada, mas pode facilmente seguir e comunicar com pessoas de outras instâncias como se estivessem todos no mesmo site.
\n
\nMais informações disponíveis em joinmastodon.org.
- Envio de Conteúdo Multimédia Terminando
+ A Terminar Envio de Conteúdo Multimédia
A enviar…
Descarregar
- Cancelar pedido para seguir\?
+ Cancelar o pedido para seguir\?
Deixar de seguir esta conta\?
Apagar este toot\?
- Apagar e criar novo rascunho\?
+ Apagar e rescrever este toot\?
Apagar esta conversa\?
- Tem certeza que pretende bloquear a instância %s\? Deixará de poder ver quaisquer conteúdos dessa instância em qualquer timeline pública ou nas suas notificações. Os seus seguidores dessa instância serão removidos.
+ Tem a certeza que pretende bloquear a instância %s\? Deixará de poder ver quaisquer conteúdos dessa instância em qualquer timeline pública ou nas suas notificações. Os seus seguidores dessa instância serão removidos.
Bloquear instância
Bloquear @%s\?
Silenciar @%s\?
@@ -202,7 +202,7 @@
Não listado: Não publicar em timelines públicas
Privado: Publicar apenas para os seguidores
Direto: Publicar apenas para os utilizadores mencionados
- Editar notificações
+ Notificações
Notificações
Alertas
Notificar com som
@@ -213,17 +213,17 @@
for seguido
alguém para quem ativei os alertas publicar um toot novo
fizerem pedido para me seguir
- derem boosts nos meus toots
+ fizerem boosts aos meus toots
adicionarem os meus toots aos favoritos
votações terminarem
alguém criar conta
um toot com o qual interagi for editado
Aparência
- Temas
+ Tema da Aplicação
Timelines
Filtros
- Noturno
- Diurno
+ Escuro
+ Claro
AMOLED
Automático ao pôr-do-sol
Usar o Tema do Sistema
@@ -232,11 +232,11 @@
Esconder o botão de criação de toots ao fazer scroll
Idioma
Mostrar indicador para bots
- Reproduzir avatares em GIFs
+ Reproduzir avatars em GIF
Mostrar desfocagem em conteúdo multimédia sensível
Animar emojis personalizados
Filtro da timeline
- Separadors
+ Separadores
Mostrar boosts
Mostrar respostas
Mostrar pré-visualização de conteúdo multimédia
@@ -268,23 +268,23 @@
Notificações para seguidores pendentes
Boosts
Votações
- Notificações para votações que terminaram
- Notificações quando alguém para quem ativei os alertas publicar um toot novo
+ Notificações para votações terminadas
+ Notificações quando alguém para quem ativou os alertas publicar um toot novo
Notificações para novos utilizadores
- Edições a toots
+ Edições de toots
Notificações para boosts recebidos
Favoritos
- Notificações quando os teus toots são adicionados aos favoritos
- Notificações quando toots com os quais interagi foram editados
+ Notificações quando os seus toots são adicionados aos favoritos
+ Notificações quando toots com os quais interagiu forem editados
%s mencionou-te
%1$s, %2$s, %3$s e %4$d outros
%1$s, %2$s e %3$s
- Perfil Bloqueado
+ Perfil Privado
Sobre
Tusky %s
A correr o Tusky
Atualizar
- Tusky é um software livre e de código aberto e é ljcenciado com a versão 3 da GNU General Public License. Leia a licença aqui: https://www.gnu.org/licenses/gpl-3.0.pt-br.html
+ Tusky é um software livre e de código aberto, licenciado com a versão 3 da GNU General Public License. Pode ler a licença aqui: https://www.gnu.org/licenses/gpl-3.0.pt-br.html
Página do projeto:
\n https://tusky.app
Reporte de erros e pedidos de funcionalidades:
@@ -296,13 +296,13 @@
Vídeo
Áudio
Anexos
- Pedido enviado
+ Pedido para seguir enviado
em %dy
em %dd
em %dh
em %dm
em %ds
- %da
+ %dy
%dd
%dh
%dm
@@ -325,7 +325,7 @@
Listas
Não foi possível renomear a lista
Listas
- Lista da timeline
+ Cronologia da timeline
Não foi possível criar a lista
Não foi possível apagar a lista
Criar uma lista
@@ -338,10 +338,11 @@
Erro ao incluir descrição
- Descrição para deficientes visuais
+\n(até %d letra)
+ - Descrição para deficientes visuais
\n(até %d caracteres)
-
- Descrever
+ Escrever descrição
Remover
Bloquear perfil
A enviar o toot…
@@ -351,34 +352,34 @@
Uma cópia do toot foi guardada nos seus rascunhos
Escrever
A sua instância, %s, não tem emojis personalizados
- Estilo de emoji
+ Estilo dos emojis
Padrão do sistema
É necessário descarregar estes pacotes de emojis primeiro
A fazer pesquisa…
Expandir/Contrair todos os toots
Abrir toot
É necessário reiniciar a aplicação
- É necessário reiniciar o aplicativo para aplicar as alterações
+ É necessário reiniciar o Tusky para aplicar as alterações
Reiniciar
Pacote de emojis padrão do seu dispositivo
- Emojis padrão do Android da versão 4.4 até 7.1
+ Emojis padrão do Android 4.4 até ao 7.1
Pacote de emojis padrão do Mastodon
- Pacote de emojis atual do Google
- Erro ao baixar
+ Pacote de emojis atuais da Google
+ Erro ao descarregar
Robô
%1$s mudou-se para:
- Dar boost para o mesmo público
+ Dar boost para o público inicial
Desfazer boost
O Tusky contém código e recursos dos seguintes projetos de código aberto:
- Licenciado sob a licença Apache (cópia separadorixo)
+ Licenciado sob a licença Apache (cópia abaixo)
CC-BY 4.0
CC-BY-SA 4.0
Metadados do perfil
- Adicionar
+ adicionar dados
Rótulo
Conteúdo
- Usar tempo absoluto
- As informações separadorixo podem refletir incompletamente o perfil do utilizador. Toque aqui para abrir o perfil completo no navegador.
+ Usar data absoluta
+ As informações abaixo podem refletir, de forma incompleta, o perfil do utilizador. Toque aqui para abrir o perfil completo no navegador.
Fixar
- %1$s Favorito
@@ -388,25 +389,25 @@
- %s Boost
- %s Boosts
- Levou boost de
- Favoritado por
+ Boost dado por
+ Adicionado aos favoritos por
%1$s
- %1$s, %2$s e %3$d outros
+ %1$s, %2$s e %3$d mais
- - excedeu o máximo de %1$d separador
- - excedeu o máximo de %1$d separadors
+ - atingiu o máximo de %1$d separador
+ - atingiu o máximo de %1$d separadores
- conteúdo multimédia: %s
+ Conteúdo multimédia: %s
Aviso de Conteúdo: %s
Sem descrição
- Você fez boost
- Favoritado
- Salvo
+ Replicado
+ Adicionado aos favoritos
+ Guardado
Público
Não-listado
Privado
Direto
- Enquete com as opções: %1$s, %2$s, %3$s, %4$s; %5$s
+ Votação com as opções: %1$s, %2$s, %3$s, %4$s; %5$s
Nome da lista
Adicionar hashtag
Hashtag sem #
@@ -414,11 +415,11 @@
Selecionar lista
Lista
Limpar
- Filtro
- Salvar
- Compor toot
- Compor
- Tem certeza de que deseja limpar permanentemente todas as suas notificações\?
+ Filtrar
+ Aplicar
+ Escrever toot
+ Escrever
+ Tem certeza que pretende limpar permanentemente todas as suas notificações\?
Opções para imagem %s
%1$s • %2$s
@@ -430,10 +431,10 @@
- %s pessoas
termina em %s
- Terminou
+ terminada
Votar
- Uma enquete que você votou terminou
- Sua enquete terminou
+ Uma votação em que votou terminou
+ A sua votação terminou
- %d dia restante
- %d dias restantes
@@ -451,20 +452,20 @@
- %d segundos restantes
Continuar
- Voltar
- Ok
+ Retroceder
+ Feito
\@%s denunciado com sucesso
Comentários adicionais
Encaminhar para %s
Erro ao denunciar
Erro ao carregar toots
- A denúncia será enviada aos moderadores da instância. Explique por que denunciou a conta:
- A conta está em outra instância. Enviar uma cópia anônima da denúncia para lá\?
+ A denúncia será enviada aos moderadores da instância. Pode adicionar abaixo uma explicação para a sua denúncia:
+ A conta está noutra instância. Quer enviar uma cópia anónima da denúncia para lá\?
Contas
Erro ao pesquisar
- Mostrar filtro de notificações
- Ativar deslizar para alternar entre separadors
- Enquete
+ Mostrar Filtro das Notificações
+ Ativar gesto de deslizar para alternar entre separadores
+ Votação
Duração
Indefinido
5 minutos
@@ -481,46 +482,46 @@
180 dias
365 dias
Adicionar opção
- Múltiplas opções
+ Escolha múltipla
Opção %d
Editar
- Erro ao pesquisar %s
- Sem rascunhos.
- Sem toots agendados.
- Salvo!
+ Erro ao pesquisar toot %s
+ Não tem rascunhos.
+ Não tem toots agendados.
+ Guardado!
Algumas informações que podem afetar seu bem-estar serão ocultadas. Isso inclui:
\n
\n- Notificações de favoritos, boosts e seguidores
\n- Número de favoritos e boosts nos toots
\n- Status de toots e seguidores nos perfis
\n
-\nNotificações push não serão afetadas, mas é possível revisar sua preferência manualmente.
- Revisar notificações
+\nNotificações push não serão afetadas, mas é possível rever as configurações das notificações manualmente.
+ Rever Notificações
Limitar notificações da timeline
- Sem comunicados.
- Mastodon possui um intervalo mínimo de 5 minutos para agendar.
- Mostrar prévias de Hiperligações nas linhas
- Solicitar confirmação antes de dar boost
- Solicitar confirmação antes de favoritar
- Esconder o título da barra superior de tarefas
- Nota pessoal sobre este perfil aqui
- Esconder status dos toots
- Esconder status dos perfis
+ Sem anúncios.
+ O Mastodon tem um intervalo mínimo de agendamento de 5 minutos.
+ Mostrar pré-visualização de hiperligações nas timelines
+ Mostrar janela de confirmação antes de dar boost
+ Mostrar janela de confirmação antes de adicionar aos favoritos
+ Esconder o título da barra superior
+ Nota pessoal sobre este perfil
+ Esconder estatísticas quantitativas nos toots
+ Esconder estatísticas quantitativas nos perfis
- - Não é possível anexar mais de %1$d arquivo de conteúdo multimédia.
- - Não é possível anexar mais de %1$d arquivos de conteúdo multimédia.
+ - Não é possível enviar mais de %1$d arquivo de conteúdo multimédia.
+ - Não é possível enviar mais de %1$d arquivos de conteúdo multimédia.
Erro ao enviar o toot!
- Erro ao carregar toot para responder
- Rascunho excluído
- O toot em que se rascunhou uma resposta foi excluído
+ Erro ao carregar informação de resposta
+ Rascunho apagado
+ O toot para o qual escreveu um rascunho foi apagado
Ocorreu um erro.
Ocorreu um erro de conetividade! Por favor, verifique a sua ligação e tente novamente!
Isto não pode estar vazio.
- Instância inválida inserida
+ A instância inserida é inválida
Erro ao autenticar com esta instância.
- Nao foi possível encontrar um navegador.
- Ocorreu um erro não identificado de autorização.
+ Não foi possível encontrar um navegador.
+ Ocorreu um erro de autorização não identificado.
Entrar
Guardar
Editar perfil
@@ -528,4 +529,5 @@
Desfazer
Aceitar
Rejeitar
+ Não foi possível carregar a página de login
\ No newline at end of file
From b353e67587355fc1dc07eb1db30a2643b2358684 Mon Sep 17 00:00:00 2001
From: Christian Schmidt
Date: Mon, 9 May 2022 11:40:35 +0000
Subject: [PATCH 086/104] Translated using Weblate (German)
Currently translated at 100.0% (16 of 16 strings)
Translation: Tusky/Tusky description
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky-app/de/
---
fastlane/metadata/android/de/changelogs/89.txt | 7 +++++++
1 file changed, 7 insertions(+)
create mode 100644 fastlane/metadata/android/de/changelogs/89.txt
diff --git a/fastlane/metadata/android/de/changelogs/89.txt b/fastlane/metadata/android/de/changelogs/89.txt
new file mode 100644
index 00000000..cb92453b
--- /dev/null
+++ b/fastlane/metadata/android/de/changelogs/89.txt
@@ -0,0 +1,7 @@
+Tusky v17.0
+
+- "Öffnen als..." ist jetzt im Menü in Konto Profilen auch verfügbar, wenn mehrere Konten genutzt werden
+- Die Anmeldung wird jetzt über die WebView innerhalb der App abgewickelt
+- Unterstützung für Android 12
+- Unterstützung für die neue Mastodon instance configuration API
+- und einige andere kleine Fehlerbehebungen und Verbesserungen
From 7db504775c094c333f8e012ef797d4ece06f2d88 Mon Sep 17 00:00:00 2001
From: Conny Duck
Date: Tue, 10 May 2022 20:00:53 +0200
Subject: [PATCH 087/104] Release 90
---
app/build.gradle | 4 ++--
fastlane/metadata/android/en-US/changelogs/91.txt | 6 ++++++
2 files changed, 8 insertions(+), 2 deletions(-)
create mode 100644 fastlane/metadata/android/en-US/changelogs/91.txt
diff --git a/app/build.gradle b/app/build.gradle
index 4484ff54..c152805b 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -20,8 +20,8 @@ android {
applicationId APP_ID
minSdkVersion 21
targetSdkVersion 31
- versionCode 89
- versionName "17.0"
+ versionCode 90
+ versionName "18.0 beta 1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
diff --git a/fastlane/metadata/android/en-US/changelogs/91.txt b/fastlane/metadata/android/en-US/changelogs/91.txt
new file mode 100644
index 00000000..e1d98303
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/91.txt
@@ -0,0 +1,6 @@
+Tusky v18.0
+
+- Support for new Mastodon 3.5 notification types
+- The bot badge now looks better and adjusts to the selected theme
+- Text can now be selected on the post detail view
+- Fixed a lot of bugs, including one that prevented logins on Android 6 and lower
From 0f1e95d0ca81d0254f8d1ae6b60a3130d51b15cf Mon Sep 17 00:00:00 2001
From: Konrad Pozniak
Date: Wed, 11 May 2022 07:43:30 +0200
Subject: [PATCH 088/104] add 35.json
---
.../35.json | 821 ++++++++++++++++++
1 file changed, 821 insertions(+)
create mode 100644 app/schemas/com.keylesspalace.tusky.db.AppDatabase/35.json
diff --git a/app/schemas/com.keylesspalace.tusky.db.AppDatabase/35.json b/app/schemas/com.keylesspalace.tusky.db.AppDatabase/35.json
new file mode 100644
index 00000000..9b71adf2
--- /dev/null
+++ b/app/schemas/com.keylesspalace.tusky.db.AppDatabase/35.json
@@ -0,0 +1,821 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 35,
+ "identityHash": "9e6c0bb60538683a16c30fa3e1cc24f2",
+ "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)",
+ "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
+ }
+ ],
+ "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, `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, `notificationSound` INTEGER NOT NULL, `notificationVibration` INTEGER NOT NULL, `notificationLight` INTEGER NOT NULL, `defaultPostPrivacy` INTEGER NOT NULL, `defaultMediaSensitivity` INTEGER NOT NULL, `alwaysShowSensitiveMedia` INTEGER NOT NULL, `alwaysOpenSpoiler` INTEGER NOT NULL, `mediaPreviewEnabled` INTEGER NOT NULL, `lastNotificationId` TEXT NOT NULL, `activeNotifications` TEXT NOT NULL, `emojis` TEXT NOT NULL, `tabPreferences` TEXT NOT NULL, `notificationsFilter` TEXT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "domain",
+ "columnName": "domain",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "accessToken",
+ "columnName": "accessToken",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isActive",
+ "columnName": "isActive",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "accountId",
+ "columnName": "accountId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "username",
+ "columnName": "username",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "displayName",
+ "columnName": "displayName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "profilePictureUrl",
+ "columnName": "profilePictureUrl",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notificationsEnabled",
+ "columnName": "notificationsEnabled",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notificationsMentioned",
+ "columnName": "notificationsMentioned",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notificationsFollowed",
+ "columnName": "notificationsFollowed",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "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": "notificationSound",
+ "columnName": "notificationSound",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notificationVibration",
+ "columnName": "notificationVibration",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notificationLight",
+ "columnName": "notificationLight",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "defaultPostPrivacy",
+ "columnName": "defaultPostPrivacy",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "defaultMediaSensitivity",
+ "columnName": "defaultMediaSensitivity",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "alwaysShowSensitiveMedia",
+ "columnName": "alwaysShowSensitiveMedia",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "alwaysOpenSpoiler",
+ "columnName": "alwaysOpenSpoiler",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "mediaPreviewEnabled",
+ "columnName": "mediaPreviewEnabled",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastNotificationId",
+ "columnName": "lastNotificationId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "activeNotifications",
+ "columnName": "activeNotifications",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "emojis",
+ "columnName": "emojis",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "tabPreferences",
+ "columnName": "tabPreferences",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notificationsFilter",
+ "columnName": "notificationsFilter",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "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, 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
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "instance"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "TimelineStatusEntity",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serverId` TEXT NOT NULL, `url` TEXT, `timelineUserId` INTEGER NOT NULL, `authorServerId` TEXT, `inReplyToId` TEXT, `inReplyToAccountId` TEXT, `content` TEXT, `createdAt` INTEGER NOT NULL, `emojis` TEXT, `reblogsCount` INTEGER NOT NULL, `favouritesCount` INTEGER NOT NULL, `reblogged` INTEGER NOT NULL, `bookmarked` INTEGER NOT NULL, `favourited` INTEGER NOT NULL, `sensitive` INTEGER NOT NULL, `spoilerText` TEXT 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, PRIMARY KEY(`serverId`, `timelineUserId`), FOREIGN KEY(`authorServerId`, `timelineUserId`) REFERENCES `TimelineAccountEntity`(`serverId`, `timelineUserId`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
+ "fields": [
+ {
+ "fieldPath": "serverId",
+ "columnName": "serverId",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "url",
+ "columnName": "url",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "timelineUserId",
+ "columnName": "timelineUserId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "authorServerId",
+ "columnName": "authorServerId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "inReplyToId",
+ "columnName": "inReplyToId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "inReplyToAccountId",
+ "columnName": "inReplyToAccountId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "createdAt",
+ "columnName": "createdAt",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "emojis",
+ "columnName": "emojis",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "reblogsCount",
+ "columnName": "reblogsCount",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "favouritesCount",
+ "columnName": "favouritesCount",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "reblogged",
+ "columnName": "reblogged",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "bookmarked",
+ "columnName": "bookmarked",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "favourited",
+ "columnName": "favourited",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "sensitive",
+ "columnName": "sensitive",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "spoilerText",
+ "columnName": "spoilerText",
+ "affinity": "TEXT",
+ "notNull": 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
+ }
+ ],
+ "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, `accounts` TEXT NOT NULL, `unread` INTEGER NOT NULL, `s_id` TEXT NOT NULL, `s_url` TEXT, `s_inReplyToId` TEXT, `s_inReplyToAccountId` TEXT, `s_account` TEXT NOT NULL, `s_content` TEXT NOT NULL, `s_createdAt` INTEGER NOT NULL, `s_emojis` TEXT NOT NULL, `s_favouritesCount` INTEGER NOT NULL, `s_favourited` INTEGER NOT NULL, `s_bookmarked` INTEGER NOT NULL, `s_sensitive` INTEGER NOT NULL, `s_spoilerText` TEXT NOT NULL, `s_attachments` TEXT NOT NULL, `s_mentions` TEXT NOT NULL, `s_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, PRIMARY KEY(`id`, `accountId`))",
+ "fields": [
+ {
+ "fieldPath": "accountId",
+ "columnName": "accountId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "accounts",
+ "columnName": "accounts",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "unread",
+ "columnName": "unread",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.id",
+ "columnName": "s_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.url",
+ "columnName": "s_url",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastStatus.inReplyToId",
+ "columnName": "s_inReplyToId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastStatus.inReplyToAccountId",
+ "columnName": "s_inReplyToAccountId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastStatus.account",
+ "columnName": "s_account",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.content",
+ "columnName": "s_content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.createdAt",
+ "columnName": "s_createdAt",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.emojis",
+ "columnName": "s_emojis",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.favouritesCount",
+ "columnName": "s_favouritesCount",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.favourited",
+ "columnName": "s_favourited",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.bookmarked",
+ "columnName": "s_bookmarked",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.sensitive",
+ "columnName": "s_sensitive",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.spoilerText",
+ "columnName": "s_spoilerText",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.attachments",
+ "columnName": "s_attachments",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.mentions",
+ "columnName": "s_mentions",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastStatus.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
+ }
+ ],
+ "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, '9e6c0bb60538683a16c30fa3e1cc24f2')"
+ ]
+ }
+}
\ No newline at end of file
From 4159826f266d7c6b69738d0668c43b33b68b51c8 Mon Sep 17 00:00:00 2001
From: mcclure
Date: Wed, 11 May 2022 11:16:51 -0400
Subject: [PATCH 089/104] Allow build on systems without git (#2514)
Set git revision to "unknown" if git not available.
---
app/build.gradle | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index c152805b..2a34c80a 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -7,9 +7,13 @@ apply from: "../instance-build.gradle"
def getGitSha = {
def stdout = new ByteArrayOutputStream()
- exec {
- commandLine 'git', 'rev-parse', '--short', 'HEAD'
- standardOutput = stdout
+ try {
+ exec {
+ commandLine 'git', 'rev-parse', '--short', 'HEAD'
+ standardOutput = stdout
+ }
+ } catch (Exception e) {
+ return "unknown"
}
return stdout.toString().trim()
}
From b8e3b6b884d0fed3005f21fc52da3816208e2894 Mon Sep 17 00:00:00 2001
From: Konrad Pozniak
Date: Thu, 12 May 2022 18:21:33 +0200
Subject: [PATCH 090/104] fix currently logged in profiles not being visible in
main drawer when offline (#2516)
---
app/src/main/java/com/keylesspalace/tusky/MainActivity.kt | 2 ++
1 file changed, 2 insertions(+)
diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt
index 466dba16..25b70240 100644
--- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt
@@ -621,6 +621,8 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
binding.mainToolbar.setOnClickListener {
(adapter.getFragment(activeTabLayout.selectedTabPosition) as? ReselectableFragment)?.onReselect()
}
+
+ updateProfiles()
}
private fun handleProfileClick(profile: IProfile, current: Boolean): Boolean {
From d9c6269d4448e40a7a07ba6d209988b2b5eabe2c Mon Sep 17 00:00:00 2001
From: Konrad Pozniak
Date: Thu, 12 May 2022 18:21:43 +0200
Subject: [PATCH 091/104] fix deleting media attachments removing the wrong
ones (#2517)
---
.../keylesspalace/tusky/components/compose/ComposeViewModel.kt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt
index abf2ff42..7faf1139 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt
@@ -184,7 +184,7 @@ class ComposeViewModel @Inject constructor(
fun removeMediaFromQueue(item: QueuedMedia) {
mediaToJob[item.localId]?.cancel()
- media.update { mediaValue -> mediaValue.filter { it.localId == item.localId } }
+ media.update { mediaValue -> mediaValue.filter { it.localId != item.localId } }
}
fun toggleMarkSensitive() {
From 0a0f31451660f388edfa9ac9373a75549d6f8490 Mon Sep 17 00:00:00 2001
From: Ihor Hordiichuk
Date: Thu, 12 May 2022 01:40:36 +0000
Subject: [PATCH 092/104] Translated using Weblate (Ukrainian)
Currently translated at 100.0% (17 of 17 strings)
Translation: Tusky/Tusky description
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky-app/uk/
---
fastlane/metadata/android/uk/changelogs/91.txt | 6 ++++++
1 file changed, 6 insertions(+)
create mode 100644 fastlane/metadata/android/uk/changelogs/91.txt
diff --git a/fastlane/metadata/android/uk/changelogs/91.txt b/fastlane/metadata/android/uk/changelogs/91.txt
new file mode 100644
index 00000000..4132d155
--- /dev/null
+++ b/fastlane/metadata/android/uk/changelogs/91.txt
@@ -0,0 +1,6 @@
+Tusky v18.0
+
+- Підтримка нових типів сповіщень Mastodon 3.5
+- Кращий вигляд позначки бота і розширений вибір тем
+- Текст тепер можна вибрати у докладному поданні допису
+- Виправлено безліч помилок, включно з тою, яка перешкоджала входу на Android 6 і старіших
From 523c9b6b8ccc38a418a146229c39cf52abf10c88 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=E1=BB=93=20Nh=E1=BA=A5t=20Duy?=
Date: Thu, 12 May 2022 01:40:36 +0000
Subject: [PATCH 093/104] Translated using Weblate (Vietnamese)
Currently translated at 100.0% (17 of 17 strings)
Translation: Tusky/Tusky description
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky-app/vi/
---
fastlane/metadata/android/vi/changelogs/91.txt | 6 ++++++
1 file changed, 6 insertions(+)
create mode 100644 fastlane/metadata/android/vi/changelogs/91.txt
diff --git a/fastlane/metadata/android/vi/changelogs/91.txt b/fastlane/metadata/android/vi/changelogs/91.txt
new file mode 100644
index 00000000..2835fdfc
--- /dev/null
+++ b/fastlane/metadata/android/vi/changelogs/91.txt
@@ -0,0 +1,6 @@
+Tusky v18.0
+
+- Hỗ trợ những kiểu thông báo mới của Mastodon 3.5
+- Nhãn của tài khoản nhìn đẹp hơn và thay đổi theo chủ đề
+- Cho phép chọn và sao chép nội dung tút
+- Sửa lỗi chặn đăng nhập trên Android 6 trở xuống
From 354b07aa737fba8c39840ed31ec4bf81e939a9e6 Mon Sep 17 00:00:00 2001
From: Agee Kalisz
Date: Fri, 13 May 2022 18:40:36 +0000
Subject: [PATCH 094/104] Translated using Weblate (Polish)
Currently translated at 100.0% (479 of 479 strings)
Co-authored-by: Agee Kalisz
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/pl/
Translation: Tusky/Tusky
---
app/src/main/res/values-pl/strings.xml | 50 +++++++++++++-------------
1 file changed, 26 insertions(+), 24 deletions(-)
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index a1424b3a..3fce8b6a 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -20,7 +20,7 @@
Strona główna
Powiadomienia
Lokalne
- Globalne
+ Sfederowane
Wątek
Wpisy
Z odpowiedziami
@@ -33,12 +33,12 @@
Edytuj profil
Szkice
Licencje
- %s podbił
- Wrażliwe treści
- Ukryto zawartość multimedialną
+ %s podbite
+ Treści wrażliwe
+ Ukryto multimedia
Naciśnij, aby wyświetlić
Pokaż więcej
- Ukryj
+ Pokaż mniej
Pusto tutaj. Pociągnij, aby odświeżyć!
%s podbił(-a) Twój wpis
%s dodał Twój post do ulubionych
@@ -95,13 +95,13 @@
Klawiatura emoji
Pobieranie %1$s
Skopiuj odnośnik
- Udostępnij odnośnik do wpisu…
+ Udostępnij URL do…
Udostępnij wpis do…
Wyślij!
Odblokowano użytkownika
Cofnięto wyciszenie użytkownika
Wyślij!
- Pomyślnie wysłano odpowiedź.
+ Odpowiedź wysłano pomyślnie.
Jaka instancja?
Co Ci chodzi po głowie?
Ostrzeżenie o zawartości
@@ -151,7 +151,7 @@
Używaj niestandardowych kart Chrome
Ukryj przycisk śledzenia podczas przewijania
Filtrowanie osi czasu
- Zakładki
+ Karty
Pokaż podbicia
Pokazuj odpowiedzi
Pokazuj podgląd zawartości multimedialnej
@@ -183,10 +183,10 @@
%1$s, %2$s, i %3$s
%1$s i %2$s
- - %d nowe powiadomienie
- - %d nowe powiadomienia
- - %d nowych powiadomień
- - %d nowych powiadomień
+ - %d nowa interakcja
+ - %d nowe interakcje
+ - %d nowych interakcji
+ - %d nowych interakcji
Konto zablokowane
O programie
@@ -404,25 +404,25 @@
Głosowanie w którym brałeś(-aś) udział zakończyła się
Ankieta, którą stworzyłeś(aś), zakończyła się
- - Zostało %d dzień
+ - Został %d dzień
- Zostało %d dni
- Zostało %d dni
- Zostało %d dni
- - Zostało %d godzina
+ - Została %d godzina
- Zostało %d godziny
- Zostało %d godzin
- Zostało %d godzin
- - Zostało %d minuta
+ - Została %d minuta
- Zostało %d minuty
- Zostało %d minut
- Zostało %d minut
- - Zostało %d sekunda
+ - Została %d sekunda
- Zostało %d sekund
- Zostało %d sekund
- Zostało %d sekund
@@ -462,7 +462,7 @@
Zakładki
Dodaj do zakładek
Zakładki
- Dodane do zakładek
+ Dodany do zakładek
Wybierz listę
Lista
Pliki audio muszą być mniejsze niż 40MB.
@@ -493,8 +493,8 @@
Dół
Góra
- - Nie możesz przesłać więcej niż %1$d załącznika.
- - Nie możesz przesłać więcej niż %1$d załączników.
+ - Nie możesz przesłać więcej niż %1$d załącznik.
+ - Nie możesz przesłać więcej niż %1$d załączniki.
- Nie możesz przesłać więcej niż %1$d załączników.
- Nie możesz przesłać więcej niż %1$d załączników.
@@ -515,21 +515,21 @@
Włącz gest przesuwania by przełączać między zakładkami
Załączniki
Powiadomienia o prośbach o obserwowanie
- ktoś kogo zasubskrybowałem/zasubskrybowałam opublikował nowy wpis
+ ktoś zasubskrybowany opublikował nowy wpis
Wysłano prośbę o obserwowanie
Ogłoszenia
Zdrowie
Anuluj subskrypcję
Zasubskrybuj
Mimo tego, że twoje konto nie jest zablokowane, administracja %1$s uznała, że możesz chcieć ręcznie przejrzeć te prośby o możliwość śledzenia od tych kont.
- Wpis dla którego naszkicowałeś/naszkicowałaś odpowiedź został usunięty
+ Wpis dla którego naszkicowałeś/aś odpowiedź został usunięty
Usunięto szkic
Ukryj ilościowe statystyki na profilach
Ukryj ilościowe statystyki na postach
Przejrzyj powiadomienia
Zapisano!
Twoja prywatna notatka o tym koncie
- Nieskończona
+ Nieograniczony
Dźwięk
Powiadomienia o opublikowaniu nowego wpisu przez kogoś, kogo obserwujesz
Pozycja głównego paska nawigacji
@@ -552,9 +552,11 @@
%s zarejestrował(a) się
Rejestracje
Powiadomienia o nowych użytkownikach
- Powiadomienia o edycji wpisów z którymi interaktowałeś/aś
+ Powiadomienia o edycji wpisów z którymi dokonałeś/aś interakcji
ktoś zarejestrował się
- wpis, z którym interaktowałem/am został edytowany
+ wpis, z którym dokonałem/am interakcji został edytowany
%s edytował(a) swój wpis
Edycje wpisów
+ Zapisywanie szkicu…
+ Nie można załadować strony logowania.
\ No newline at end of file
From 6f515ad98a26a148ee2929ada665dbbf9e1b00e2 Mon Sep 17 00:00:00 2001
From: Eric
Date: Fri, 13 May 2022 18:40:36 +0000
Subject: [PATCH 095/104] Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (479 of 479 strings)
Co-authored-by: Eric
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/zh_Hans/
Translation: Tusky/Tusky
---
app/src/main/res/values-zh-rCN/strings.xml | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index f3d7b2aa..862cf06c 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -536,4 +536,5 @@
嘟文编辑
当你进行过互动的嘟文被编辑时发出通知
无法加载登录页。
+ 正在保存草稿…
\ No newline at end of file
From 47eabafed38f5435c9238bcabd8fbd5a0c5b259e Mon Sep 17 00:00:00 2001
From: Ihor Hordiichuk
Date: Fri, 13 May 2022 18:40:36 +0000
Subject: [PATCH 096/104] Translated using Weblate (Ukrainian)
Currently translated at 100.0% (479 of 479 strings)
Co-authored-by: Ihor Hordiichuk
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/uk/
Translation: Tusky/Tusky
---
app/src/main/res/values-uk/strings.xml | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 6c510097..5998c9b0 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -550,4 +550,5 @@
Редакції допису
Вхід
Не вдалося завантажити сторінку входу.
+ Збереження чернетки…
\ No newline at end of file
From a6dc7ef425872bfe0fd4a3c14f996d2c829b8ac6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=E1=BB=93=20Nh=E1=BA=A5t=20Duy?=
Date: Fri, 13 May 2022 18:40:36 +0000
Subject: [PATCH 097/104] Translated using Weblate (Vietnamese)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Currently translated at 100.0% (479 of 479 strings)
Co-authored-by: Hồ Nhất Duy
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/
Translation: Tusky/Tusky
---
app/src/main/res/values-vi/strings.xml | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
index 0c8109db..bdf3af84 100644
--- a/app/src/main/res/values-vi/strings.xml
+++ b/app/src/main/res/values-vi/strings.xml
@@ -517,4 +517,5 @@
Thông báo khi tút mà tôi tương tác bị sửa
Đăng nhập
Không thể tải trang đăng nhập.
+ Đang lưu nháp…
\ No newline at end of file
From 8fc2c1601eaa74c1b98eaacd8fcca32a266f7299 Mon Sep 17 00:00:00 2001
From: GunChleoc
Date: Fri, 13 May 2022 18:40:37 +0000
Subject: [PATCH 098/104] Translated using Weblate (Gaelic)
Currently translated at 100.0% (479 of 479 strings)
Translated using Weblate (Gaelic)
Currently translated at 100.0% (479 of 479 strings)
Co-authored-by: GunChleoc
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/gd/
Translation: Tusky/Tusky
---
app/src/main/res/values-gd/strings.xml | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/app/src/main/res/values-gd/strings.xml b/app/src/main/res/values-gd/strings.xml
index e27b677a..d01b0dca 100644
--- a/app/src/main/res/values-gd/strings.xml
+++ b/app/src/main/res/values-gd/strings.xml
@@ -295,7 +295,7 @@
Cuir post air an sgeideal
Faicsinneachd a’ phuist
Postaichean air an sgeideal
- Chuir %s am post agad ris na h-annsachdan
+ Is annsa le %s am post agad
Bhrosnaich %s am post agad
Postaichean air an sgeideal
Snàithlean
@@ -556,4 +556,5 @@
chaidh post a rinn mi conaltradh leis a deasachadh
Clàraich a-steach
Cha b’ urrainn dhuinn duilleag a’ chlàraidh a-steach fhosgladh.
+ A’ sàbhaladh na dreuchd…
\ No newline at end of file
From 8c6ccf426103b4ceac93b14d981f1f009f980225 Mon Sep 17 00:00:00 2001
From: Konrad Pozniak
Date: Fri, 13 May 2022 22:00:30 +0200
Subject: [PATCH 099/104] fix notification message formatting when username is
not at the beginning of the message (#2522)
* fix notification message formatting when username is not at the beginning of the message
* search for placeholder in format string
---
.../tusky/adapter/NotificationsAdapter.java | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
index f681e64f..327e4aa7 100644
--- a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
+++ b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
@@ -531,8 +531,13 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
message.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
String wholeMessage = String.format(format, displayName);
final SpannableStringBuilder str = new SpannableStringBuilder(wholeMessage);
- str.setSpan(new StyleSpan(Typeface.BOLD), 0, displayName.length(),
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ int displayNameIndex = format.indexOf("%s");
+ str.setSpan(
+ new StyleSpan(Typeface.BOLD),
+ displayNameIndex,
+ displayNameIndex + displayName.length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
+ );
CharSequence emojifiedText = CustomEmojiHelper.emojify(
str, notificationViewData.getAccount().getEmojis(), message, statusDisplayOptions.animateEmojis()
);
From ec72cd0b52716e297ef256a75205f9129e642256 Mon Sep 17 00:00:00 2001
From: ButterflyOfFire
Date: Tue, 17 May 2022 09:40:38 +0000
Subject: [PATCH 100/104] Translated using Weblate (French)
Currently translated at 99.5% (477 of 479 strings)
Co-authored-by: ButterflyOfFire
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/fr/
Translation: Tusky/Tusky
---
app/src/main/res/values-fr/strings.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index dc30483b..d6b6e44f 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -295,7 +295,7 @@
Mettre une légende
Supprimer le média
Verrouiller le compte
- Vous devez approuvez manuellement les abonnements
+ Vous devez approuver manuellement les abonnements
Enregistrer comme brouillon ?
Envoi du pouet…
Erreur lors de l’envoi du pouet
From 725ce02ab1feb041cca51a62330c7b2d1e1a7416 Mon Sep 17 00:00:00 2001
From: hebbeff
Date: Tue, 17 May 2022 09:40:38 +0000
Subject: [PATCH 101/104] Translated using Weblate (Chinese (Traditional))
Currently translated at 91.6% (439 of 479 strings)
Co-authored-by: hebbeff
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/zh_Hant/
Translation: Tusky/Tusky
---
app/src/main/res/values-zh-rTW/strings.xml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index 9fd135f4..02d7d2ed 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -525,4 +525,6 @@
總是顯示被標注為內容警告的嘟文
搜尋失敗
帳號
+ 登入
+ 無法載入登入頁面。
\ No newline at end of file
From 0bf71e642017382347042c1f84a764cc7b243b76 Mon Sep 17 00:00:00 2001
From: GunChleoc
Date: Tue, 17 May 2022 09:40:38 +0000
Subject: [PATCH 102/104] Translated using Weblate (Gaelic)
Currently translated at 100.0% (479 of 479 strings)
Co-authored-by: GunChleoc
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/gd/
Translation: Tusky/Tusky
---
app/src/main/res/values-gd/strings.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/src/main/res/values-gd/strings.xml b/app/src/main/res/values-gd/strings.xml
index d01b0dca..317331dc 100644
--- a/app/src/main/res/values-gd/strings.xml
+++ b/app/src/main/res/values-gd/strings.xml
@@ -92,7 +92,7 @@
Brathan nuair a dh’fhoillsich cuideigin air a rinn mi fo-sgrìobhadh post ùr
Postaichean ùra
dh’fhoillsich cuideigin air a rinn mi fo-sgrìobhadh post ùr
- Tha %s air rud a phostadh
+ Phostaich %s rud
Chan eil brath-fios ann.
Brathan-fios
Chaidh a shàbhaladh!
From 74e139c11014772249fb7b85a24016555c3d2811 Mon Sep 17 00:00:00 2001
From: hebbeff
Date: Tue, 17 May 2022 09:40:38 +0000
Subject: [PATCH 103/104] Translated using Weblate (Chinese (Simplified))
Currently translated at 88.2% (15 of 17 strings)
Translation: Tusky/Tusky description
Translate-URL: https://weblate.tusky.app/projects/tusky/tusky-app/zh_Hans/
---
fastlane/metadata/android/zh-Hans/changelogs/83.txt | 3 +++
fastlane/metadata/android/zh-Hans/changelogs/87.txt | 8 ++++++++
2 files changed, 11 insertions(+)
create mode 100644 fastlane/metadata/android/zh-Hans/changelogs/83.txt
create mode 100644 fastlane/metadata/android/zh-Hans/changelogs/87.txt
diff --git a/fastlane/metadata/android/zh-Hans/changelogs/83.txt b/fastlane/metadata/android/zh-Hans/changelogs/83.txt
new file mode 100644
index 00000000..e8f7c36e
--- /dev/null
+++ b/fastlane/metadata/android/zh-Hans/changelogs/83.txt
@@ -0,0 +1,3 @@
+Tusky v15.1
+
+此版本修复了给图片添加标题时会崩溃的问题
diff --git a/fastlane/metadata/android/zh-Hans/changelogs/87.txt b/fastlane/metadata/android/zh-Hans/changelogs/87.txt
new file mode 100644
index 00000000..06fcd290
--- /dev/null
+++ b/fastlane/metadata/android/zh-Hans/changelogs/87.txt
@@ -0,0 +1,8 @@
+Tusky v16.0
+
+- 时间线加载逻辑完全重写,提升了流畅度、稳定性,更便于维护。
+- APNG和动画WebP格式的动态自定义表情符号。
+- 修正大量BUG
+- 支持Android 11
+- 新增界面语言支持:苏格兰盖尔语、加利西亚语、乌克兰语
+- 改进翻译
From 20f3ec921f4040b1a1ca69ee89aa9f27b3561c2f Mon Sep 17 00:00:00 2001
From: Conny Duck
Date: Tue, 17 May 2022 19:24:17 +0200
Subject: [PATCH 104/104] Release 91
---
app/build.gradle | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index 2a34c80a..34e941ec 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -24,8 +24,8 @@ android {
applicationId APP_ID
minSdkVersion 21
targetSdkVersion 31
- versionCode 90
- versionName "18.0 beta 1"
+ versionCode 91
+ versionName "18.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true