From b91a0aceebcf0359be61000effe7242b5ec1cd67 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 23 Dec 2020 14:52:39 +0300 Subject: [PATCH 01/81] Notification bell (#2012) * Add notification bell button, API endpoints and new relationship field * Add notification type for subscriptions * Add subscriptions to legacy notification filtering * Update schemas * Fix build * Make rewrite static method into method of Notification class, fix getNotificationText * Mastodon wording for subscriptions --- .../24.json | 747 ++++++++++++++++++ .../keylesspalace/tusky/AccountActivity.kt | 35 +- .../tusky/adapter/NotificationsAdapter.java | 59 +- .../notifications/NotificationHelper.java | 18 +- .../NotificationPreferencesFragment.kt | 11 + .../keylesspalace/tusky/db/AccountEntity.kt | 1 + .../keylesspalace/tusky/db/AppDatabase.java | 9 +- .../com/keylesspalace/tusky/di/AppModule.kt | 4 +- .../tusky/entity/Notification.kt | 15 +- .../tusky/entity/Relationship.kt | 4 +- .../tusky/fragment/NotificationsFragment.java | 6 +- .../tusky/network/MastodonApi.kt | 13 +- .../tusky/settings/SettingsConstants.kt | 1 + .../tusky/viewmodel/AccountViewModel.kt | 38 +- .../drawable/ic_notifications_active_24dp.xml | 13 + app/src/main/res/layout/activity_account.xml | 22 +- .../res/layout/item_status_notification.xml | 2 - app/src/main/res/values/strings.xml | 5 +- 18 files changed, 970 insertions(+), 33 deletions(-) create mode 100644 app/schemas/com.keylesspalace.tusky.db.AppDatabase/24.json create mode 100644 app/src/main/res/drawable/ic_notifications_active_24dp.xml diff --git a/app/schemas/com.keylesspalace.tusky.db.AppDatabase/24.json b/app/schemas/com.keylesspalace.tusky.db.AppDatabase/24.json new file mode 100644 index 00000000..c47a305b --- /dev/null +++ b/app/schemas/com.keylesspalace.tusky.db.AppDatabase/24.json @@ -0,0 +1,747 @@ +{ + "formatVersion": 1, + "database": { + "version": 24, + "identityHash": "ea8559bbdf434c7b9086384a9a4cc8e6", + "entities": [ + { + "tableName": "TootEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `text` TEXT, `urls` TEXT, `descriptions` TEXT, `contentWarning` TEXT, `inReplyToId` TEXT, `inReplyToText` TEXT, `inReplyToUsername` TEXT, `visibility` INTEGER, `poll` TEXT)", + "fields": [ + { + "fieldPath": "uid", + "columnName": "uid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "urls", + "columnName": "urls", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "descriptions", + "columnName": "descriptions", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "contentWarning", + "columnName": "contentWarning", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inReplyToId", + "columnName": "inReplyToId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inReplyToText", + "columnName": "inReplyToText", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inReplyToUsername", + "columnName": "inReplyToUsername", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "visibility", + "columnName": "visibility", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "poll", + "columnName": "poll", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "uid" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AccountEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `domain` TEXT NOT NULL, `accessToken` TEXT NOT NULL, `isActive` INTEGER NOT NULL, `accountId` TEXT NOT NULL, `username` TEXT NOT NULL, `displayName` TEXT NOT NULL, `profilePictureUrl` TEXT NOT NULL, `notificationsEnabled` INTEGER NOT NULL, `notificationsMentioned` INTEGER NOT NULL, `notificationsFollowed` INTEGER NOT NULL, `notificationsFollowRequested` INTEGER NOT NULL, `notificationsReblogged` INTEGER NOT NULL, `notificationsFavorited` INTEGER NOT NULL, `notificationsPolls` INTEGER NOT NULL, `notificationsSubscriptions` 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": "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" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_AccountEntity_domain_accountId` ON `${TABLE_NAME}` (`domain`, `accountId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "InstanceEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`instance` TEXT NOT NULL, `emojiList` TEXT, `maximumTootCharacters` INTEGER, `maxPollOptions` INTEGER, `maxPollOptionLength` INTEGER, `version` TEXT, PRIMARY KEY(`instance`))", + "fields": [ + { + "fieldPath": "instance", + "columnName": "instance", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "emojiList", + "columnName": "emojiList", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "maximumTootCharacters", + "columnName": "maximumTootCharacters", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "maxPollOptions", + "columnName": "maxPollOptions", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "maxPollOptionLength", + "columnName": "maxPollOptionLength", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "instance" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimelineStatusEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serverId` TEXT NOT NULL, `url` TEXT, `timelineUserId` INTEGER NOT NULL, `authorServerId` TEXT, `inReplyToId` TEXT, `inReplyToAccountId` TEXT, `content` TEXT, `createdAt` INTEGER NOT NULL, `emojis` TEXT, `reblogsCount` INTEGER NOT NULL, `favouritesCount` INTEGER NOT NULL, `reblogged` INTEGER NOT NULL, `bookmarked` INTEGER NOT NULL, `favourited` INTEGER NOT NULL, `sensitive` INTEGER NOT NULL, `spoilerText` TEXT, `visibility` INTEGER, `attachments` TEXT, `mentions` TEXT, `application` TEXT, `reblogServerId` TEXT, `reblogAccountId` TEXT, `poll` TEXT, `muted` INTEGER, PRIMARY KEY(`serverId`, `timelineUserId`), FOREIGN KEY(`authorServerId`, `timelineUserId`) REFERENCES `TimelineAccountEntity`(`serverId`, `timelineUserId`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "serverId", + "columnName": "serverId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "timelineUserId", + "columnName": "timelineUserId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "authorServerId", + "columnName": "authorServerId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inReplyToId", + "columnName": "inReplyToId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inReplyToAccountId", + "columnName": "inReplyToAccountId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "emojis", + "columnName": "emojis", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "reblogsCount", + "columnName": "reblogsCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "favouritesCount", + "columnName": "favouritesCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "reblogged", + "columnName": "reblogged", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "bookmarked", + "columnName": "bookmarked", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "favourited", + "columnName": "favourited", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sensitive", + "columnName": "sensitive", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "spoilerText", + "columnName": "spoilerText", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "visibility", + "columnName": "visibility", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "mentions", + "columnName": "mentions", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "application", + "columnName": "application", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "reblogServerId", + "columnName": "reblogServerId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "reblogAccountId", + "columnName": "reblogAccountId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "poll", + "columnName": "poll", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "muted", + "columnName": "muted", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "serverId", + "timelineUserId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_TimelineStatusEntity_authorServerId_timelineUserId", + "unique": false, + "columnNames": [ + "authorServerId", + "timelineUserId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_TimelineStatusEntity_authorServerId_timelineUserId` ON `${TABLE_NAME}` (`authorServerId`, `timelineUserId`)" + } + ], + "foreignKeys": [ + { + "table": "TimelineAccountEntity", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "authorServerId", + "timelineUserId" + ], + "referencedColumns": [ + "serverId", + "timelineUserId" + ] + } + ] + }, + { + "tableName": "TimelineAccountEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serverId` TEXT NOT NULL, `timelineUserId` INTEGER NOT NULL, `localUsername` TEXT NOT NULL, `username` TEXT NOT NULL, `displayName` TEXT NOT NULL, `url` TEXT NOT NULL, `avatar` TEXT NOT NULL, `emojis` TEXT NOT NULL, `bot` INTEGER NOT NULL, PRIMARY KEY(`serverId`, `timelineUserId`))", + "fields": [ + { + "fieldPath": "serverId", + "columnName": "serverId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timelineUserId", + "columnName": "timelineUserId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "localUsername", + "columnName": "localUsername", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "username", + "columnName": "username", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "displayName", + "columnName": "displayName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatar", + "columnName": "avatar", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "emojis", + "columnName": "emojis", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "bot", + "columnName": "bot", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "serverId", + "timelineUserId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ConversationEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`accountId` INTEGER NOT NULL, `id` TEXT NOT NULL, `accounts` TEXT NOT NULL, `unread` INTEGER NOT NULL, `s_id` TEXT NOT NULL, `s_url` TEXT, `s_inReplyToId` TEXT, `s_inReplyToAccountId` TEXT, `s_account` TEXT NOT NULL, `s_content` TEXT NOT NULL, `s_createdAt` INTEGER NOT NULL, `s_emojis` TEXT NOT NULL, `s_favouritesCount` INTEGER NOT NULL, `s_favourited` INTEGER NOT NULL, `s_bookmarked` INTEGER NOT NULL, `s_sensitive` INTEGER NOT NULL, `s_spoilerText` TEXT NOT NULL, `s_attachments` TEXT NOT NULL, `s_mentions` TEXT NOT NULL, `s_showingHiddenContent` INTEGER NOT NULL, `s_expanded` INTEGER NOT NULL, `s_collapsible` INTEGER NOT NULL, `s_collapsed` INTEGER NOT NULL, `s_poll` TEXT, PRIMARY KEY(`id`, `accountId`))", + "fields": [ + { + "fieldPath": "accountId", + "columnName": "accountId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accounts", + "columnName": "accounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.id", + "columnName": "s_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.url", + "columnName": "s_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastStatus.inReplyToId", + "columnName": "s_inReplyToId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastStatus.inReplyToAccountId", + "columnName": "s_inReplyToAccountId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastStatus.account", + "columnName": "s_account", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.content", + "columnName": "s_content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.createdAt", + "columnName": "s_createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.emojis", + "columnName": "s_emojis", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.favouritesCount", + "columnName": "s_favouritesCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.favourited", + "columnName": "s_favourited", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.bookmarked", + "columnName": "s_bookmarked", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.sensitive", + "columnName": "s_sensitive", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.spoilerText", + "columnName": "s_spoilerText", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.attachments", + "columnName": "s_attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.mentions", + "columnName": "s_mentions", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.showingHiddenContent", + "columnName": "s_showingHiddenContent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.expanded", + "columnName": "s_expanded", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.collapsible", + "columnName": "s_collapsible", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.collapsed", + "columnName": "s_collapsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.poll", + "columnName": "s_poll", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id", + "accountId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ea8559bbdf434c7b9086384a9a4cc8e6')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt index 478d8aff..1a725d34 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt @@ -84,6 +84,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI private var muting: Boolean = false private var blockingDomain: Boolean = false private var showingReblogs: Boolean = false + private var subscribing: Boolean = false private var loadedAccount: Account? = null private var animateAvatar: Boolean = false @@ -116,7 +117,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI loadResources() makeNotificationBarTransparent() setContentView(R.layout.activity_account) - + // Obtain information to fill out the profile. viewModel.setAccountInfo(intent.getStringExtra(KEY_ACCOUNT_ID)!!) @@ -159,7 +160,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI accountMuteButton.hide() accountFollowsYouTextView.hide() - // setup the RecyclerView for the account fields accountFieldList.isNestedScrollingEnabled = false accountFieldList.layoutManager = LinearLayoutManager(this) @@ -185,7 +185,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI poorTabView.isPressed = true accountTabLayout.postDelayed({ poorTabView.isPressed = false }, 300) } - } /** @@ -374,7 +373,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI accountFieldAdapter.emojis = account.emojis ?: emptyList() accountFieldAdapter.notifyDataSetChanged() - accountLockedImageView.visible(account.locked) accountBadgeTextView.visible(account.bot) @@ -538,6 +536,20 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI accountFollowsYouTextView.visible(relation.followedBy) + // because subscribing is Pleroma extension, enable it __only__ when we have non-null subscribing field + // it's also now supported in Mastodon 3.3.0rc but called notifying and use different API call + if(!viewModel.isSelf && followState == FollowState.FOLLOWING + && (relation.subscribing != null || relation.notifying != null)) { + accountSubscribeButton.show() + accountSubscribeButton.setOnClickListener { + viewModel.changeSubscribingState() + } + if(relation.notifying != null) + subscribing = relation.notifying + else if(relation.subscribing != null) + subscribing = relation.subscribing + } + accountNoteTextInputLayout.visible(relation.note != null) accountNoteTextInputLayout.editText?.setText(relation.note) @@ -574,6 +586,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI accountFollowButton.setText(R.string.action_unfollow) } } + updateSubscribeButton() } private fun updateMuteButton() { @@ -584,6 +597,18 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI } } + private fun updateSubscribeButton() { + if(followState != FollowState.FOLLOWING) { + accountSubscribeButton.hide() + } + + if(subscribing) { + accountSubscribeButton.setIconResource(R.drawable.ic_notifications_active_24dp) + } else { + accountSubscribeButton.setIconResource(R.drawable.ic_notifications_24dp) + } + } + private fun updateButtons() { invalidateOptionsMenu() @@ -595,6 +620,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI if (blocking || viewModel.isSelf) { accountFloatingActionButton.hide() accountMuteButton.hide() + accountSubscribeButton.hide() } else { accountFloatingActionButton.show() if (muting) @@ -608,6 +634,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI accountFloatingActionButton.hide() accountFollowButton.hide() accountMuteButton.hide() + accountSubscribeButton.hide() } } 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 dcaf4169..d879bac0 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java @@ -37,6 +37,7 @@ import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; import androidx.recyclerview.widget.RecyclerView; +import com.bumptech.glide.Glide; import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.entity.Emoji; @@ -198,8 +199,12 @@ public class NotificationsAdapter extends RecyclerView.Adapter { holder.setUsername(statusViewData.getNickname()); holder.setCreatedAt(statusViewData.getCreatedAt()); - holder.setAvatars(concreteNotificaton.getStatusViewData().getAvatar(), - concreteNotificaton.getAccount().getAvatar()); + if(concreteNotificaton.getType() == Notification.Type.STATUS) { + holder.setAvatar(statusViewData.getAvatar(), statusViewData.isBot()); + } else { + holder.setAvatars(statusViewData.getAvatar(), + concreteNotificaton.getAccount().getAvatar()); + } } holder.setMessage(concreteNotificaton, statusListener); @@ -267,6 +272,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter { case POLL: { return VIEW_TYPE_STATUS; } + case STATUS: case FAVOURITE: case REBLOG: { return VIEW_TYPE_STATUS_NOTIFICATION; @@ -373,6 +379,10 @@ public class NotificationsAdapter extends RecyclerView.Adapter { private StatusViewData.Concrete statusViewData; private SimpleDateFormat shortSdf; private SimpleDateFormat longSdf; + + private int avatarRadius48dp; + private int avatarRadius36dp; + private int avatarRadius24dp; StatusNotificationViewHolder(View itemView, StatusDisplayOptions statusDisplayOptions) { super(itemView); @@ -398,6 +408,10 @@ public class NotificationsAdapter extends RecyclerView.Adapter { 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); + this.avatarRadius24dp = itemView.getContext().getResources().getDimensionPixelSize(R.dimen.avatar_radius_24dp); } private void showNotificationContent(boolean show) { @@ -488,6 +502,16 @@ public class NotificationsAdapter extends RecyclerView.Adapter { 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); + } + + format = context.getString(R.string.notification_subscription_format); + break; + } } message.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null); String wholeMessage = String.format(format, displayName); @@ -526,19 +550,34 @@ public class NotificationsAdapter extends RecyclerView.Adapter { this.notificationId = notificationId; } - void setAvatars(@Nullable String statusAvatarUrl, @Nullable String notificationAvatarUrl) { - - int statusAvatarRadius = statusAvatar.getContext().getResources() - .getDimensionPixelSize(R.dimen.avatar_radius_36dp); + void setAvatar(@Nullable String statusAvatarUrl, boolean isBot) { + statusAvatar.setPaddingRelative(0, 0, 0, 0); ImageLoadingHelper.loadAvatar(statusAvatarUrl, - statusAvatar, statusAvatarRadius, statusDisplayOptions.animateAvatars()); + statusAvatar, avatarRadius48dp, statusDisplayOptions.animateAvatars()); - int notificationAvatarRadius = statusAvatar.getContext().getResources() - .getDimensionPixelSize(R.dimen.avatar_radius_24dp); + if (statusDisplayOptions.showBotOverlay() && isBot) { + notificationAvatar.setVisibility(View.VISIBLE); + notificationAvatar.setBackgroundColor(0x50ffffff); + Glide.with(notificationAvatar) + .load(R.drawable.ic_bot_24dp) + .into(notificationAvatar); + } else { + notificationAvatar.setVisibility(View.GONE); + } + } + + void setAvatars(@Nullable String statusAvatarUrl, @Nullable String notificationAvatarUrl) { + int padding = Utils.dpToPx(statusAvatar.getContext(), 12); + statusAvatar.setPaddingRelative(0, 0, padding, padding); + + ImageLoadingHelper.loadAvatar(statusAvatarUrl, + statusAvatar, avatarRadius36dp, statusDisplayOptions.animateAvatars()); + + notificationAvatar.setVisibility(View.VISIBLE); ImageLoadingHelper.loadAvatar(notificationAvatarUrl, notificationAvatar, - notificationAvatarRadius, statusDisplayOptions.animateAvatars()); + avatarRadius24dp, statusDisplayOptions.animateAvatars()); } @Override 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 14c9e480..87a5d261 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 @@ -121,7 +121,7 @@ public class NotificationHelper { public static final String CHANNEL_BOOST = "CHANNEL_BOOST"; public static final String CHANNEL_FAVOURITE = "CHANNEL_FAVOURITE"; public static final String CHANNEL_POLL = "CHANNEL_POLL"; - + public static final String CHANNEL_SUBSCRIPTIONS = "CHANNEL_SUBSCRIPTIONS"; /** * WorkManager Tag @@ -138,6 +138,7 @@ public class NotificationHelper { */ public static void make(final Context context, Notification body, AccountEntity account, boolean isFirstOfBatch) { + body = body.rewriteToStatusTypeIfNeeded(account.getAccountId()); if (!filterNotification(account, body, context)) { return; @@ -355,6 +356,7 @@ public class NotificationHelper { CHANNEL_BOOST + account.getIdentifier(), CHANNEL_FAVOURITE + account.getIdentifier(), CHANNEL_POLL + account.getIdentifier(), + CHANNEL_SUBSCRIPTIONS + account.getIdentifier(), }; int[] channelNames = { R.string.notification_mention_name, @@ -362,7 +364,8 @@ public class NotificationHelper { R.string.notification_follow_request_name, R.string.notification_boost_name, R.string.notification_favourite_name, - R.string.notification_poll_name + R.string.notification_poll_name, + R.string.notification_subscription_name, }; int[] channelDescriptions = { R.string.notification_mention_descriptions, @@ -370,7 +373,8 @@ public class NotificationHelper { R.string.notification_follow_request_description, R.string.notification_boost_description, R.string.notification_favourite_description, - R.string.notification_poll_description + R.string.notification_poll_description, + R.string.notification_subscription_description, }; List channels = new ArrayList<>(6); @@ -516,6 +520,8 @@ public class NotificationHelper { switch (notification.getType()) { case MENTION: return account.getNotificationsMentioned(); + case STATUS: + return account.getNotificationsSubscriptions(); case FOLLOW: return account.getNotificationsFollowed(); case FOLLOW_REQUEST: @@ -536,6 +542,8 @@ public class NotificationHelper { switch (notification.getType()) { case MENTION: return CHANNEL_MENTION + account.getIdentifier(); + case STATUS: + return CHANNEL_SUBSCRIPTIONS + account.getIdentifier(); case FOLLOW: return CHANNEL_FOLLOW + account.getIdentifier(); case FOLLOW_REQUEST: @@ -606,6 +614,9 @@ public class NotificationHelper { case MENTION: return String.format(context.getString(R.string.notification_mention_format), accountName); + case STATUS: + return String.format(context.getString(R.string.notification_subscription_format), + accountName); case FOLLOW: return String.format(context.getString(R.string.notification_follow_format), accountName); @@ -636,6 +647,7 @@ public class NotificationHelper { case MENTION: case FAVOURITE: case REBLOG: + case STATUS: if (!TextUtils.isEmpty(notification.getStatus().getSpoilerText())) { return notification.getStatus().getSpoilerText(); } else { 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 f917d2bb..1e90abc8 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 @@ -111,6 +111,17 @@ class NotificationPreferencesFragment : PreferenceFragmentCompat(), Injectable { true } } + + switchPreference { + setTitle(R.string.pref_title_notification_filter_subscriptions) + key = PrefKeys.NOTIFICATION_FILTER_SUBSCRIPTIONS + isIconSpaceReserved = false + isChecked = activeAccount.notificationsSubscriptions + setOnPreferenceChangeListener { _, newValue -> + updateAccount { it.notificationsSubscriptions = 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 77ce16cc..ab6dbb7e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt @@ -43,6 +43,7 @@ data class AccountEntity(@field:PrimaryKey(autoGenerate = true) var id: Long, var notificationsReblogged: Boolean = true, var notificationsFavorited: Boolean = true, var notificationsPolls: Boolean = true, + var notificationsSubscriptions: 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 608333d9..da3aba59 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java +++ b/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java @@ -30,7 +30,7 @@ import androidx.annotation.NonNull; @Database(entities = {TootEntity.class, AccountEntity.class, InstanceEntity.class, TimelineStatusEntity.class, TimelineAccountEntity.class, ConversationEntity.class - }, version = 23) + }, version = 24) public abstract class AppDatabase extends RoomDatabase { public abstract TootDao tootDao(); @@ -339,5 +339,12 @@ public abstract class AppDatabase extends RoomDatabase { database.execSQL("ALTER TABLE `TimelineStatusEntity` ADD COLUMN `muted` INTEGER"); } }; + + public static final Migration MIGRATION_23_24 = new Migration(23, 24) { + @Override + public void migrate(@NonNull SupportSQLiteDatabase database) { + database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `notificationsSubscriptions` 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 aedcc3ae..7e86bbac 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt @@ -80,7 +80,7 @@ class AppModule { AppDatabase.MIGRATION_13_14, AppDatabase.MIGRATION_14_15, AppDatabase.MIGRATION_15_16, AppDatabase.MIGRATION_16_17, AppDatabase.MIGRATION_17_18, AppDatabase.MIGRATION_18_19, AppDatabase.MIGRATION_19_20, AppDatabase.MIGRATION_20_21, AppDatabase.MIGRATION_21_22, - AppDatabase.MIGRATION_22_23) + AppDatabase.MIGRATION_22_23, AppDatabase.MIGRATION_23_24) .build() } @@ -88,4 +88,4 @@ class AppModule { @Singleton fun notifier(context: Context): Notifier = SystemNotifier(context) -} \ No newline at end of file +} 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 fe342e7a..5a169b11 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/Notification.kt +++ b/app/src/main/java/com/keylesspalace/tusky/entity/Notification.kt @@ -32,7 +32,8 @@ data class Notification( FAVOURITE("favourite"), FOLLOW("follow"), FOLLOW_REQUEST("follow_request"), - POLL("poll"); + POLL("poll"), + STATUS("status"); companion object { @@ -44,7 +45,7 @@ data class Notification( } return UNKNOWN } - val asList = listOf(MENTION, REBLOG, FAVOURITE, FOLLOW, FOLLOW_REQUEST, POLL) + val asList = listOf(MENTION, REBLOG, FAVOURITE, FOLLOW, FOLLOW_REQUEST, POLL, STATUS) } override fun toString(): String { @@ -72,4 +73,14 @@ data class Notification( } } + + // for Pleroma compatibility that uses Mention type + fun rewriteToStatusTypeIfNeeded(accountId: String) : Notification { + if (type == Type.MENTION && status != null) { + return if (status.mentions.any { + it.id == accountId + }) this else copy(type = Type.STATUS) + } + return this + } } diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Relationship.kt b/app/src/main/java/com/keylesspalace/tusky/entity/Relationship.kt index e6a75c51..e25a3d10 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/Relationship.kt +++ b/app/src/main/java/com/keylesspalace/tusky/entity/Relationship.kt @@ -26,6 +26,8 @@ data class Relationship ( @SerializedName("muting_notifications") val mutingNotifications: Boolean, val requested: Boolean, @SerializedName("showing_reblogs") val showingReblogs: Boolean, + val subscribing: Boolean? = null, // Pleroma extension @SerializedName("domain_blocking") val blockingDomain: Boolean, - val note: String? // nullable for backward compatibility / feature detection + val note: String?, // nullable for backward compatibility / feature detection + val notifying: Boolean? // since 3.3.0rc ) 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 6014ac9b..9d88d261 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java @@ -179,7 +179,9 @@ public class NotificationsFragment extends SFragment implements @Override public NotificationViewData apply(Either input) { if (input.isRight()) { - Notification notification = input.asRight(); + Notification notification = input.asRight() + .rewriteToStatusTypeIfNeeded(accountManager.getActiveAccount().getAccountId()); + return ViewDataUtils.notificationToViewData( notification, alwaysShowSensitiveMedia, @@ -770,6 +772,8 @@ public class NotificationsFragment extends SFragment implements return getString(R.string.notification_follow_request_name); case POLL: return getString(R.string.notification_poll_name); + case STATUS: + return getString(R.string.notification_subscription_name); default: return "Unknown"; } 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 8f3dab3f..08a5483d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt +++ b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt @@ -307,7 +307,8 @@ interface MastodonApi { @POST("api/v1/accounts/{id}/follow") fun followAccount( @Path("id") accountId: String, - @Field("reblogs") showReblogs: Boolean + @Field("reblogs") showReblogs: Boolean? = null, + @Field("notify") notify: Boolean? = null ): Single @POST("api/v1/accounts/{id}/unfollow") @@ -347,6 +348,16 @@ interface MastodonApi { @Path("id") accountId: String ): Single> + @POST("api/v1/pleroma/accounts/{id}/subscribe") + fun subscribeAccount( + @Path("id") accountId: String + ): Single + + @POST("api/v1/pleroma/accounts/{id}/unsubscribe") + fun unsubscribeAccount( + @Path("id") accountId: String + ): Single + @GET("api/v1/blocks") fun blocks( @Query("max_id") maxId: String? 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 df9627d8..c3c26782 100644 --- a/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt +++ b/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt @@ -53,6 +53,7 @@ object PrefKeys { const val NOTIFICATION_FILTER_REBLOGS = "notificationFilterReblogs" const val NOTIFICATION_FILTER_FOLLOW_REQUESTS = "notificationFilterFollowRequests" const val NOTIFICATIONS_FILTER_FOLLOWS = "notificationFilterFollows" + const val NOTIFICATION_FILTER_SUBSCRIPTIONS = "notificationFilterSubscriptions" const val TAB_FILTER_HOME_REPLIES = "tabFilterHomeReplies" const val TAB_FILTER_HOME_BOOSTS = "tabFilterHomeBoosts" diff --git a/app/src/main/java/com/keylesspalace/tusky/viewmodel/AccountViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/viewmodel/AccountViewModel.kt index 593f43d3..b2568f34 100644 --- a/app/src/main/java/com/keylesspalace/tusky/viewmodel/AccountViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/viewmodel/AccountViewModel.kt @@ -126,6 +126,16 @@ class AccountViewModel @Inject constructor( fun unmuteAccount() { changeRelationship(RelationShipAction.UNMUTE) } + + fun changeSubscribingState() { + val relationship = relationshipData.value?.data + if(relationship?.notifying == true /* Mastodon 3.3.0rc1 */ + || relationship?.subscribing == true /* Pleroma */ ) { + changeRelationship(RelationShipAction.UNSUBSCRIBE) + } else { + changeRelationship(RelationShipAction.SUBSCRIBE) + } + } fun blockDomain(instance: String) { mastodonApi.blockDomain(instance).enqueue(object: Callback { @@ -180,6 +190,7 @@ class AccountViewModel @Inject constructor( private fun changeRelationship(relationshipAction: RelationShipAction, parameter: Boolean? = null) { val relation = relationshipData.value?.data val account = accountData.value?.data + val isMastodon = relationshipData.value?.data?.notifying != null if (relation != null && account != null) { // optimistically post new state for faster response @@ -197,17 +208,37 @@ class AccountViewModel @Inject constructor( RelationShipAction.UNBLOCK -> relation.copy(blocking = false) RelationShipAction.MUTE -> relation.copy(muting = true) RelationShipAction.UNMUTE -> relation.copy(muting = false) + RelationShipAction.SUBSCRIBE -> { + if(isMastodon) + relation.copy(notifying = true) + else relation.copy(subscribing = true) + } + RelationShipAction.UNSUBSCRIBE -> { + if(isMastodon) + relation.copy(notifying = false) + else relation.copy(subscribing = false) + } } relationshipData.postValue(Loading(newRelation)) } when (relationshipAction) { - RelationShipAction.FOLLOW -> mastodonApi.followAccount(accountId, parameter ?: true) + RelationShipAction.FOLLOW -> mastodonApi.followAccount(accountId, showReblogs = parameter ?: true) RelationShipAction.UNFOLLOW -> mastodonApi.unfollowAccount(accountId) RelationShipAction.BLOCK -> mastodonApi.blockAccount(accountId) RelationShipAction.UNBLOCK -> mastodonApi.unblockAccount(accountId) RelationShipAction.MUTE -> mastodonApi.muteAccount(accountId, parameter ?: true) RelationShipAction.UNMUTE -> mastodonApi.unmuteAccount(accountId) + RelationShipAction.SUBSCRIBE -> { + if(isMastodon) + mastodonApi.followAccount(accountId, notify = true) + else mastodonApi.subscribeAccount(accountId) + } + RelationShipAction.UNSUBSCRIBE -> { + if(isMastodon) + mastodonApi.followAccount(accountId, notify = false) + else mastodonApi.unsubscribeAccount(accountId) + } }.subscribe( { relationship -> relationshipData.postValue(Success(relationship)) @@ -263,7 +294,6 @@ class AccountViewModel @Inject constructor( if (!isSelf) obtainRelationship(isReload) } - } fun setAccountInfo(accountId: String) { @@ -273,10 +303,10 @@ class AccountViewModel @Inject constructor( } enum class RelationShipAction { - FOLLOW, UNFOLLOW, BLOCK, UNBLOCK, MUTE, UNMUTE + FOLLOW, UNFOLLOW, BLOCK, UNBLOCK, MUTE, UNMUTE, SUBSCRIBE, UNSUBSCRIBE } companion object { const val TAG = "AccountViewModel" } -} \ No newline at end of file +} diff --git a/app/src/main/res/drawable/ic_notifications_active_24dp.xml b/app/src/main/res/drawable/ic_notifications_active_24dp.xml new file mode 100644 index 00000000..9a60daac --- /dev/null +++ b/app/src/main/res/drawable/ic_notifications_active_24dp.xml @@ -0,0 +1,13 @@ + + + + + + \ 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 fe9f9a73..a6cb7d95 100644 --- a/app/src/main/res/layout/activity_account.xml +++ b/app/src/main/res/layout/activity_account.xml @@ -71,6 +71,26 @@ app:layout_constraintStart_toEndOf="@id/accountMuteButton" app:layout_constraintTop_toTopOf="parent" tools:text="Follow Requested" /> + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bc340a3d..f99bbcf3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -62,6 +62,7 @@ %s favorited your toot %s followed you %s requested to follow you + %s just posted Report @%s Additional comments? @@ -223,6 +224,7 @@ my posts are boosted my posts are favorited polls have ended + somebody I\'m subscribed to published a new toot Appearance App Theme Timelines @@ -287,7 +289,8 @@ Notifications when your toots get marked as favorite Polls Notifications about polls that have ended - + New toots + Notifications when somebody you\'re subscribed to published a new toot %s mentioned you %1$s, %2$s, %3$s and %4$d others From 6fe3c043dbd758f9adfe21bc4e36624968292285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sveinn=20=C3=AD=20Felli?= Date: Sat, 19 Dec 2020 18:09:27 +0000 Subject: [PATCH 02/81] Translated using Weblate (Icelandic) Currently translated at 90.9% (10 of 11 strings) Translation: Tusky/Tusky description Translate-URL: https://weblate.tusky.app/projects/tusky/tusky-app/is/ --- fastlane/metadata/android/is/changelogs/74.txt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 fastlane/metadata/android/is/changelogs/74.txt diff --git a/fastlane/metadata/android/is/changelogs/74.txt b/fastlane/metadata/android/is/changelogs/74.txt new file mode 100644 index 00000000..f178690a --- /dev/null +++ b/fastlane/metadata/android/is/changelogs/74.txt @@ -0,0 +1,8 @@ +Tusky útg. 12.0 + +- Bætt aðalviðmót - hægt að færa flipa neðst +- Þegar þaggað er niður í notanda er núna hægt að ákveða hvort líka sé þaggað niður í tilkynningum frá honum +- Nú er hægt að fylgjast með eins mörgum myllumerkjum og maður vill í hverjum myllumerkis-flipa +- Bett leið við að birta lýsingar á myndefni, þannig að núna virka mjög langar lýsingar + +Full breytingaskrá: https://github.com/tuskyapp/Tusky/releases From 4df2ce2d6bcb1fc960af9d574c8a0775fa54b462 Mon Sep 17 00:00:00 2001 From: Mike Barnes Date: Wed, 23 Dec 2020 22:53:18 +1100 Subject: [PATCH 03/81] Exchange colorSurface and colorBackground to improve text contrast in light theme (#2025) --- app/src/main/res/values/theme_colors.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values/theme_colors.xml b/app/src/main/res/values/theme_colors.xml index b0e77046..c1f14f38 100644 --- a/app/src/main/res/values/theme_colors.xml +++ b/app/src/main/res/values/theme_colors.xml @@ -1,10 +1,10 @@ - @color/white + @color/tusky_grey_95 @color/tusky_grey_70 - @color/tusky_grey_95 + @color/white @color/tusky_grey_80 @color/tusky_grey_10 From d5e26ca8235577e6cc34f895ee88a089205e41a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Mesk=C3=B3?= Date: Mon, 21 Dec 2020 21:29:16 +0000 Subject: [PATCH 04/81] Translated using Weblate (Hungarian) Currently translated at 100.0% (434 of 434 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/hu/ --- app/src/main/res/values-hu/strings.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index df5cd610..4c1f5cd7 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -33,7 +33,7 @@ Követő Kedvencek Némított felhasználók - Blokkolt felhasználók + Letiltott felhasználók Követési kérelmek Profilod szerkesztése Piszkozatok @@ -65,8 +65,8 @@ Biztosan ki szeretnél jelentkezni a következőből: %1$s? Követés Követés vége - Blokkolás - Blokkolás feloldása + Letiltás + Letiltás feloldása Megtolások elrejtése Megtolások mutatása Bejelentés @@ -80,7 +80,7 @@ Fiókbeállítások Kedvencek Némított felhasználók - Blokkolt felhasználók + Letiltott felhasználók Követési kérelmek Média Megnyitás böngészőben @@ -118,7 +118,7 @@ Tülk URL megosztása… Tülk megosztása… Elküldve! - Felhasználó blokkolása feloldva + Felhasználó letiltása feloldva Felhasználó némítása feloldva Elküldve! Válasz sikeresen elküldve. @@ -462,7 +462,7 @@ Színes homály mutatása rejtett médiánál követni szeretnének Értesítések elrejtése - Blokkolod: @%s\? + Letiltod: @%s\? Elnémítsuk @%s fiókot\? Beszélgetés némításának feloldása Beszélgetés némítása From 0fbb4e971323bc860d81c3b95ec0ccc1d590fc4c Mon Sep 17 00:00:00 2001 From: Garrit Franke <32395585+garritfra@users.noreply.github.com> Date: Wed, 23 Dec 2020 19:13:37 +0100 Subject: [PATCH 05/81] Wellbeing mode (#1992) * Add wellbeing mode settings toggle * Translate wellbeing mode string to german * Disable fav/boost count on toots if wellbeing is enabled * Hide follow/post stats on profiles * Reload notifications when wellbeing mode is toggled * Add wellbeing mode explainer dialog * Move wellbeing filter timeline into own category * Add toggles for quantitative stats * Hide announcement badge counts if wellbeing is enabled * Move fetching of wellbeing setting to activity * Add wellbeing option to statusDisplayOptions * Update post filters for all accounts * Remove local translations * Revert "Remove local translations" This reverts commit e92e636a5c759b09649174ab68ec91bc13680287. * Remove german translations --- .../keylesspalace/tusky/AccountActivity.kt | 25 ++++++++-- .../tusky/adapter/NotificationsAdapter.java | 3 +- .../adapter/StatusDetailedViewHolder.java | 13 +++++- .../tusky/adapter/TimelineAdapter.java | 3 +- .../announcements/AnnouncementAdapter.kt | 12 ++++- .../announcements/AnnouncementsActivity.kt | 15 +++++- .../conversation/ConversationsFragment.kt | 5 +- .../preference/PreferencesFragment.kt | 46 +++++++++++++++++++ .../fragments/ReportStatusesFragment.kt | 4 +- .../fragments/SearchStatusesFragment.kt | 4 +- .../keylesspalace/tusky/db/AccountManager.kt | 3 +- .../keylesspalace/tusky/db/AppDatabase.java | 10 ++-- .../tusky/entity/Notification.kt | 5 +- .../tusky/fragment/AccountListFragment.kt | 2 +- .../tusky/fragment/NotificationsFragment.java | 11 ++++- .../tusky/fragment/TimelineFragment.java | 5 +- .../tusky/fragment/ViewThreadFragment.java | 6 ++- .../tusky/settings/SettingsConstants.kt | 3 ++ .../tusky/util/StatusDisplayOptions.kt | 4 +- .../keylesspalace/tusky/util/ThemeUtils.java | 3 +- app/src/main/res/values/strings.xml | 11 +++++ 21 files changed, 164 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt index 1a725d34..c16584eb 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt @@ -57,6 +57,7 @@ import com.keylesspalace.tusky.interfaces.ActionButtonActivity import com.keylesspalace.tusky.interfaces.LinkListener import com.keylesspalace.tusky.interfaces.ReselectableFragment import com.keylesspalace.tusky.pager.AccountPagerAdapter +import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.util.* import com.keylesspalace.tusky.view.showMuteAccountDialog import com.keylesspalace.tusky.viewmodel.AccountViewModel @@ -185,6 +186,17 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI poorTabView.isPressed = true accountTabLayout.postDelayed({ poorTabView.isPressed = false }, 300) } + + // If wellbeing mode is enabled, follow stats and posts count should be hidden + val preferences = PreferenceManager.getDefaultSharedPreferences(this) + val wellbeingEnabled = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_PROFILE, false) + + if (wellbeingEnabled) { + accountStatuses.hide() + accountFollowers.hide() + accountFollowing.hide() + } + } /** @@ -199,8 +211,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI val pageTitles = arrayOf(getString(R.string.title_statuses), getString(R.string.title_statuses_with_replies), getString(R.string.title_statuses_pinned), getString(R.string.title_media)) - TabLayoutMediator(accountTabLayout, accountFragmentViewPager) { - tab, position -> + TabLayoutMediator(accountTabLayout, accountFragmentViewPager) { tab, position -> tab.text = pageTitles[position] }.attach() @@ -534,7 +545,11 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI blockingDomain = relation.blockingDomain showingReblogs = relation.showingReblogs - accountFollowsYouTextView.visible(relation.followedBy) + // If wellbeing mode is enabled, "follows you" text should not be visible + val preferences = PreferenceManager.getDefaultSharedPreferences(this) + val wellbeingEnabled = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_PROFILE, false) + + accountFollowsYouTextView.visible(relation.followedBy && !wellbeingEnabled) // because subscribing is Pleroma extension, enable it __only__ when we have non-null subscribing field // it's also now supported in Mastodon 3.3.0rc but called notifying and use different API call @@ -749,8 +764,8 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI if (viewModel.relationshipData.value?.data?.muting != true) { loadedAccount?.let { showMuteAccountDialog( - this, - it.username + this, + it.username ) { notifications -> viewModel.muteAccount(notifications) } 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 d879bac0..2fe95385 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java @@ -254,7 +254,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter { statusDisplayOptions.showBotOverlay(), statusDisplayOptions.useBlurhash(), CardViewMode.NONE, - statusDisplayOptions.confirmReblogs() + statusDisplayOptions.confirmReblogs(), + statusDisplayOptions.hideStats() ); } 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 6350dde0..8755e8e8 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusDetailedViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusDetailedViewHolder.java @@ -108,7 +108,12 @@ class StatusDetailedViewHolder extends StatusBaseViewHolder { super.setupWithStatus(status, listener, statusDisplayOptions, payloads); setupCard(status, CardViewMode.FULL_WIDTH, statusDisplayOptions); // Always show card for detailed status if (payloads == null) { - setReblogAndFavCount(status.getReblogsCount(), status.getFavouritesCount(), listener); + + if (!statusDisplayOptions.hideStats()) { + setReblogAndFavCount(status.getReblogsCount(), status.getFavouritesCount(), listener); + } else { + hideQuantitativeStats(); + } setApplication(status.getApplication()); @@ -174,4 +179,10 @@ class StatusDetailedViewHolder extends StatusBaseViewHolder { null ); } + + private void hideQuantitativeStats() { + reblogs.setVisibility(View.GONE); + favourites.setVisibility(View.GONE); + infoDivider.setVisibility(View.GONE); + } } diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java index 91ec25e2..7963847a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java @@ -65,7 +65,8 @@ public final class TimelineAdapter extends RecyclerView.Adapter { statusDisplayOptions.showBotOverlay(), statusDisplayOptions.useBlurhash(), statusDisplayOptions.cardViewMode(), - statusDisplayOptions.confirmReblogs() + statusDisplayOptions.confirmReblogs(), + statusDisplayOptions.hideStats() ); } 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 29b57a2e..c0e6bdd8 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.util.LinkHelper import com.keylesspalace.tusky.util.emojify import kotlinx.android.synthetic.main.item_announcement.view.* + interface AnnouncementActionListener: LinkListener { fun openReactionPicker(announcementId: String, target: View) fun addReaction(announcementId: String, name: String) @@ -40,7 +41,8 @@ interface AnnouncementActionListener: LinkListener { class AnnouncementAdapter( private var items: List = emptyList(), - private val listener: AnnouncementActionListener + private val listener: AnnouncementActionListener, + private val wellbeingEnabled: Boolean = false ) : RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AnnouncementViewHolder { @@ -68,6 +70,14 @@ class AnnouncementAdapter( fun bind(item: Announcement) { LinkHelper.setClickableText(text, item.content, null, listener) + // If wellbeing mode is enabled, announcement badge counts should not be shown. + if (wellbeingEnabled) { + // Since reactions are not visible in wellbeing mode, + // we shouldn't be able to add any ourselves. + addReactionChip.visibility = View.GONE + return + } + item.reactions.forEachIndexed { i, reaction -> (chips.getChildAt(i)?.takeUnless { it.id == R.id.addReactionChip } as Chip? ?: Chip(ContextThemeWrapper(view.context, R.style.Widget_MaterialComponents_Chip_Choice)).apply { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt index 1f86a0ab..0b96b430 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt @@ -17,18 +17,23 @@ package com.keylesspalace.tusky.components.announcements import android.content.Context import android.content.Intent +import android.content.SharedPreferences import android.os.Bundle import android.view.MenuItem import android.view.View import android.widget.PopupWindow import androidx.activity.viewModels +import androidx.preference.PreferenceManager import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager -import com.keylesspalace.tusky.* +import com.keylesspalace.tusky.BottomSheetActivity +import com.keylesspalace.tusky.R +import com.keylesspalace.tusky.ViewTagActivity import com.keylesspalace.tusky.adapter.EmojiAdapter import com.keylesspalace.tusky.adapter.OnEmojiSelectedListener import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.di.ViewModelFactory +import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.util.* import com.keylesspalace.tusky.view.EmojiPicker import kotlinx.android.synthetic.main.activity_announcements.* @@ -42,7 +47,7 @@ class AnnouncementsActivity : BottomSheetActivity(), AnnouncementActionListener, private val viewModel: AnnouncementsViewModel by viewModels { viewModelFactory } - private val adapter = AnnouncementAdapter(emptyList(), this) + private lateinit var adapter: AnnouncementAdapter private val picker by lazy { EmojiPicker(this) } private val pickerDialog by lazy { @@ -75,6 +80,12 @@ class AnnouncementsActivity : BottomSheetActivity(), AnnouncementActionListener, announcementsList.layoutManager = LinearLayoutManager(this) val divider = DividerItemDecoration(this, DividerItemDecoration.VERTICAL) announcementsList.addItemDecoration(divider) + + val preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) + val wellbeingEnabled = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false) + + adapter = AnnouncementAdapter(emptyList(), this, wellbeingEnabled) + announcementsList.adapter = adapter viewModel.announcements.observe(this) { 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 b1ab32d7..2add9c44 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 @@ -34,6 +34,7 @@ import com.keylesspalace.tusky.di.ViewModelFactory import com.keylesspalace.tusky.fragment.SFragment import com.keylesspalace.tusky.interfaces.ReselectableFragment import com.keylesspalace.tusky.interfaces.StatusActionListener +import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.util.CardViewMode import com.keylesspalace.tusky.util.NetworkState import com.keylesspalace.tusky.util.StatusDisplayOptions @@ -68,7 +69,9 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res showBotOverlay = preferences.getBoolean("showBotOverlay", true), useBlurhash = preferences.getBoolean("useBlurhash", true), cardViewMode = CardViewMode.NONE, - confirmReblogs = preferences.getBoolean("confirmReblogs", true) + confirmReblogs = preferences.getBoolean("confirmReblogs", true), + hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false) + ) adapter = ConversationAdapter(statusDisplayOptions, this, ::onTopLoaded, viewModel::retry) 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 5e61e2fa..d2598c12 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 @@ -19,10 +19,14 @@ import android.os.Bundle import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import com.keylesspalace.tusky.R +import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.di.Injectable +import com.keylesspalace.tusky.entity.Notification import com.keylesspalace.tusky.settings.* import com.keylesspalace.tusky.util.ThemeUtils +import com.keylesspalace.tusky.util.deserialize import com.keylesspalace.tusky.util.getNonNullString +import com.keylesspalace.tusky.util.serialize import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial import com.mikepenz.iconics.utils.colorInt @@ -35,6 +39,9 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable { @Inject lateinit var okhttpclient: OkHttpClient + @Inject + lateinit var accountManager: AccountManager + private val iconSize by lazy { resources.getDimensionPixelSize(R.dimen.preference_icon_size) } private var httpProxyPref: Preference? = null @@ -192,6 +199,45 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable { } } + preferenceCategory(R.string.pref_title_wellbeing_mode) { + switchPreference { + title = getString(R.string.limit_notifications) + setDefaultValue(false) + key = PrefKeys.WELLBEING_LIMITED_NOTIFICATIONS + setOnPreferenceChangeListener { _, value -> + for (account in accountManager.accounts) { + val notificationFilter = deserialize(account.notificationsFilter).toMutableSet() + + if (value == true) { + notificationFilter.add(Notification.Type.FAVOURITE) + notificationFilter.add(Notification.Type.FOLLOW) + notificationFilter.add(Notification.Type.REBLOG) + } else { + notificationFilter.remove(Notification.Type.FAVOURITE) + notificationFilter.remove(Notification.Type.FOLLOW) + notificationFilter.remove(Notification.Type.REBLOG) + } + + account.notificationsFilter = serialize(notificationFilter) + accountManager.saveAccount(account) + } + true + } + } + + switchPreference { + title = getString(R.string.wellbeing_hide_stats_posts) + setDefaultValue(false) + key = PrefKeys.WELLBEING_HIDE_STATS_POSTS + } + + switchPreference { + title = getString(R.string.wellbeing_hide_stats_profile) + setDefaultValue(false) + key = PrefKeys.WELLBEING_HIDE_STATS_PROFILE + } + } + preferenceCategory(R.string.pref_title_proxy_settings) { httpProxyPref = preference { setTitle(R.string.pref_title_http_proxy_settings) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt index 703acee2..116a0c0f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt @@ -41,6 +41,7 @@ import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.di.ViewModelFactory import com.keylesspalace.tusky.entity.Attachment import com.keylesspalace.tusky.entity.Status +import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.util.CardViewMode import com.keylesspalace.tusky.util.StatusDisplayOptions import com.keylesspalace.tusky.util.hide @@ -118,7 +119,8 @@ class ReportStatusesFragment : Fragment(), Injectable, AdapterHandler { showBotOverlay = false, useBlurhash = preferences.getBoolean("useBlurhash", true), cardViewMode = CardViewMode.NONE, - confirmReblogs = preferences.getBoolean("confirmReblogs", true) + confirmReblogs = preferences.getBoolean("confirmReblogs", true), + hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false) ) adapter = StatusesAdapter(statusDisplayOptions, 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 ce5c2612..ffd69899 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 @@ -52,6 +52,7 @@ import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.entity.Status.Mention import com.keylesspalace.tusky.interfaces.AccountSelectionListener import com.keylesspalace.tusky.interfaces.StatusActionListener +import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.util.CardViewMode import com.keylesspalace.tusky.util.NetworkState import com.keylesspalace.tusky.util.StatusDisplayOptions @@ -84,7 +85,8 @@ class SearchStatusesFragment : SearchFragment = mutableListOf() + var accounts: MutableList = mutableListOf() + private set private val accountDao: AccountDao = db.accountDao() init { 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 da3aba59..5c5b7cb6 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java +++ b/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java @@ -15,14 +15,14 @@ package com.keylesspalace.tusky.db; -import com.keylesspalace.tusky.TabDataKt; -import com.keylesspalace.tusky.components.conversation.ConversationEntity; - -import androidx.sqlite.db.SupportSQLiteDatabase; +import androidx.annotation.NonNull; import androidx.room.Database; import androidx.room.RoomDatabase; import androidx.room.migration.Migration; -import androidx.annotation.NonNull; +import androidx.sqlite.db.SupportSQLiteDatabase; + +import com.keylesspalace.tusky.TabDataKt; +import com.keylesspalace.tusky.components.conversation.ConversationEntity; /** * DB version & declare DAO 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 5a169b11..0dbefd61 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/Notification.kt +++ b/app/src/main/java/com/keylesspalace/tusky/entity/Notification.kt @@ -15,7 +15,10 @@ package com.keylesspalace.tusky.entity -import com.google.gson.* +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement +import com.google.gson.JsonParseException import com.google.gson.annotations.JsonAdapter data class Notification( diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.kt index da9893a2..d5785ae3 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.kt @@ -49,7 +49,7 @@ import retrofit2.Call import retrofit2.Callback import retrofit2.Response import java.io.IOException -import java.util.HashMap +import java.util.* import javax.inject.Inject class AccountListFragment : BaseFragment(), AccountActionListener, Injectable { 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 9d88d261..fe0c75f5 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java @@ -71,6 +71,7 @@ import com.keylesspalace.tusky.interfaces.AccountActionListener; import com.keylesspalace.tusky.interfaces.ActionButtonActivity; import com.keylesspalace.tusky.interfaces.ReselectableFragment; import com.keylesspalace.tusky.interfaces.StatusActionListener; +import com.keylesspalace.tusky.settings.PrefKeys; import com.keylesspalace.tusky.util.CardViewMode; import com.keylesspalace.tusky.util.Either; import com.keylesspalace.tusky.util.HttpHeaderLink; @@ -251,7 +252,8 @@ public class NotificationsFragment extends SFragment implements preferences.getBoolean("showBotOverlay", true), preferences.getBoolean("useBlurhash", true), CardViewMode.NONE, - preferences.getBoolean("confirmReblogs", true) + preferences.getBoolean("confirmReblogs", true), + preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false) ); adapter = new NotificationsAdapter(accountManager.getActiveAccount().getAccountId(), @@ -801,6 +803,7 @@ public class NotificationsFragment extends SFragment implements private void loadNotificationsFilter() { AccountEntity account = accountManager.getActiveAccount(); if (account != null) { + notificationFilter.clear(); notificationFilter.addAll(NotificationTypeConverterKt.deserialize( account.getNotificationsFilter())); } @@ -1277,6 +1280,12 @@ public class NotificationsFragment extends SFragment implements @Override public void onResume() { super.onResume(); + String rawAccountNotificationFilter = accountManager.getActiveAccount().getNotificationsFilter(); + Set accountNotificationFilter = NotificationTypeConverterKt.deserialize(rawAccountNotificationFilter); + if (!notificationFilter.equals(accountNotificationFilter)) { + loadNotificationsFilter(); + fullyRefreshWithProgressBar(true); + } startUpdateTimestamp(); } diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java index ddcb0eb0..d6910a4c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java @@ -74,6 +74,7 @@ import com.keylesspalace.tusky.network.MastodonApi; import com.keylesspalace.tusky.repository.Placeholder; import com.keylesspalace.tusky.repository.TimelineRepository; import com.keylesspalace.tusky.repository.TimelineRequestMode; +import com.keylesspalace.tusky.settings.PrefKeys; import com.keylesspalace.tusky.util.CardViewMode; import com.keylesspalace.tusky.util.Either; import com.keylesspalace.tusky.util.HttpHeaderLink; @@ -83,7 +84,6 @@ import com.keylesspalace.tusky.util.ListUtils; import com.keylesspalace.tusky.util.PairedList; import com.keylesspalace.tusky.util.StatusDisplayOptions; import com.keylesspalace.tusky.util.StringUtils; -import com.keylesspalace.tusky.util.ThemeUtils; import com.keylesspalace.tusky.util.ViewDataUtils; import com.keylesspalace.tusky.view.BackgroundMessageView; import com.keylesspalace.tusky.view.EndlessOnScrollListener; @@ -252,7 +252,8 @@ public class TimelineFragment extends SFragment implements preferences.getBoolean("showCardsInTimelines", false) ? CardViewMode.INDENTED : CardViewMode.NONE, - preferences.getBoolean("confirmReblogs", true) + preferences.getBoolean("confirmReblogs", true), + preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false) ); adapter = new TimelineAdapter(dataSource, statusDisplayOptions, this); diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java index 77fb3283..bf28301f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java @@ -58,11 +58,11 @@ import com.keylesspalace.tusky.entity.Status; import com.keylesspalace.tusky.entity.StatusContext; import com.keylesspalace.tusky.interfaces.StatusActionListener; import com.keylesspalace.tusky.network.MastodonApi; +import com.keylesspalace.tusky.settings.PrefKeys; import com.keylesspalace.tusky.util.CardViewMode; import com.keylesspalace.tusky.util.ListStatusAccessibilityDelegate; import com.keylesspalace.tusky.util.PairedList; import com.keylesspalace.tusky.util.StatusDisplayOptions; -import com.keylesspalace.tusky.util.ThemeUtils; import com.keylesspalace.tusky.util.ViewDataUtils; import com.keylesspalace.tusky.view.ConversationLineItemDecoration; import com.keylesspalace.tusky.viewdata.StatusViewData; @@ -127,6 +127,7 @@ public final class ViewThreadFragment extends SFragment implements thisThreadsStatusId = getArguments().getString("id"); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity()); + StatusDisplayOptions statusDisplayOptions = new StatusDisplayOptions( preferences.getBoolean("animateGifAvatars", false), accountManager.getActiveAccount().getMediaPreviewEnabled(), @@ -136,7 +137,8 @@ public final class ViewThreadFragment extends SFragment implements preferences.getBoolean("showCardsInTimelines", false) ? CardViewMode.INDENTED : CardViewMode.NONE, - preferences.getBoolean("confirmReblogs", true) + preferences.getBoolean("confirmReblogs", true), + preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false) ); adapter = new ThreadAdapter(statusDisplayOptions, this); } 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 c3c26782..a35885f3 100644 --- a/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt +++ b/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt @@ -33,6 +33,9 @@ object PrefKeys { const val ENABLE_SWIPE_FOR_TABS = "enableSwipeForTabs" const val CUSTOM_TABS = "customTabs" + const val WELLBEING_LIMITED_NOTIFICATIONS = "wellbeingModeLimitedNotifications" + const val WELLBEING_HIDE_STATS_POSTS = "wellbeingHideStatsPosts" + const val WELLBEING_HIDE_STATS_PROFILE = "wellbeingHideStatsProfile" const val HTTP_PROXY_ENABLED = "httpProxyEnabled" const val HTTP_PROXY_SERVER = "httpProxyServer" diff --git a/app/src/main/java/com/keylesspalace/tusky/util/StatusDisplayOptions.kt b/app/src/main/java/com/keylesspalace/tusky/util/StatusDisplayOptions.kt index eaaa5e19..93cbb3d9 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/StatusDisplayOptions.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/StatusDisplayOptions.kt @@ -14,5 +14,7 @@ data class StatusDisplayOptions( @get:JvmName("cardViewMode") val cardViewMode: CardViewMode, @get:JvmName("confirmReblogs") - val confirmReblogs: Boolean + val confirmReblogs: Boolean, + @get:JvmName("hideStats") + val hideStats: Boolean ) \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/util/ThemeUtils.java b/app/src/main/java/com/keylesspalace/tusky/util/ThemeUtils.java index d01775dd..8c04a7d2 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/ThemeUtils.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/ThemeUtils.java @@ -20,11 +20,12 @@ import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; +import android.util.TypedValue; + import androidx.annotation.AttrRes; import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatDelegate; -import android.util.TypedValue; /** * Provides runtime compatibility to obtain theme information and re-theme views, especially where diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f99bbcf3..85cd7cce 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -578,7 +578,18 @@ Show link previews in timelines Show confirmation dialog before boosting Hide the title of the top toolbar + Wellbeing Your private note about this account Saved! + Some information that might affect your mental wellbeing will be hidden. This includes:\n\n + - Favorite/Boost/Follow notifications\n + - Favorite/Boost count on toots\n + - Follower/Post stats on profiles\n\n + Push-notifications will not be affected, but you can review your notification preferences manually. + + Review Notifications + Limit timeline notifications + Hide quantitative stats on posts + Hide quantitative stats on profiles From 8b9ddca7bdf200e6d77bf1769bc92494c31207a2 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Sun, 27 Dec 2020 21:25:35 +0100 Subject: [PATCH 06/81] cleanup code in ConversationsFragment, SearchFragment and Report*Fragments (#2027) --- .../conversation/ConversationsFragment.kt | 3 --- .../report/fragments/ReportDoneFragment.kt | 16 +++------------- .../report/fragments/ReportNoteFragment.kt | 14 +++----------- .../report/fragments/ReportStatusesFragment.kt | 18 ++++-------------- .../search/fragments/SearchFragment.kt | 13 +++---------- 5 files changed, 13 insertions(+), 51 deletions(-) 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 2add9c44..d50f9043 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 @@ -28,7 +28,6 @@ import androidx.recyclerview.widget.SimpleItemAnimator import com.keylesspalace.tusky.AccountActivity import com.keylesspalace.tusky.R import com.keylesspalace.tusky.ViewTagActivity -import com.keylesspalace.tusky.db.AppDatabase import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.di.ViewModelFactory import com.keylesspalace.tusky.fragment.SFragment @@ -46,8 +45,6 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res @Inject lateinit var viewModelFactory: ViewModelFactory - @Inject - lateinit var db: AppDatabase private val viewModel: ConversationsViewModel by viewModels { viewModelFactory } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportDoneFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportDoneFragment.kt index 6151903f..03bd8ef9 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportDoneFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportDoneFragment.kt @@ -15,13 +15,10 @@ package com.keylesspalace.tusky.components.report.fragments - import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.fragment.app.Fragment -import androidx.fragment.app.viewModels +import androidx.fragment.app.activityViewModels import com.keylesspalace.tusky.R import com.keylesspalace.tusky.components.report.ReportViewModel import com.keylesspalace.tusky.components.report.Screen @@ -33,19 +30,12 @@ import com.keylesspalace.tusky.util.show import kotlinx.android.synthetic.main.fragment_report_done.* import javax.inject.Inject - -class ReportDoneFragment : Fragment(), Injectable { +class ReportDoneFragment : Fragment(R.layout.fragment_report_done), Injectable { @Inject lateinit var viewModelFactory: ViewModelFactory - private val viewModel: ReportViewModel by viewModels({ requireActivity() }) { viewModelFactory } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View? { - // Inflate the layout for this fragment - return inflater.inflate(R.layout.fragment_report_done, container, false) - } + private val viewModel: ReportViewModel by activityViewModels { viewModelFactory } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { textReported.text = getString(R.string.report_sent_success, viewModel.accountUserName) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportNoteFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportNoteFragment.kt index 4e7b00ab..b933b2fa 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportNoteFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportNoteFragment.kt @@ -16,12 +16,10 @@ package com.keylesspalace.tusky.components.report.fragments import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.core.widget.doAfterTextChanged import androidx.fragment.app.Fragment -import androidx.fragment.app.viewModels +import androidx.fragment.app.activityViewModels import com.google.android.material.snackbar.Snackbar import com.keylesspalace.tusky.R import com.keylesspalace.tusky.components.report.ReportViewModel @@ -33,18 +31,12 @@ import kotlinx.android.synthetic.main.fragment_report_note.* import java.io.IOException import javax.inject.Inject -class ReportNoteFragment : Fragment(), Injectable { +class ReportNoteFragment : Fragment(R.layout.fragment_report_note), Injectable { @Inject lateinit var viewModelFactory: ViewModelFactory - private val viewModel: ReportViewModel by viewModels({ requireActivity() }) { viewModelFactory } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View? { - // Inflate the layout for this fragment - return inflater.inflate(R.layout.fragment_report_note, container, false) - } + private val viewModel: ReportViewModel by activityViewModels { viewModelFactory } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { fillViews() diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt index 116a0c0f..70bc694d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt @@ -16,13 +16,11 @@ package com.keylesspalace.tusky.components.report.fragments import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.core.app.ActivityOptionsCompat import androidx.core.view.ViewCompat import androidx.fragment.app.Fragment -import androidx.fragment.app.viewModels +import androidx.fragment.app.activityViewModels import androidx.preference.PreferenceManager import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager @@ -50,7 +48,7 @@ import com.keylesspalace.tusky.viewdata.AttachmentViewData import kotlinx.android.synthetic.main.fragment_report_statuses.* import javax.inject.Inject -class ReportStatusesFragment : Fragment(), Injectable, AdapterHandler { +class ReportStatusesFragment : Fragment(R.layout.fragment_report_statuses), Injectable, AdapterHandler { @Inject lateinit var viewModelFactory: ViewModelFactory @@ -58,10 +56,9 @@ class ReportStatusesFragment : Fragment(), Injectable, AdapterHandler { @Inject lateinit var accountManager: AccountManager - private val viewModel: ReportViewModel by viewModels({ requireActivity() }) { viewModelFactory } + private val viewModel: ReportViewModel by activityViewModels { viewModelFactory } private lateinit var adapter: StatusesAdapter - private lateinit var layoutManager: LinearLayoutManager private var snackbarErrorRetry: Snackbar? = null @@ -89,12 +86,6 @@ class ReportStatusesFragment : Fragment(), Injectable, AdapterHandler { } } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View? { - // Inflate the layout for this fragment - return inflater.inflate(R.layout.fragment_report_statuses, container, false) - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { handleClicks() initStatusesView() @@ -127,8 +118,7 @@ class ReportStatusesFragment : Fragment(), Injectable, AdapterHandler { viewModel.statusViewState, this) recyclerView.addItemDecoration(DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL)) - layoutManager = LinearLayoutManager(requireContext()) - recyclerView.layoutManager = layoutManager + recyclerView.layoutManager = LinearLayoutManager(requireContext()) recyclerView.adapter = adapter (recyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false 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 c5929c43..83eb3091 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 @@ -1,11 +1,9 @@ package com.keylesspalace.tusky.components.search.fragments import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.fragment.app.Fragment -import androidx.fragment.app.viewModels +import androidx.fragment.app.activityViewModels import androidx.lifecycle.LiveData import androidx.paging.PagedList import androidx.paging.PagedListAdapter @@ -26,13 +24,13 @@ import com.keylesspalace.tusky.util.* import kotlinx.android.synthetic.main.fragment_search.* import javax.inject.Inject -abstract class SearchFragment : Fragment(), +abstract class SearchFragment : Fragment(R.layout.fragment_search), LinkListener, Injectable, SwipeRefreshLayout.OnRefreshListener { @Inject lateinit var viewModelFactory: ViewModelFactory - protected val viewModel: SearchViewModel by viewModels({ requireActivity() }) { viewModelFactory } + protected val viewModel: SearchViewModel by activityViewModels { viewModelFactory } private var snackbarErrorRetry: Snackbar? = null @@ -43,12 +41,7 @@ abstract class SearchFragment : Fragment(), abstract val data: LiveData> protected lateinit var adapter: PagedListAdapter - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_search, container, false) - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) initAdapter() setupSwipeRefreshLayout() subscribeObservables() From f7a630c1d81cb0d06be11aa2d17592cba62d2cd4 Mon Sep 17 00:00:00 2001 From: lenchan139 Date: Thu, 31 Dec 2020 00:36:00 +0800 Subject: [PATCH 07/81] multiple media upload support (#2029) * multiple media upload support * multiple media upload support * multiple media upload support * remove typing * Update app/src/main/res/values/strings.xml Co-authored-by: Konrad Pozniak * remove magic number on string.xml and add to activity. Co-authored-by: Konrad Pozniak --- .../components/compose/ComposeActivity.kt | 20 ++++++++++++++++++- app/src/main/res/values/strings.xml | 1 + 2 files changed, 20 insertions(+), 1 deletion(-) 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 e9615d33..5306e57a 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 @@ -110,6 +110,7 @@ class ComposeActivity : BaseActivity(), private var composeOptions: ComposeOptions? = null private val viewModel: ComposeViewModel by viewModels { viewModelFactory } + private val maxUploadMediaNumber = 4 private var mediaCount = 0 public override fun onCreate(savedInstanceState: Bundle?) { @@ -807,6 +808,7 @@ class ComposeActivity : BaseActivity(), val mimeTypes = arrayOf("image/*", "video/*", "audio/*") intent.type = "*/*" intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes) + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true) startActivityForResult(intent, MEDIA_PICK_RESULT) } @@ -833,7 +835,23 @@ class ComposeActivity : BaseActivity(), override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { super.onActivityResult(requestCode, resultCode, intent) if (resultCode == Activity.RESULT_OK && requestCode == MEDIA_PICK_RESULT && intent != null) { - pickMedia(intent.data!!) + if(intent.data != null){ + // Single media, upload it and done. + pickMedia(intent.data!!) + }else if(intent.clipData != null){ + val clipData = intent.clipData!! + val count = clipData.itemCount + if(mediaCount + count > maxUploadMediaNumber){ + // check if exist media + upcoming media > 4, then prob error message. + Toast.makeText(this, getString(R.string.error_upload_max_media_reached, maxUploadMediaNumber), Toast.LENGTH_SHORT).show() + }else{ + // if not grater then 4, upload all multiple media. + for (i in 0 until count) { + val imageUri = clipData.getItemAt(i).getUri() + pickMedia(imageUri) + } + } + } } else if (resultCode == Activity.RESULT_OK && requestCode == MEDIA_TAKE_PHOTO_RESULT) { pickMedia(photoUploadUri!!) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 85cd7cce..87d63df5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -591,5 +591,6 @@ Limit timeline notifications Hide quantitative stats on posts Hide quantitative stats on profiles + You cannot upload more than %1$d media attachments. From 4029dd0d514cd8893cc799bb597d61e2dd7e4755 Mon Sep 17 00:00:00 2001 From: qezwan Date: Wed, 13 Jan 2021 08:10:26 +0000 Subject: [PATCH 08/81] Translated using Weblate (Sorani) Currently translated at 36.3% (4 of 11 strings) Translation: Tusky/Tusky description Translate-URL: https://weblate.tusky.app/projects/tusky/tusky-app/ckb/ --- fastlane/metadata/android/ckb/changelogs/77.txt | 10 ++++++++++ fastlane/metadata/android/ckb/full_description.txt | 12 ++++++++++++ fastlane/metadata/android/ckb/short_description.txt | 1 + fastlane/metadata/android/ckb/title.txt | 1 + 4 files changed, 24 insertions(+) create mode 100644 fastlane/metadata/android/ckb/changelogs/77.txt create mode 100644 fastlane/metadata/android/ckb/full_description.txt create mode 100644 fastlane/metadata/android/ckb/short_description.txt create mode 100644 fastlane/metadata/android/ckb/title.txt diff --git a/fastlane/metadata/android/ckb/changelogs/77.txt b/fastlane/metadata/android/ckb/changelogs/77.txt new file mode 100644 index 00000000..ee35b258 --- /dev/null +++ b/fastlane/metadata/android/ckb/changelogs/77.txt @@ -0,0 +1,10 @@ +تاسکی وشانی ١٣.٠ + +- پشتگیری بۆ تێبینیەکانی پرۆفایل (تایبەتمەندی ماستۆدۆن ٣.٢.٠) +- پشتگیری لە راگەیاندنی بەڕێوەبەر (تایبەتمەندی ماستۆدۆن ٣.١.٠) + +- ئێستا ئەژمێری هەڵبژێردراوی هەژمارەکەت لە شریتی ئامڕازی سەرەکی دا پیشان دەدرێت +- کرتە کردن لەسەر ناوی پیشاندان لە هێڵی کات ئێستا لاپەڕەی پرۆفایلی ئەو بەکارهێنەرە هەڵدەدات + +- زۆر چاککردنەوەی هەڵەکان و چاککردنەوەی بچووک +- وەرگێڕانە باشەکان diff --git a/fastlane/metadata/android/ckb/full_description.txt b/fastlane/metadata/android/ckb/full_description.txt new file mode 100644 index 00000000..ab90d927 --- /dev/null +++ b/fastlane/metadata/android/ckb/full_description.txt @@ -0,0 +1,12 @@ +توسکی ئەپێکی سووکەڵە بۆ ماستۆدۆنە، خزمەتکاری تۆڕی کۆمەڵایەتی ئازاد و کراوە + +• دیزاینی ماتریالی +• زۆربەی ماستۆدۆن API جێبەجێ دەکا +• پشتیوانی هەژمارەی هەمەجۆر +• ڕووکاری تاریک و رووناک لەگەڵ ئەگەری گۆڕینی خۆکار لەسەر بنەمای کاتی رۆژ +• ڕەشنووسەکان - دروستکردنی دووتەکان و هەڵگرتنیان بۆ دواتر +• هەڵبژێرە لەنێوان شێوازە ئیمۆجییە جیاوازەکان +• باشترکراوە بۆ هەموو قەبارەی شاشە +• بەتەواوی کراوەی سەرچاوە - هیچ پشت پێبەستنێکی نائازاد وەک خزمەتگوزاریەکانی گووگڵ + +بۆ زیاتر فێربوون لەبارەی مەستوورن ، سەردانی https://joinmastodon.org/ diff --git a/fastlane/metadata/android/ckb/short_description.txt b/fastlane/metadata/android/ckb/short_description.txt new file mode 100644 index 00000000..df2f8d34 --- /dev/null +++ b/fastlane/metadata/android/ckb/short_description.txt @@ -0,0 +1 @@ +کڕیارێکی هەژماری هەمەجۆر بۆ تۆڕی کۆمەڵایەتی ماستۆدۆن diff --git a/fastlane/metadata/android/ckb/title.txt b/fastlane/metadata/android/ckb/title.txt new file mode 100644 index 00000000..57a4e890 --- /dev/null +++ b/fastlane/metadata/android/ckb/title.txt @@ -0,0 +1 @@ +تاسکی From 270e49a708fe1b588e28bee4abf538efb4d53a01 Mon Sep 17 00:00:00 2001 From: Grandasse Date: Wed, 13 Jan 2021 08:10:27 +0000 Subject: [PATCH 09/81] Translated using Weblate (French) Currently translated at 100.0% (11 of 11 strings) Translation: Tusky/Tusky description Translate-URL: https://weblate.tusky.app/projects/tusky/tusky-app/fr/ --- fastlane/metadata/android/fr/changelogs/58.txt | 5 ++++- fastlane/metadata/android/fr/changelogs/70.txt | 2 +- fastlane/metadata/android/fr/changelogs/72.txt | 6 +++--- fastlane/metadata/android/fr/changelogs/74.txt | 8 ++++++++ fastlane/metadata/android/fr/changelogs/77.txt | 10 ++++++++++ 5 files changed, 26 insertions(+), 5 deletions(-) create mode 100644 fastlane/metadata/android/fr/changelogs/74.txt create mode 100644 fastlane/metadata/android/fr/changelogs/77.txt diff --git a/fastlane/metadata/android/fr/changelogs/58.txt b/fastlane/metadata/android/fr/changelogs/58.txt index fb6993c1..22c4090e 100644 --- a/fastlane/metadata/android/fr/changelogs/58.txt +++ b/fastlane/metadata/android/fr/changelogs/58.txt @@ -1,3 +1,6 @@ Tusky v6.0 -- +- Les filtres de la timeline ont été déplacés dans les Préférences de compte et se synchronisent avec le serveur +- Vous pouvez personnaliser un onglet avec un hashtag dans la fenêtre principale +- Les listes peuvent être éditées +- Sécurité : suppression du support pour TLS 1.0 et TLS 1.1, et ajout du support pour TLS 1.3 sur Android 6+ diff --git a/fastlane/metadata/android/fr/changelogs/70.txt b/fastlane/metadata/android/fr/changelogs/70.txt index 832aec90..5fb322c6 100644 --- a/fastlane/metadata/android/fr/changelogs/70.txt +++ b/fastlane/metadata/android/fr/changelogs/70.txt @@ -1,7 +1,7 @@ Tusky v10.0 - Vous pouvez maintenant marquer les statuts et lister vos signets dans Tusky. -- Vous pouvez maintenant programmer des toots avec Tusky. Notez que l'heure que vous sélectionnez doit être d'au moins 5 minutes dans le futur. +- Vous pouvez maintenant programmer des pouets avec Tusky. Notez que l'heure que vous sélectionnez doit être d'au moins 5 minutes dans le futur. - Vous pouvez maintenant ajouter des listes à l'écran principal. - Vous pouvez désormais publier des pièces jointes audio avec Tusky. diff --git a/fastlane/metadata/android/fr/changelogs/72.txt b/fastlane/metadata/android/fr/changelogs/72.txt index ad75c990..fb48ccbc 100644 --- a/fastlane/metadata/android/fr/changelogs/72.txt +++ b/fastlane/metadata/android/fr/changelogs/72.txt @@ -2,8 +2,8 @@ Tusky v11.0 - Notifications à propos des nouvelles demandes d’abonnement lorsque votre compte est verrouillé - Nouvelles fonctionalités activables via l’écran Préférences : -- désactiver le pivotement entre onglets -- afficher une boîte de dialogue de confirmation avant de booster un pouet -- afficher les aperçus des liens dans les fils + - désactiver le pivotement entre onglets + - afficher une boîte de dialogue de confirmation avant de booster un pouet + - afficher les aperçus des liens dans les fils - Possibilité de mettre en sourdine les conversations - Les résultats des sondages seront désormais calculés en fonction du nombre des sond·é·s diff --git a/fastlane/metadata/android/fr/changelogs/74.txt b/fastlane/metadata/android/fr/changelogs/74.txt new file mode 100644 index 00000000..f61f152a --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/74.txt @@ -0,0 +1,8 @@ +Tusky v12.0 + +- Amélioration de l'interface principale - vous pouvez maintenant déplacer les onglets vers le bas +- Lorsque vous mettez un utilisateur en sourdine, vous pouvez désormais décider de désactiver ses notifications +- Vous pouvez maintenant suivre autant de hashtags que vous le souhaitez dans un seul onglet hashtag +- La description des médias s'affiche correctement quelque soit sa taille + +Historique complet : https://github.com/tuskyapp/Tusky/releases diff --git a/fastlane/metadata/android/fr/changelogs/77.txt b/fastlane/metadata/android/fr/changelogs/77.txt new file mode 100644 index 00000000..1558ac22 --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/77.txt @@ -0,0 +1,10 @@ +Tusky v13.0 + +- prise en charge des notes de profil (fonctionnalité de Mastodon 3.2.0) +- le support des annonces de l'administration (fonctionnalité de Mastodon 3.1.0) + +- l'avatar de votre compte sélectionné apparaîtra désormais dans la barre d'outils principale +- en cliquant sur le nom affiché dans une timeline, la page de profil de cet utilisateur s'ouvrira + +- plein corrections de bugs et de petites améliorations +- l'amélioration des traductions From c119c0ce6d82fb355957c0f7762c9e39a9524fb3 Mon Sep 17 00:00:00 2001 From: Prashant Date: Thu, 14 Jan 2021 04:10:28 +0000 Subject: [PATCH 10/81] Translated using Weblate (Hindi) Currently translated at 85.8% (381 of 444 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/hi/ --- app/src/main/res/values-hi/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 6c652ead..466019f6 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -403,4 +403,6 @@ %d सेकेंड शेष %d सेकेंड शेष + पोस्ट बहुत लंबा है! + उस सर्वर से प्रमाणित करने में विफल। \ No newline at end of file From b4d0158cb24abc0b3246c3e8cb97184a4916de67 Mon Sep 17 00:00:00 2001 From: x Date: Thu, 14 Jan 2021 04:10:28 +0000 Subject: [PATCH 11/81] Translated using Weblate (Italian) Currently translated at 99.7% (444 of 445 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/it/ Translated using Weblate (Italian) Currently translated at 99.7% (443 of 444 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/it/ --- app/src/main/res/values-it/strings.xml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index a704c110..01dbcb69 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -468,7 +468,7 @@ 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 le anteprime dei collegamenti nelle sequenze temporali + Mostra le anteprime dei collegamenti nelle timelines Mastodon ha un intervallo minimo di programmazione di 5 minuti. Non ci sono annunci. Non hai stati pianificati. @@ -483,4 +483,14 @@ Riattiva le notifiche da %s Annunci Richieste di seguirti + Nascondi statistiche quantitative sui profili + Nascondi le statistiche quantitative sui post + Limita le notifiche della timeline + Revisiona 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 + Non puoi caricare più di %1$d allegati multimediali. \ No newline at end of file From d8a3476942e4bc62f6f24657e33b35c601628e07 Mon Sep 17 00:00:00 2001 From: Ho Nhat Duy Date: Thu, 14 Jan 2021 04:10:28 +0000 Subject: [PATCH 12/81] Translated using Weblate (Vietnamese) Currently translated at 100.0% (445 of 445 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/ Translated using Weblate (Vietnamese) Currently translated at 100.0% (444 of 444 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/ Translated using Weblate (Vietnamese) Currently translated at 100.0% (444 of 444 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/ --- app/src/main/res/values-vi/strings.xml | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index e6a93e82..295c438b 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -124,8 +124,8 @@ Mở ngăn kéo Làm mờ hình ảnh Nhắc tới - Bỏ ẩn cuộc trò chuyện - Ẩn cuộc trò chuyện + Mở lại thông báo + Tắt thông báo Ẩn %s Bỏ ẩn Ẩn @@ -171,7 +171,7 @@ Thu gọn Xem thêm Thu gọn - Mở rộng + Xem thêm Hiển thị Nội dung bị ẩn Nhạy cảm @@ -459,4 +459,21 @@ Ghi chú của bạn Chưa có thông báo. Tin tức + Ẩn số liệu trên trang cá nhân + Ẩn tương tác trên tút + Hạn chế thông báo trên bảng tin + Chọn loại thông báo + Các thông tin ảnh hưởng tới tâm lý hành vi của bạn sẽ bị ẩn. Bao gồm: +\n +\n - Thông báo Lượt thích/Chia sẻ/Theo dõi +\n - Số Lượt thích/Chia sẻ của tút +\n - Số Người theo dõi/Tút trên trang cá nhân +\n +\nThông báo đẩy sẽ không ảnh hưởng, bạn có thể tự thiết lập trong phần cài đặt điện thoại của bạn. + Cai nghiện + Thông báo khi người bạn đăng ký theo dõi đăng tút mới + Tút mới + người tôi đăng ký theo dõi đăng tút mới + %s vừa đăng tút + Bạn không thể đính kèm quá %1$d tệp. \ No newline at end of file From a4f9acc70e432e24123aca64efcb5c8bee275707 Mon Sep 17 00:00:00 2001 From: Connyduck Date: Thu, 14 Jan 2021 04:10:29 +0000 Subject: [PATCH 13/81] Added translation using Weblate (Telugu) Added translation using Weblate (Punjabi) --- app/src/main/res/values-pa/strings.xml | 2 ++ app/src/main/res/values-te/strings.xml | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 app/src/main/res/values-pa/strings.xml create mode 100644 app/src/main/res/values-te/strings.xml diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml new file mode 100644 index 00000000..a6b3daec --- /dev/null +++ b/app/src/main/res/values-pa/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/app/src/main/res/values-te/strings.xml b/app/src/main/res/values-te/strings.xml new file mode 100644 index 00000000..a6b3daec --- /dev/null +++ b/app/src/main/res/values-te/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From 282ed03a1a057e3cd2a48ea67e70af3e7b82c7a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isak=20Holmstr=C3=B6m?= Date: Thu, 14 Jan 2021 04:10:29 +0000 Subject: [PATCH 14/81] Translated using Weblate (Swedish) Currently translated at 100.0% (444 of 444 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/sv/ Translated using Weblate (Swedish) Currently translated at 98.6% (438 of 444 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/sv/ --- app/src/main/res/values-sv/strings.xml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 8e3d5fb7..6b5296a3 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -484,4 +484,20 @@ Din privata notering om detta kontot Det finns inga meddelanden. Meddelanden + Aviseringar när någon du följer skrivit en ny toot + Nya toots + någon som jag följer har skrivit en ny toot + %s skrev precis + Dölj kvantitativ information på profiler + Dölj kvantitativ information på inlägg + Begränsa tidslinje aviseringar + Ändra aviseringar + Information som kan påverka ditt välmående kommer att döljas. Detta inkluderar: +\n +\n- Favorisering/Knuff/Följaraviseringar +\n- Favorisering/Antal knuffar +\n- Följare/Inlägg på profiler +\n +\nPush-aviseringar påverkas inte, men du ändra dina aviseringinställningar manuellt. + Välmående \ No newline at end of file From 54236fa9ba5eb213fc3215c25f1ce1ae666ad289 Mon Sep 17 00:00:00 2001 From: Vegard Skjefstad Date: Thu, 14 Jan 2021 04:10:29 +0000 Subject: [PATCH 15/81] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (445 of 445 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/nb_NO/ Translated using Weblate (Norwegian Bokmål) Currently translated at 100.0% (444 of 444 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/nb_NO/ --- app/src/main/res/values-no-rNB/strings.xml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/app/src/main/res/values-no-rNB/strings.xml b/app/src/main/res/values-no-rNB/strings.xml index d259e5e0..9201edb7 100644 --- a/app/src/main/res/values-no-rNB/strings.xml +++ b/app/src/main/res/values-no-rNB/strings.xml @@ -467,4 +467,21 @@ Ditt private notat om denne kontoen Det er ingen kunngjøringer. Kunngjøringer + Skjul kvantitativ informasjon på profiler + Skjul kvantitativ informasjon på toots + Begrens tidslinjevarsler + Se over varsler + Informasjon som kan påvirke ditt mentale velvære vil bli skjult. Dette inkluderer: +\n +\n - Varsler om favorisering, boosts og følgere +\n - Antall favoriseringer og boots på toots +\n - Antall følgere og toots på profiler +\n +\n Push-varsler vil ikke påvirkes, men du kan se over dine varselinnstillinger manuelt. + Velvære + Varsler når noen jeg følger publiserer en ny toot + Nye toots + noen jeg følger publiserer en ny toot + %s tootet akkurat + Du kan ikke laste opp flere enn %1$d mediavedlegg. \ No newline at end of file From 4fe17b0485e73bb33e74a7be176549ac62756dde Mon Sep 17 00:00:00 2001 From: nailyk Date: Thu, 14 Jan 2021 04:10:29 +0000 Subject: [PATCH 16/81] Added translation using Weblate (Sorani) --- app/src/main/res/values-ckb/strings.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 app/src/main/res/values-ckb/strings.xml diff --git a/app/src/main/res/values-ckb/strings.xml b/app/src/main/res/values-ckb/strings.xml new file mode 100644 index 00000000..a6b3daec --- /dev/null +++ b/app/src/main/res/values-ckb/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From 871fddc79cf762b86226f484500c3a9947664904 Mon Sep 17 00:00:00 2001 From: qezwan Date: Thu, 14 Jan 2021 04:10:29 +0000 Subject: [PATCH 17/81] Translated using Weblate (Sorani) Currently translated at 100.0% (445 of 445 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/ckb/ Translated using Weblate (Sorani) Currently translated at 50.3% (224 of 445 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/ckb/ Translated using Weblate (Sorani) Currently translated at 34.8% (155 of 445 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/ckb/ --- app/src/main/res/values-ckb/strings.xml | 487 +++++++++++++++++++++++- 1 file changed, 486 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-ckb/strings.xml b/app/src/main/res/values-ckb/strings.xml index a6b3daec..d07ed227 100644 --- a/app/src/main/res/values-ckb/strings.xml +++ b/app/src/main/res/values-ckb/strings.xml @@ -1,2 +1,487 @@ - \ No newline at end of file + + چی خەریکه ڕوودەدات؟ + کام نموونە؟ + وەڵام دانەوە کە بە سەرکەوتوویی نێردرا. + ناردن! + %s نەشاراوە + بەکارهێنەر نەگۆڕاو + بەکارهێنەر بەربەست نەکراوە + ناردن! + هاوبەشکردنی میدیا بۆ… + هاوبەشی کردن بە توت بۆ… + هاوبەشکردنی توتی URL بۆ… + داگرتنی میدیا + داگرتنی میدیا + هاوبەش کردن وەک … + کردنەوە وەک %s + بەستەرەکە ڕوونوس بکە + داگرتنی %1$s + کردنەوەی میدیا #%d + بەستەرەکان + ئاماژەکان + هاشتاگی + پیشاندانی دڵخوازەکان + پیشاندانی بەهێزکردنەکان + کردنەوەی بەهێزکردنی نووسەر + هاشتاگ + ئاماژەکان + بەستەرەکان + زیادکردنی سەرخشت + ڕیسێت کردن + خشتەی توت + تەختەکلیلی ئیمۆجی + ئاگاداری ناوەڕۆک + بینینی توت + توتی خشتەکراو + ڕەشنووسەکان + گەڕان + ڕەتکردنەوە + ڕازیبون + گەڕانەوە + بژارکردن + دەستکاری پرۆفایل بکە + بپارێزە + کردنەوەی وێنەکێش + شاردنەوەی میدیا + ئاماژە + گفتوگۆی لاببە + نابێدەنگ کردن %s + بێدەنگکردن %s + بێدەنگکردنی ئاگانامەکان لە %s + ئاگانامەکانی لاببە لە %s + نابێدەنگ %s + بێدەنگی لابردن + بێدەنگ + هاوبەش کردن + وێنە بگرە + زیادکردنی ڕاپرسی + زیادکردنی میدیا + کردنەوە لە وێبگەڕ + میدیا + بەدواداچونی داواکاریەکان بکە + دۆمەینە شاراوەکان + بەکارهێنەرە بلۆککراوەکان + بەکارهێنەرە گۆڕاوەکان + نیشانەکان + دڵخوازەکان + پەسەندکراوەکانی ئەژمێر + پەسەندەکان + پرۆفایل + دابخە + دووبارە هەوڵ بدە + توت! + توت + سڕینەوە و دووبارە-ڕەشنووس + سڕینەوە + دەستکاری + گوزارشەکان + پیشاندانی بەهێزکردنەکان + شاردنەوەی بەهێزکردنەکان + بەربەست کردن لاببە + بلۆک + بەدوادانەچو + بەدواداکەوتن + ئایا دڵنیایت لەوەی دەتەوێت بچیتەدەرەوە لە هەژماری %1$s؟ + چوونەدەرەوە + چوونەژوورەوە لەگەڵ ماستۆدۆن + دروستکردن + زیاتر + لابردنی دڵخوازەکان + نیشانه + دڵخواز + لابردنی بەهێزکردن + بەهێزکردن + وەڵام + وەڵامدانەوەی خێرا + سەرنجەکانی تر؟ + گوزارشت @%s + %s تەنها بڵاوکرایەوە + %s داواکراوە کە شوێنت بکەوێت + %s بەدواتا کەوت + %s خۆشترین توتەکەت + %s توتەکەتی بەرزکردەوە + هیچ شتێک لێرە نیە ڕاکە خوارەوە بۆ نوێکردنەوە! + هیچ شتێک لێرە نیە. + نوشتانەوە + فراوانکردن + کەمتر نیشان بدە + زیاتر پیشان بدە + کرتە بکە بۆ بینین + میدیا شاراوە + ناوەڕۆکی هەستیار + %s بەرزکرا + \@%s + مۆڵەتەکان + ڕاگه یه نراوەکان + توتی خشتەکراو + ڕەشنووسەکان + دەستکاری پرۆفایلەکەت بکە + بەدواداچونی داواکاریەکان بکە + دۆمەینە شاراوەکان + بەکارهێنەرە بلۆککراوەکان + بەکارهێنەرە بێدەنگ + نیشانەکان + دڵخوازەکان + شوێنکەوتوان + بەدوادا + چەسپا + لەگەڵ وەڵامەکان + بابەتەکان + توت + سەرخشتەکان + نامە ڕاستەوخۆکان + گشتی + ناوخۆیی + ئاگادارییەکان + سەرەتا + هەڵە لە ناردنی توت. + بارکردن سەرکەوتوو نەبوو. + وێنە و ڤیدیۆکان ناتوانرێت هەردووک هاوپێچ بکرێت لەگەڵ یەک دۆخ. + مۆڵەت بۆ پاشکەوتکردنی میدیا پێویستە. + مۆڵەت بۆ خوێندنەوەی میدیا پێویستە. + ئەم فایلە ناتوانرێت بکرێتەوە. + ناتوانرێت ئەو جۆرە فایلە باربکرێت. + فایلەکانی دەنگ دەبێت کەمتر بێت لە ٤٠MB. + پێویستە فایلەکانی ڤیدیۆ کەمتر لە 40 مێگابایت بن. + فایلەکە دەبێت کەمتر بێت لە 8 مێگابایت. + ڕەستە زۆر درێژە! + سەرکەوتوو نەبوو لە بەدەستهێنانی نیشانەی چوونەژوورەوە. + ڕێپێدان ڕەتکرایەوە. + هەڵەیەک بۆ مۆڵەتدانی نەناسراو ڕووی دا. + نەیتوانی وێبگەڕبدۆزێتەوە بۆ بەکارهێنان. + سەرکەوتوو نەبوو، ڕاستکردنەوە لەگەڵ ئەم نمونەیە. + دۆمەینی نادروست تێنووسکرا + ئەمە ناتوانێت بەتاڵ بێت. + هەڵەیەک لە تۆڕ ڕوویدا! تکایە پەیوەندیت بپشکنە و دوبارە هەوڵ بدە! + هەڵەیەک ڕوویدا. + تایبەتمەندی بابەت گریمانەیی + دەرگای پرۆکسی HTTP + ڕاژەکاری پرۆکسی HTTP + چالاککردنی پرۆکسی HTTP + HTTP proxy + پرۆکسی + داگرتنی پێشبینینی میدیا + وەڵامدانەوەکان پیشان بدە + پیشاندانی بەهێزکردنەکان + سەرخشتەکان + فلتەرکردنی تایملاین + نمرەی لاری ڕەنگاوڕەنگ نیشان بدە بۆ میدیای شاراوە + وێنۆجکەی ئەنیمەی GIF + نیشاندەر نیشاندەر بۆ بۆتەکان نیشان بدە + زمان + دوگمەی ئاوازدانان بشارەوە لەکاتی خشاندن + بەکارهێنانی خشتەبەندەکانی دڵخواز + وێبگەڕ + دیزاینی سیستەم بەکاربهێنە + خۆکار لە کاتی خۆرئاوابووندا + ڕەش + ڕووناکی + تاریک + فلتەرەکان + ڕووکاری ئەپ + تایملاین + دەرکەوتن + کەسێک کە من بەشدارم لە بڵاو کردنەوەی توتێکی نوێیرکری + ڕاپرسی کۆتایی هاتووە + بابەتەکانی من پەسەندن + پۆستەکانم بەرزدەکرانەوه + بەدواداچوونەوەی داواکراو + بەدوادا + ناوبراو + ئاگادارم بکەوە کاتێک + ئاگاداربکەوە بە ڕووناکی + ئاگادارکردنەوەی لەلەرینە + ئاگادارکردنەوەی بە دەنگێک + ئاگادارکردنەوەکان + ئاگانامەکان + ئاگانامەکان + ڕاستەوخۆ: تەنها بۆ بەکارهێنەرانی ناوبراو پۆست بکە + تەنها شوێنکەوتوانی: تەنها پۆست بۆ شوێنکەوتوانی + لیستی نەکراو: لە هێڵی کاتی گشتی دا پیشان مەدە + گشتی: پۆست بکە بۆ هێڵی کاتی گشتی + شاردنەوەی ئاگانامەکان + بێدەنگکردن @%s؟ + بلۆککردنی @%s؟ + شاردنەوەی هەموو دۆمەینەکە + ئایا دڵنیایت لەوەی دەتەوێت هەموو %s بلۆک بکەیت؟ تۆ ناوەڕۆکێک نابینیت لە دۆمەینەکە لە هیچ هێڵی کاتی گشتی یان لە ئاگانامەکانت. شوێنکەوتوانی تۆ لەو دۆمەینەوە لادەبرێن. + ئەم دووانە بسڕەوە و دووبارە ڕەشنووس یان دەکەیتەوە؟ + ئەم توتە بسڕەوە؟ + شوێن نەکەوتنی ئەم هەژمارە؟ + داواکاری بەدوادا چوەکان هەڵوەشانەوە؟ + داگرتن + بارکردن… + تەواوکردنی بارکردنی میدیا + ناونیشان یان دۆمەینی هەر نمونەیەک دەکرێت لێرە تێبنووسرێت، وەک فرەتر! +\n +\nئەگەر هێشتا ئەژمێرێکت نیە، دەتوانیت ناوی ئەو نمونەیە داخڵ بکەیت کە دەتەوێت بیبەستیت و ئەژمێرێک دروست بکەیت لەوێ. +\n +\nنموونەیەک تاکە شوێنە کە ئەژمێرەکەت میوانداری کراوە، بەڵام دەتوانیت بە ئاسانی پەیوەندی لەگەڵ بکەیت و دوای ئەو خەڵکانە بکەویت لە نمونەکانی تر وەک ئەوەی تۆ لە هەمان سایت دابیت. +\n +\nزانیاری زیاتر دەتوانرێت بدۆزرێتەوە لە joinmastodon.org. + گرێدان… + نموونەیەک چییە؟ + سەرپەڕە + وێنۆچکە + وەڵام… + هیچ ئەنجامێک نیە + گەڕان… + دەربارە + ناوی پیشاندان + ئاگاداری ناوەڕۆک + گفتوگۆی بێدەنگ + + %d کاژێرماوە + %d کاژێرماوە + + کاتێک وشەکە یان دەستەواژەکە تەنها ئەبجەدییە، تەنها ئەگەر لەگەڵ هەموو وشەکە یەکبێت کاری پێدەکرێت + ناتوانیت زیاتر لە %1$d هاوپێچی میدیا باربکەیت. + شاردنەوەی زانیاری چەندێتی لەسەر پرۆفایلەکان + شاردنەوەی زانیاری چەندێتی لە بابەتەکان + سنووردارکردنی ئاگانامەکانی تایم لاین + پێداچوونەوەی ئاگانامەکان + هەندێک زانیاری کە لەوانەیە کاریگەری لەسەر باشبوونی دەروونیت دروست بکات دەشاردرێنەوە. ئەمە پێکدێت لە: +\n +\n- ئاگانامەکانی پەسەند/بەهێزکردن/بەدوادا +\n - پەسەندترین/بەرزکردنەوە لەسەر توت +\n - بەدواداچوون/زانیاری بابەت لەسەر پرۆفایلەکان +\n +\nکارتێکردنی ئاگانامەکانی پاڵپێوەنان، بەڵام دەتوانیت بە پەسەندکردنە ئاگانامەکانت دا بخشێنیەوە بە دەستی. + ڕزگارکرا + تێبینی تایبەتی تۆ دەربارەی ئەم ئەژمێرە + Wellbeing + شاردنەوەی ناونیشانی شریتی ئامڕازی سەرەوە + پیشاندانی دیالۆگی دووپاتکردنەوە پێش بەهێزکردن + نیشاندانی پێشاندانی بەستەر لە هێڵی کات + ماستۆدۆن کەمترین ماوەی خشتەی هەیە لە ٥ خولەک. + هیچ ڕاگه یه نراوێک له بەرده رنه کەون. + هیچ بارێکی خشتەکراوت نیە. + هیچ ڕەشنووسێکت نییە. + هەڵە لە گەڕان بەدوای بابەت %s + دەستکاریکردن + هەڵبژاردنی %d + چەند هەڵبژاردنێک + زیادکردنی هەڵبژاردن + ٧ ڕۆژ + ٣ ڕۆژ + ١ ڕۆژ + ٦ کاتژمێر + ١ کاتژمێر + ٣٠ خولەک + ٥ خولەک + ڕاپرسی + چالاککردنی ئاماژەکردنی لێدانی چالاک بۆ گۆڕین لە نێوان خشتەبەندەکان + تاسکی کۆد و سەرمایەکانی تێدایە لەم پڕۆژە کراوەی سەرچاوە: + فلتەری ئاگانامەکان نیشان بدە + گەڕانەکە سەرکەوتوو نەبوو + ئەژمێرەکان + هەژمارەلە ڕاژەیەکی دیکەیە ترە. کۆپیەکی بێ سەروبەر بنێرە بۆ ڕاپۆرتەکە لەوێ؟ + ڕاپۆرتەکە دەنێردرێت بۆ بەڕێوەبەری ڕاژەکەت. دەتوانیت ڕوونکردنەوەیەک پێشکەش بکەیت کە بۆچی ئەم ئەژمێرە لە خوارەوە ڕاپۆرت دەکەیت: + سەرکەوتوو نەبوو لە هێنانی بارەکان + ڕاپۆرتکردن سەرکەوتوو نەبوو + ناردنەوە بۆ %s + سەرنجەکانی زیاتر + سەرکەوتووانە ڕاپۆرتکرا @%s + تەواوبوو + دواوە + بەردەوام بە + + %d چرکەی ماوەو + %d دووەم چەپ + + + %d خولەک ماوە + %d خولەک ماوە + + + %d ڕۆژ ماوە + %d ڕۆژ ماوە + + ڕاپرسییەک کە دروستت کردووە کۆتایی هات + ڕاپرسییەک کە دەنگی پێداویت کۆتایی هات + دەنگ + داخراوە + کۆتایی دێت لە %s + + %s کەس + %s کەس + + + %s دەنگ + %s دەنگ + + %1$s • %2$s + کارەکان بۆ وێنە %s + ئایا دڵنیایت لەوەی دەتەوێت بە هەمیشەیی هەموو ئاگانامەکانت بسڕیتەوە؟ + دروستکردن + دروستکردنی توت + جێبەجێ کردن + فلتەر + سڕینەوە + لیست + دیاریکردنی لیست + هاشتاگی + هاشتاگی بێ # + هاشتاگی زیاد بکە + ناوی لیست + ڕاپرسی لەگەڵ هەڵبژاردنەکان: %1$s, %2$s, %3$s, %4$s; %5$s + ڕاستەوخۆ + شوێنکەوتوانی + لە لیست نەکراو + گشتی + نیشانکراوە + پەسەندکراو + دووبارە بڵاگ کرا + هیچ وەسفێک + ئاگاداری ناوەڕۆک: %s + میدیا: %s + بەرزترین رێژەی خشتەبەندەکانی %1$d گەیشت + %1$s, %2$s و %3$d زیاتر + %1$s و %2$s + %1$s + پەسەندکراوە لەلایەن + بەرزکراوە لەلایەن + + %s بەهێزکردن + %s بەهێزکردن + + + %1$s دڵخواز + %1$s دڵخواز + + Pin + لابردن + ڕەنگە زانیاری خوارەوە ڕەنگدانەوەی پرۆفایلی بەکارهێنەر بە ناتەواوی بێت. فشار بکە بۆ کردنەوەی پرۆفایلی تەواو لە وێبگەڕەکە. + کاتی ڕەها بەکاربهێنە + ناوەڕۆک + ناونیشان + داتا زیاد بکە + مێتاداتای پرۆفایل + CC-BY-SA 4.0 + CC-BY 4.0 + مۆڵەتدراوە لەژێر مۆڵەتی ئەپاچی (لەبەرگیراوە لە خوارەوە) + بێ هێزکردن + بەرزکردنەوە بۆ جەماوەری ڕەسەن + %1$s گواسترایەوە بۆ: + بۆت + داگرتن سەرکەوتوو نەبوو + کۆمەڵە ئیمۆجیەکەی ئێستای گووگڵ + سێتی ئیمۆجی پێوانەیی ماتۆدۆن + ئیمۆجی Blob لە ئەندرۆید ەوە ناسراوە 4.4–7.1 + سێتی ئیمۆجی بنەڕەتی ئامێرەکەت + دەستپێکردنەوە + دواتر + تۆ پێویستە توسکی دەستپێبکەیتەوە بۆ ئەوەی ئەم گۆڕانکاریانە جێبەجێ بکەیت + دەسپێکردنەوەی کاربەرنامە پێویستە + کردنەوە توت + فراوانکردن/نوشتانەوەی هەموو بارەکان + ئەنجامدانی گەڕان… + تۆ پێویستە سەرەتا ئەم سێتە ئیمۆجییانە دابگریت + سیستەمی بنەڕەت + شێوازی ئیمۆجی + ڕوونووسکراوە بۆ کلیپ بۆرد + نموونەکەت %s هیچ ئیمۆجییەکی ئاسایی نییە + دروستکردن + کۆپیەکی دەستنووسەکە خەزن کراوە بۆ ڕەشنووسەکانت + ناردنی هەڵوەشاوە + ناردنی توتس + هەڵە لە ناردنی توت + (توت) دەنێرم… + ڕەشنووس پاشەکەوت بکەیت؟ + داوات لێدەکات کە بە دەستی شوێنکەوتوانی پەسەند بکە + داخستنی ئەژمێر + لابردن + دانانی سەردێڕ + وەسف بکە بۆ بینایی داڕماو +\n(%d سنوری کاراکتەر) + دانانی سەردێڕ شکستی هێنا + بڵاوکردنەوە بە هەژماری %1$s + لابردنی ئەژمێر لە لیستەکە + زیادکردنی ئەژمێر بۆ لیستەکە + گەڕان بەدوای ئەو کەسانەی کە پەیڕەوی ان دەکەیت + دەستکاریکردنی لیستەکە + سڕینەوەی لیستەکە + ناونانەوەی لیستەکە + دروستکردنی لیستێک + نەیتوانی لیستەکە بسڕێتەوە + نەیتوانی ناوی لیست بنووسرێ + نەیتوانی لیست دروست بکات + لیستی تایم لاین + لیستەکان + لیستەکان + زیادکردنی ئەژمێری ماتۆدۆنی نوێ + زیادکردنی ئەژمێر + دەستەواژە بۆ فلتەر + هەموو وشەکە + نوێکردنەوە + لابردن + دەستکاریکردنی فلتەر + زیادکردنی فلتەر + گفتوگۆکان + هێڵی کاتی گشتی + بارکردنی زیاتر + وەڵام دانەوە بۆ @%s + میدیا + هەمیشە ئەو توتانەی کە بە ئاگادارکردنەوەکانی ناوەڕۆکەوە نیشانەکراون فراوان بکە + هەمیشە ناوەڕۆکی هەستیار نیشان بدە + دوای تۆ دەکەوێت + %ds + %dm + %dh + %dd + %dy + لە %ds + لە %dm + لە %dh + لە %dd + لە %dy + بەدواداچوونەوەی داواکراو + ڤیدیۆ + وێنەکان + هاوبەشکردنی لینک بۆ توت + هاوبەشکردنی ناوەڕۆکی دووت + پرۆفایلی تاسکی + ڕاپۆرتەکانی هەڵەکان و داواکاریەکانی تایبەتمەندی: +\nhttps://github.com/tuskyapp/Tusky/issues + وێبسایتی پڕۆژە: +\nhttps://tusky.app + توسکی سۆفتوێری ئازاد و سەرچاوەی کراوەیە مۆڵەتدراوە بە پێ نامەی گشتی GNU Public Version 3. دەتوانیت لێرە مۆڵەتەکە نیشان بدەی: https://www.gnu.org/licenses/gpl-3.0.en.html + لەلایەن تاسکیەوە دەست کراوە بە + توسکی %s + سەبارەت + هەژماری داخراو + %d چالاکی نوێ + %1$s و %2$s + %1$s و %2$s و %3$s + %1$s, %2$s, %3$s و %4$d ئەوانی تر + %s ئاماژەی بە تۆ کرد + ئاگانامەکان کاتێک کەسێک کە تۆ بەشداریت کردووە لە بڵاوکردنەوەی توتێکی نوێ + توتی نوێ + ئاگادارییەکان دەربارەی ڕاپرسییەکان کە کۆتایی هاتووە + ڕاپرسییەکان + ئاگانامەکان کاتێک کەتوتەکان نیشانە کراون وەک دڵخواز + دڵخوازەکان + ئاگانامەکان کاتێک کە دووتەکەت بەرز دەکرێتەوە + بەهێزکردن + ئاگانامەکان دەربارەی داواکاریەکانی بەدوادا + بەدواداچونی داواکاریەکان بکە + ئاگانامەکان دەربارەی شوێنکەوتوانی نوێ + شوێنکەوتوانی نوێ + ئاگانامەکان دەربارەی ئاماژە نوێیەکان + ئاماژە نوێیەکان + گەورەترین + گەورە + مامناوەندی + بچووک + بچووکترین + قەبارەی دەقی بار + شوێنکەوتوانی تەنها + لە لیست نەکراو + گشتی + خوارەوە + سەرەوە + شوێنی سەرەکی ڕێنیشاندەر + سەرکەوتوو نەبوو لە هاودەمکردنی ڕێکبەندەکان + بڵاوکردنەوە (هاوکاتکراوە لەگەڵ سێرڤەر) + هەمیشە میدیا وەک هەستیار نیشان بکە + \ No newline at end of file From 914e9a78f43776a439caf88061982a19137f1798 Mon Sep 17 00:00:00 2001 From: Gisele Galaburri Date: Thu, 14 Jan 2021 04:10:29 +0000 Subject: [PATCH 18/81] Translated using Weblate (Spanish) Currently translated at 99.7% (444 of 445 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/es/ --- app/src/main/res/values-es/strings.xml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index e81f543e..4072ae0c 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -479,4 +479,20 @@ Tu nota privada acerca de esta cuenta No hay anuncios. Anuncios + % recién publicado + No puedes cargar más de %1$d archivos adjuntos multimedia. + Esconder las estadísticas cuantitativas de los perfiles + Esconder las estadísticas cuantitativas de las publicaciones + Revisar Notificaciones + Bienestar + Notificaciones cuando alguien al que estoy suscrito publicó un nuevo toot + Nuevos toots + alguien al que estoy suscrito publicó un nuevo toot + Alguna información que podría afectar tu bienestar mental va a ser oculta. Esto incluye: +\n +\n- Favoritos/impulsar/Notificaciones de Follow +\n- Favoritos/Conteo de Impulsos en toots +\n- Seguidor/Publicar estadísticas en perfiles +\n +\nLas notificaciones Push no serán afectadas, pero puedes revisar manualmente tus referencias de notificaciones. \ No newline at end of file From 4ab33f0ad5e27fbe926df7048386081b02f4d8c6 Mon Sep 17 00:00:00 2001 From: Grandasse Date: Thu, 14 Jan 2021 04:10:29 +0000 Subject: [PATCH 19/81] Translated using Weblate (French) Currently translated at 100.0% (445 of 445 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/fr/ Translated using Weblate (French) Currently translated at 99.3% (442 of 445 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/fr/ --- app/src/main/res/values-fr/strings.xml | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 732f65a2..c0ad6bfd 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -94,7 +94,7 @@ Mentionner Cacher les médias Ouvrir le menu - Sauvegarder + Enregistrer Modifier le profil Modifier Annuler @@ -488,4 +488,21 @@ Votre note privée sur ce compte Il n’y a pas d’annonce. Annonces + Certaines informations susceptibles d\'affecter votre bien-être mental seront cachées. Il s\'agit : +\n +\n - des notifications de favoris, de partage et de suivi +\n - du compte des favoris/partages sur les pouets +\n - des statistiques sur les profils +\n +\n Les notifications \"push\" ne seront pas affectées, mais vous pouvez revoir vos préférences de notification manuellement. + une personne à laquelle je suis abonné a publié un nouveau pouet + %s vient de publier + Examiner les notifications + Nouveau pouets + Vous ne pouvez pas téléverser plus de %1$d pièces jointes. + Bien-être + Notifications quand quelqu\'un que vous suivez publie un nouveau pouet + Limiter les notifications de la timeline + Cacher les statistiques quantitatives sur les profils + Cacher les statistiques quantitatives sur les publications \ No newline at end of file From be60155de5f17fd5078df1cc95bf8fa06e4b9a37 Mon Sep 17 00:00:00 2001 From: Levi Bard Date: Fri, 15 Jan 2021 21:05:36 +0100 Subject: [PATCH 20/81] Implement timed mutes. (#2035) Fixes #2033 --- .../keylesspalace/tusky/AccountActivity.kt | 4 +-- .../components/search/SearchViewModel.kt | 4 +-- .../fragments/SearchStatusesFragment.kt | 4 +-- .../tusky/fragment/SFragment.java | 4 +-- .../tusky/network/MastodonApi.kt | 3 +- .../tusky/network/TimelineCases.kt | 6 ++-- .../tusky/view/MuteAccountDialog.kt | 9 +++-- .../tusky/viewmodel/AccountViewModel.kt | 8 ++--- .../main/res/layout/dialog_mute_account.xml | 15 +++++++- app/src/main/res/values-ar/strings.xml | 14 ++++---- app/src/main/res/values-bn-rBD/strings.xml | 14 ++++---- app/src/main/res/values-bn-rIN/strings.xml | 14 ++++---- app/src/main/res/values-ca/strings.xml | 14 ++++---- app/src/main/res/values-cs/strings.xml | 14 ++++---- app/src/main/res/values-de/strings.xml | 14 ++++---- app/src/main/res/values-eo/strings.xml | 14 ++++---- app/src/main/res/values-es/strings.xml | 14 ++++---- app/src/main/res/values-eu/strings.xml | 14 ++++---- app/src/main/res/values-fa/strings.xml | 14 ++++---- app/src/main/res/values-fr/strings.xml | 14 ++++---- app/src/main/res/values-ga/strings.xml | 14 ++++---- app/src/main/res/values-hi/strings.xml | 14 ++++---- app/src/main/res/values-hu/strings.xml | 14 ++++---- app/src/main/res/values-is/strings.xml | 14 ++++---- app/src/main/res/values-it/strings.xml | 14 ++++---- app/src/main/res/values-ja/strings.xml | 14 ++++---- app/src/main/res/values-kab/strings.xml | 14 ++++---- app/src/main/res/values-ko/strings.xml | 14 ++++---- app/src/main/res/values-nl/strings.xml | 14 ++++---- app/src/main/res/values-no-rNB/strings.xml | 14 ++++---- app/src/main/res/values-oc/strings.xml | 14 ++++---- app/src/main/res/values-pl/strings.xml | 14 ++++---- app/src/main/res/values-pt-rBR/strings.xml | 14 ++++---- app/src/main/res/values-ru/strings.xml | 14 ++++---- app/src/main/res/values-sa/strings.xml | 14 ++++---- app/src/main/res/values-sl/strings.xml | 14 ++++---- app/src/main/res/values-sv/strings.xml | 14 ++++---- app/src/main/res/values-ta/strings.xml | 6 ++-- app/src/main/res/values-th/strings.xml | 14 ++++---- app/src/main/res/values-tr/strings.xml | 14 ++++---- app/src/main/res/values-vi/strings.xml | 14 ++++---- app/src/main/res/values-zh-rCN/strings.xml | 14 ++++---- app/src/main/res/values/donottranslate.xml | 36 +++++++++++++++---- app/src/main/res/values/strings.xml | 16 +++++---- 44 files changed, 303 insertions(+), 260 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt index c16584eb..c89623d7 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt @@ -766,8 +766,8 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI showMuteAccountDialog( this, it.username - ) { notifications -> - viewModel.muteAccount(notifications) + ) { notifications, duration -> + viewModel.muteAccount(notifications, duration) } } } else { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/SearchViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/SearchViewModel.kt index da2c3fb1..08afe44d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/SearchViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/SearchViewModel.kt @@ -193,8 +193,8 @@ class SearchViewModel @Inject constructor( return accountManager.getAllAccountsOrderedByActive() } - fun muteAccount(accountId: String, notifications: Boolean) { - timelineCases.mute(accountId, notifications) + fun muteAccount(accountId: String, notifications: Boolean, duration: Int) { + timelineCases.mute(accountId, notifications, duration) } fun pinAccount(status: Status, isPin: Boolean) { 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 ffd69899..5fbbc715 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 @@ -377,8 +377,8 @@ class SearchStatusesFragment : SearchFragment - viewModel.muteAccount(accountId, notifications) + ) { notifications, duration -> + viewModel.muteAccount(accountId, notifications, duration) } } 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 8fe141c6..e1d50437 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java @@ -340,8 +340,8 @@ public abstract class SFragment extends BaseFragment implements Injectable { MuteAccountDialog.showMuteAccountDialog( this.getActivity(), accountUsername, - (notifications) -> { - timelineCases.mute(accountId, notifications); + (notifications, duration) -> { + timelineCases.mute(accountId, notifications, duration); return Unit.INSTANCE; } ); 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 08a5483d..7b79e1b5 100644 --- a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt +++ b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt @@ -330,7 +330,8 @@ interface MastodonApi { @POST("api/v1/accounts/{id}/mute") fun muteAccount( @Path("id") accountId: String, - @Field("notifications") notifications: Boolean? = null + @Field("notifications") notifications: Boolean? = null, + @Field("duration") duration: Int? = null ): Single @POST("api/v1/accounts/{id}/unmute") diff --git a/app/src/main/java/com/keylesspalace/tusky/network/TimelineCases.kt b/app/src/main/java/com/keylesspalace/tusky/network/TimelineCases.kt index efdb410b..8cf2b688 100644 --- a/app/src/main/java/com/keylesspalace/tusky/network/TimelineCases.kt +++ b/app/src/main/java/com/keylesspalace/tusky/network/TimelineCases.kt @@ -33,7 +33,7 @@ interface TimelineCases { fun reblog(status: Status, reblog: Boolean): Single fun favourite(status: Status, favourite: Boolean): Single fun bookmark(status: Status, bookmark: Boolean): Single - fun mute(id: String, notifications: Boolean) + fun mute(id: String, notifications: Boolean, duration: Int) fun block(id: String) fun delete(id: String): Single fun pin(status: Status, pin: Boolean) @@ -104,8 +104,8 @@ class TimelineCasesImpl( } } - override fun mute(id: String, notifications: Boolean) { - mastodonApi.muteAccount(id, notifications) + override fun mute(id: String, notifications: Boolean, duration: Int) { + mastodonApi.muteAccount(id, notifications, duration) .subscribe({ eventHub.dispatch(MuteEvent(id)) }, { t -> diff --git a/app/src/main/java/com/keylesspalace/tusky/view/MuteAccountDialog.kt b/app/src/main/java/com/keylesspalace/tusky/view/MuteAccountDialog.kt index 44a70267..435e2450 100644 --- a/app/src/main/java/com/keylesspalace/tusky/view/MuteAccountDialog.kt +++ b/app/src/main/java/com/keylesspalace/tusky/view/MuteAccountDialog.kt @@ -4,6 +4,7 @@ package com.keylesspalace.tusky.view import android.app.Activity import android.widget.CheckBox +import android.widget.Spinner import android.widget.TextView import androidx.appcompat.app.AlertDialog import com.keylesspalace.tusky.R @@ -11,7 +12,7 @@ import com.keylesspalace.tusky.R fun showMuteAccountDialog( activity: Activity, accountUsername: String, - onOk: (notifications: Boolean) -> Unit + onOk: (notifications: Boolean, duration: Int) -> Unit ) { val view = activity.layoutInflater.inflate(R.layout.dialog_mute_account, null) (view.findViewById(R.id.warning) as TextView).text = @@ -21,7 +22,11 @@ fun showMuteAccountDialog( AlertDialog.Builder(activity) .setView(view) - .setPositiveButton(android.R.string.ok) { _, _ -> onOk(checkbox.isChecked) } + .setPositiveButton(android.R.string.ok) { _, _ -> + val spinner: Spinner = view.findViewById(R.id.duration) + val durationValues = activity.resources.getIntArray(R.array.mute_duration_values) + onOk(checkbox.isChecked, durationValues[spinner.selectedItemPosition]) + } .setNegativeButton(android.R.string.cancel, null) .show() } \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/viewmodel/AccountViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/viewmodel/AccountViewModel.kt index b2568f34..a0f0ed68 100644 --- a/app/src/main/java/com/keylesspalace/tusky/viewmodel/AccountViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/viewmodel/AccountViewModel.kt @@ -119,8 +119,8 @@ class AccountViewModel @Inject constructor( } } - fun muteAccount(notifications: Boolean) { - changeRelationship(RelationShipAction.MUTE, notifications) + fun muteAccount(notifications: Boolean, duration: Int) { + changeRelationship(RelationShipAction.MUTE, notifications, duration) } fun unmuteAccount() { @@ -187,7 +187,7 @@ class AccountViewModel @Inject constructor( /** * @param parameter showReblogs if RelationShipAction.FOLLOW, notifications if MUTE */ - private fun changeRelationship(relationshipAction: RelationShipAction, parameter: Boolean? = null) { + private fun changeRelationship(relationshipAction: RelationShipAction, parameter: Boolean? = null, duration: Int? = null) { val relation = relationshipData.value?.data val account = accountData.value?.data val isMastodon = relationshipData.value?.data?.notifying != null @@ -227,7 +227,7 @@ class AccountViewModel @Inject constructor( RelationShipAction.UNFOLLOW -> mastodonApi.unfollowAccount(accountId) RelationShipAction.BLOCK -> mastodonApi.blockAccount(accountId) RelationShipAction.UNBLOCK -> mastodonApi.unblockAccount(accountId) - RelationShipAction.MUTE -> mastodonApi.muteAccount(accountId, parameter ?: true) + RelationShipAction.MUTE -> mastodonApi.muteAccount(accountId, parameter ?: true, duration) RelationShipAction.UNMUTE -> mastodonApi.unmuteAccount(accountId) RelationShipAction.SUBSCRIBE -> { if(isMastodon) diff --git a/app/src/main/res/layout/dialog_mute_account.xml b/app/src/main/res/layout/dialog_mute_account.xml index 673fc9e4..b58a277c 100644 --- a/app/src/main/res/layout/dialog_mute_account.xml +++ b/app/src/main/res/layout/dialog_mute_account.xml @@ -22,4 +22,17 @@ app:buttonTint="@color/compound_button_color" android:text="@string/dialog_mute_hide_notifications"/> - \ No newline at end of file + + + + + diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 52d36870..835a32f8 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -441,13 +441,13 @@ إضافة استطلاع رأي افتح دائما التبويقات التي تحتوي على محتوى حساس استطلاع رأي - 5 دقائق - 30 دقيقة - ساعة واحدة - 6 ساعات - يوم واحد - 3 أيام - 7 أيام + 5 دقائق + 30 دقيقة + ساعة واحدة + 6 ساعات + يوم واحد + 3 أيام + 7 أيام ضف خيارا خيارات متعددة الخيار %d diff --git a/app/src/main/res/values-bn-rBD/strings.xml b/app/src/main/res/values-bn-rBD/strings.xml index 2fb78dd8..2413319b 100644 --- a/app/src/main/res/values-bn-rBD/strings.xml +++ b/app/src/main/res/values-bn-rBD/strings.xml @@ -345,13 +345,13 @@ পছন্দ %d একাধিক পছন্দ পছন্দ যুক্ত করুন - ৭ দিন - ৩ দিন - ১ দিন - ৬ ঘন্টা - ১ ঘন্টা - ৩০ মিনিট - ৫ মিনিট + ৭ দিন + ৩ দিন + ১ দিন + ৬ ঘন্টা + ১ ঘন্টা + ৩০ মিনিট + ৫ মিনিট ভোটগ্রহণ সরান পোল যুক্ত করুন diff --git a/app/src/main/res/values-bn-rIN/strings.xml b/app/src/main/res/values-bn-rIN/strings.xml index d2e7a0aa..e578655a 100644 --- a/app/src/main/res/values-bn-rIN/strings.xml +++ b/app/src/main/res/values-bn-rIN/strings.xml @@ -395,13 +395,13 @@ অনুসন্ধান করতে ব্যর্থ পোল যুক্ত করুন ভোটগ্রহণ - ৫ মিনিট - ৩০ মিনিট - ১ ঘন্টা - ৬ ঘন্টা - ১ দিন - ৩ দিন - ৭ দিন + ৫ মিনিট + ৩০ মিনিট + ১ ঘন্টা + ৬ ঘন্টা + ১ দিন + ৩ দিন + ৭ দিন পছন্দ যুক্ত করুন একাধিক পছন্দ পছন্দ %d diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 882a3f38..a769dbe8 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -399,16 +399,16 @@ \@%s reportat satisfactoriament El compte és d\'un altre servidor. Enviar, igualment, una copia anònima del report\? Cerca fallida - 1 hora - 6 hores + 1 hora + 6 hores Edita Afegeix una enquesta Enquesta - 5 minuts - 30 minuts - 1 dia - 3 dies - 7 dies + 5 minuts + 30 minuts + 1 dia + 3 dies + 7 dies Afegeix una tria Múltiples tries Tria %d diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 42c5ce26..867b3ec2 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -422,13 +422,13 @@ Tento účet je z jiného serveru. Chcete na něj také poslat anonymizovanou kopii\? Zobrazit filtr oznámení Anketa - 5 minut - 30 minut - 1 hodinu - 6 hodin - 1 den - 3 dny - 7 dní + 5 minut + 30 minut + 1 hodinu + 6 hodin + 1 den + 3 dny + 7 dní Přidat možnost Lze zvolit více možností Možnost %d diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index edf0e7f6..6218bbbf 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -395,13 +395,13 @@ Dieses Konto ist von einem anderen Server. Soll eine anonymisierte Kopie des Berichts auch dorthin geschickt werden\? Benachrichtigungsfilter anzeigen Umfrage - 5 Minuten - 30 Minuten - 1 Stunde - 6 Stunden - 1 Tag - 3 Tage - 7 Tage + 5 Minuten + 30 Minuten + 1 Stunde + 6 Stunden + 1 Tag + 3 Tage + 7 Tage Editieren test %s Umfrage hinzufügen diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml index 82988898..e2e01108 100644 --- a/app/src/main/res/values-eo/strings.xml +++ b/app/src/main/res/values-eo/strings.xml @@ -407,13 +407,13 @@ Aldoni baloton Ĉiam pligrandigi tootoj markiĝita per enhavaj avertoj Baloto - 5 minutoj - 30 minutoj - 1 horo - 6 horoj - 1 tago - 3 tagoj - 7 tagoj + 5 minutoj + 30 minutoj + 1 horo + 6 horoj + 1 tago + 3 tagoj + 7 tagoj Aldoni elekton Multaj elektoj Elekton %d diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 4072ae0c..08955280 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -420,13 +420,13 @@ Error al buscar Añadir encuesta Encuesta - 5 minutos - 30 minutos - 1 hora - 6 horas - 1 día - 3 días - 7 días + 5 minutos + 30 minutos + 1 hora + 6 horas + 1 día + 3 días + 7 días Añadir opción Opciones múltiples Opción %d diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 6dabede9..f4eb9e71 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -417,13 +417,13 @@ Bilaketa huts egin du Erakutsi jakinarazpenen iragazkia Inkesta - 5 minutu - 30 minutu - Ordu 1 - 6 ordu - Egun 1 - 3 egun - 7 egun + 5 minutu + 30 minutu + Ordu 1 + 6 ordu + Egun 1 + 3 egun + 7 egun Gehitu aukera Aukera anitzak %d. aukera diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index a4284406..2f20b210 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -400,13 +400,13 @@ شکست در جست‌وجو نمایش پالایهٔ آگاهی‌ها نظرسنجی - ۵ دقیقه - ۳۰ دقیقه - ۱ ساعت - ۶ ساعت - ۱ روز - ۳ روز - ۷ روز + ۵ دقیقه + ۳۰ دقیقه + ۱ ساعت + ۶ ساعت + ۱ روز + ۳ روز + ۷ روز افزودن گزینه گزینه‌های چندگانه گزینهٔ %d diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index c0ad6bfd..6240d813 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -425,13 +425,13 @@ Toujours ouvrir les pouets avec un contenu sensible Ajouter un sondage Sondage - 5 minutes - 30 minutes - 1 heure - 6 heures - 1 jour - 3 jours - 7 jours + 5 minutes + 30 minutes + 1 heure + 6 heures + 1 jour + 3 jours + 7 jours Ajouter un choix Choix multiples Choix %d diff --git a/app/src/main/res/values-ga/strings.xml b/app/src/main/res/values-ga/strings.xml index fa8b8562..aad2a612 100644 --- a/app/src/main/res/values-ga/strings.xml +++ b/app/src/main/res/values-ga/strings.xml @@ -445,13 +445,13 @@ Taispeáin scagaire Fógraí Cumasaigh gotha swipe aistriú idir cluaisíní Vótaíocht - 5 nóiméad - 30 nóiméad - 1 uair an chloig - 6 uair an chloig - 1 lá - 3 lá - 7 lá + 5 nóiméad + 30 nóiméad + 1 uair an chloig + 6 uair an chloig + 1 lá + 3 lá + 7 lá Cuir rogha leis Ilroghanna Rogha %d diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 466019f6..a4c1b373 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -200,13 +200,13 @@ विकल्प जोड़ें विकल्प %d कई विकल्प - 7 दिन - 3 दिन - 1 दिन - 6 घंटे - 1 घंटा - 30 मिनिट - 5 मिनट + 7 दिन + 3 दिन + 1 दिन + 6 घंटे + 1 घंटा + 30 मिनिट + 5 मिनट %d घंटा शेष %d घंटे शेष diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 4c1f5cd7..9215ed09 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -417,13 +417,13 @@ Sikertelen keresés Szavazás hozzáadása Szavazás - 5 perc - 30 perc - 1 óra - 6 óra - 1 nap - 3 nap - 7 nap + 5 perc + 30 perc + 1 óra + 6 óra + 1 nap + 3 nap + 7 nap Válasz hozzáadása Több lehetőség Válasz %d diff --git a/app/src/main/res/values-is/strings.xml b/app/src/main/res/values-is/strings.xml index 4e27ac52..e52cda27 100644 --- a/app/src/main/res/values-is/strings.xml +++ b/app/src/main/res/values-is/strings.xml @@ -396,13 +396,13 @@ Tókst ekki að leita Birta tilkynningasíu Athuga - 5 mínútur - 30 mínútur - 1 klukkustund - 6 klukkustundir - 1 dagur - 3 dagar - 7 dagar + 5 mínútur + 30 mínútur + 1 klukkustund + 6 klukkustundir + 1 dagur + 3 dagar + 7 dagar Bæta við valkosti Margir valkostir Valkostur %d diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 01dbcb69..3b716a1f 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -431,13 +431,13 @@ Errore durante la ricerca Mostra il filtro delle notifiche Sondaggio - 5 minuti - 30 minuti - 1 ora - 6 ore - 1 giorno - 3 giorni - 7 giorni + 5 minuti + 30 minuti + 1 ora + 6 ore + 1 giorno + 3 giorni + 7 giorni Aggiungi scelta Scelte multiple Scelta %d diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 37ecd192..df4ec931 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -370,13 +370,13 @@ 参加した投票の結果がでました 作成した投票の結果がでました 投票 - 5分 - 30分 - 1時間 - 6時間 - 1日 - 3日 - 7日 + 5分 + 30分 + 1時間 + 6時間 + 1日 + 3日 + 7日 選択肢を追加 複数選択可 編集 diff --git a/app/src/main/res/values-kab/strings.xml b/app/src/main/res/values-kab/strings.xml index 3c10f80b..8f51de5e 100644 --- a/app/src/main/res/values-kab/strings.xml +++ b/app/src/main/res/values-kab/strings.xml @@ -196,13 +196,13 @@ Tella-d tuccḍa deg cetki Tucḍa n unadi Assenqed - 5 n tisdidin - 30 n tisdidin - 1 n usrag - 6 n isragen - 1 n wass - 3 n wussan - 7 n wussan + 5 n tisdidin + 30 n tisdidin + 1 n usrag + 6 n isragen + 1 n wass + 3 n wussan + 7 n wussan Tafrant %d Ig ṭṭafar Imeḍfaṛen diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 4689c5af..cf11c797 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -409,13 +409,13 @@ 열람주의로 설정된 툿을 항상 펼치기 투표 추가 투표 - 5분 - 30분 - 1시간 - 6시간 - 1일 - 3일 - 7일 + 5분 + 30분 + 1시간 + 6시간 + 1일 + 3일 + 7일 항목 추가 여러 항목 선택 가능 %d번 항목 diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index d24e48c5..b8d2d09d 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -418,13 +418,13 @@ Meldingenfilter tonen Heel woord Wanneer het trefwoord of zinsdeel alfanumeriek is, wordt het alleen gefilterd wanneer het hele woord overeenkomt - 5 minuten - 30 minuten - 1 uur - 6 uur - 1 dag - 3 dagen - 7 dagen + 5 minuten + 30 minuten + 1 uur + 6 uur + 1 dag + 3 dagen + 7 dagen Voeg keuze toe Meerdere keuzes Keuze %d diff --git a/app/src/main/res/values-no-rNB/strings.xml b/app/src/main/res/values-no-rNB/strings.xml index 9201edb7..cd633dd0 100644 --- a/app/src/main/res/values-no-rNB/strings.xml +++ b/app/src/main/res/values-no-rNB/strings.xml @@ -408,13 +408,13 @@ Ekspander alltid toots markert med innholdsadvarsel Legg til avstemming Avstemming - 5 minutter - 30 minutter - 1 time - 6 timer - 1 dag - 3 dager - 7 dager + 5 minutter + 30 minutter + 1 time + 6 timer + 1 dag + 3 dager + 7 dager Legg til valg Flere valg Valg %d diff --git a/app/src/main/res/values-oc/strings.xml b/app/src/main/res/values-oc/strings.xml index 2ffd91ed..c22afc29 100644 --- a/app/src/main/res/values-oc/strings.xml +++ b/app/src/main/res/values-oc/strings.xml @@ -419,13 +419,13 @@ Fracàs de la recèrca Ajustar un sondatge Sondatge - 5 minutas - 30 minutas - 1 ora - 6 oras - 1 jorn - 3 jorns - 7 jorns + 5 minutas + 30 minutas + 1 ora + 6 oras + 1 jorn + 3 jorns + 7 jorns Ajustar d’opcions Opcions multiplas Opcion %d diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 5a6ec295..9082f634 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -430,13 +430,13 @@ Wyszukiwanie nie powidło się Pokaż filtr powiadomień Głosowanie - 5 minut - 30 minut - 1 godzina - 6 godzin - 1 dzień - 3 dni - 7 dni + 5 minut + 30 minut + 1 godzina + 6 godzin + 1 dzień + 3 dni + 7 dni Dodaj wybór Kilka wyborów Opcja %d diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index c5e9a7aa..95bf8a29 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -417,13 +417,13 @@ Contas Erro ao pesquisar Enquete - 5 minutos - 30 minutos - 1 hora - 6 horas - 1 dia - 3 dias - 7 dias + 5 minutos + 30 minutos + 1 hora + 6 horas + 1 dia + 3 dias + 7 dias Adicionar opção Múltiplas opções Opção %d diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index a13d58ac..6adb5768 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -444,13 +444,13 @@ Аккаунты Поиск завершился ошибкой Опрос - 5 минут - 30 минут - 1 час - 6 часов - 1 день - 3 дня - 7 дней + 5 минут + 30 минут + 1 час + 6 часов + 1 день + 3 дня + 7 дней Добавить Множественный выбор Вариант %d diff --git a/app/src/main/res/values-sa/strings.xml b/app/src/main/res/values-sa/strings.xml index a322838e..2fb217ad 100644 --- a/app/src/main/res/values-sa/strings.xml +++ b/app/src/main/res/values-sa/strings.xml @@ -373,13 +373,13 @@ मतम् %d बहूनि मतानि अपरं मतं युज्यताम् - ७ दिनानि - ३ दिनानि - १ दिनम् - ६ घण्टाः - १ घण्टा - ३० निमेषाः - ५ निमेषाः + ७ दिनानि + ३ दिनानि + १ दिनम् + ६ घण्टाः + १ घण्टा + ३० निमेषाः + ५ निमेषाः मतपेटिका सारणहावभावस्य संयुतनं पीठिकापरिवर्तनार्थं कार्यम् सूचनाशोधकं दृश्यताम् diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml index 5f4ca650..70bb80be 100644 --- a/app/src/main/res/values-sl/strings.xml +++ b/app/src/main/res/values-sl/strings.xml @@ -415,13 +415,13 @@ Vedno razširite tute, označene z opozorilom o vsebini Dodaj anketo Anketa - 5 minut - 30 minut - 1 ura - 6 ur - 1 dan - 3 dni - 7 dni + 5 minut + 30 minut + 1 ura + 6 ur + 1 dan + 3 dni + 7 dni Dodaj izbiro Več izbir Izbira %d diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 6b5296a3..ff8a5a13 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -425,13 +425,13 @@ Sökning misslyckades Skapa en omröstning Omröstning - 5 minuter - 30 minuter - 1 timme - 6 timmar - 1 dag - 3 dagar - 7 dagar + 5 minuter + 30 minuter + 1 timme + 6 timmar + 1 dag + 3 dagar + 7 dagar Lägg till alternativ Flerval Val %d diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml index 2a5a035e..2affaa29 100644 --- a/app/src/main/res/values-ta/strings.xml +++ b/app/src/main/res/values-ta/strings.xml @@ -265,9 +265,9 @@ நேரடி தகவல் பட்டைகள் பொருத்தப்பட்டது - 1 நாள் - 3 நாட்கள் - 7 நாட்கள் + 1 நாள் + 3 நாட்கள் + 7 நாட்கள் விருப்பத்தைச் சேர் பின்பற்ற கோரிக்கை நீக்கு diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index ea831d51..5c994db6 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -1,13 +1,13 @@ เพิ่มตัวเลือก - 7 วัน - 3 วัน - 1 วัน - 6 ชั่วโมง - 1 ชั่วโมง - 30 นาที - 5 นาที + 7 วัน + 3 วัน + 1 วัน + 6 ชั่วโมง + 1 ชั่วโมง + 30 นาที + 5 นาที โพล เปิดใช้งานการเลื่อนนิ้วเพื่อสลับระหว่างแท็บ แสดงตัวกรองการแจ้งเตือน diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index fdf123a1..596bafa4 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -418,13 +418,13 @@ Hesaplar Arama başarısız Anket - 5 dakika - 30 dakika - 1 saat - 6 saat - 1 gün - 3 gün - 7 gün + 5 dakika + 30 dakika + 1 saat + 6 saat + 1 gün + 3 gün + 7 gün Seçenek ekle Çoklu seçim Düzenle diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 295c438b..460ebb2d 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -318,13 +318,13 @@ Lựa chọn %d Cho phép chọn nhiều lựa chọn Thêm lựa chọn - 7 ngày - 3 ngày - 1 ngày - 6 giờ - 1 giờ - 30 phút - 5 phút + 7 ngày + 3 ngày + 1 ngày + 6 giờ + 1 giờ + 30 phút + 5 phút Bình chọn Vuốt qua lại giữa các tab Hiện bộ lọc thông báo diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index ccd20b0b..03c70771 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -436,13 +436,13 @@ 搜索失败 显示通知过滤器 投票 - 5 分钟 - 30 分钟 - 1 小时 - 6 小时 - 1 天 - 3 天 - 7 天 + 5 分钟 + 30 分钟 + 1 小时 + 6 小时 + 1 天 + 3 天 + 7 天 添加选择 多项选择 选择 %d diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 027247a0..794682c6 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -145,13 +145,13 @@ - @string/poll_duration_5_min - @string/poll_duration_30_min - @string/poll_duration_1_hour - @string/poll_duration_6_hours - @string/poll_duration_1_day - @string/poll_duration_3_days - @string/poll_duration_7_days + @string/duration_5_min + @string/duration_30_min + @string/duration_1_hour + @string/duration_6_hours + @string/duration_1_day + @string/duration_3_days + @string/duration_7_days @@ -164,5 +164,27 @@ 604800 + + @string/duration_indefinite + @string/duration_5_min + @string/duration_30_min + @string/duration_1_hour + @string/duration_6_hours + @string/duration_1_day + @string/duration_3_days + @string/duration_7_days + + + + 0 + 300 + 1800 + 3600 + 21600 + 86400 + 259200 + 604800 + + <b>%1$d%%</b> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 87d63df5..d0bc048d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -558,13 +558,15 @@ Poll - 5 minutes - 30 minutes - 1 hour - 6 hours - 1 day - 3 days - 7 days + Duration + Indefinite + 5 minutes + 30 minutes + 1 hour + 6 hours + 1 day + 3 days + 7 days Add choice Multiple choices Choice %d From 4c7d09b1e39465255f1a54c83182e3d938e03951 Mon Sep 17 00:00:00 2001 From: Garrit Franke <32395585+garritfra@users.noreply.github.com> Date: Fri, 15 Jan 2021 21:16:32 +0100 Subject: [PATCH 21/81] Show display names for reposts (#2041) * WIP: Show display names for reposts * Display emojis on reposts * Move unicode wrapping Co-authored-by: Garrit Franke --- .../tusky/adapter/StatusViewHolder.java | 12 +++++++---- .../tusky/util/ViewDataUtils.java | 3 ++- .../tusky/viewdata/StatusViewData.java | 20 ++++++++++++++++--- 3 files changed, 27 insertions(+), 8 deletions(-) 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 56cef6ee..4a0f6679 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java @@ -27,8 +27,10 @@ import androidx.recyclerview.widget.RecyclerView; import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.interfaces.StatusActionListener; +import com.keylesspalace.tusky.util.CustomEmojiHelper; import com.keylesspalace.tusky.util.SmartLengthInputFilter; import com.keylesspalace.tusky.util.StatusDisplayOptions; +import com.keylesspalace.tusky.util.StringUtils; import com.keylesspalace.tusky.viewdata.StatusViewData; import at.connyduck.sparkbutton.helpers.Utils; @@ -64,7 +66,7 @@ public class StatusViewHolder extends StatusBaseViewHolder { if (rebloggedByDisplayName == null) { hideStatusInfo(); } else { - setRebloggedByDisplayName(rebloggedByDisplayName); + setRebloggedByDisplayName(rebloggedByDisplayName, status); statusInfo.setOnClickListener(v -> listener.onOpenReblog(getAdapterPosition())); } @@ -73,10 +75,12 @@ public class StatusViewHolder extends StatusBaseViewHolder { } - private void setRebloggedByDisplayName(final String name) { + private void setRebloggedByDisplayName(final CharSequence name, final StatusViewData.Concrete status) { Context context = statusInfo.getContext(); - String boostedText = context.getString(R.string.status_boosted_format, name); - statusInfo.setText(boostedText); + CharSequence wrappedName = StringUtils.unicodeWrap(name); + CharSequence boostedText = context.getString(R.string.status_boosted_format, wrappedName); + CharSequence emojifiedText = CustomEmojiHelper.emojify(boostedText, status.getRebloggedByAccountEmojis(), statusInfo); + statusInfo.setText(emojifiedText); statusInfo.setVisibility(View.VISIBLE); } diff --git a/app/src/main/java/com/keylesspalace/tusky/util/ViewDataUtils.java b/app/src/main/java/com/keylesspalace/tusky/util/ViewDataUtils.java index 69faa6fd..ffe64a14 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/ViewDataUtils.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/ViewDataUtils.java @@ -52,7 +52,7 @@ public final class ViewDataUtils { .setSensitive(visibleStatus.getSensitive()) .setIsShowingSensitiveContent(alwaysShowSensitiveMedia || !visibleStatus.getSensitive()) .setSpoilerText(visibleStatus.getSpoilerText()) - .setRebloggedByUsername(status.getReblog() == null ? null : status.getAccount().getUsername()) + .setRebloggedByUsername(status.getReblog() == null ? null : status.getAccount().getDisplayName()) .setUserFullName(visibleStatus.getAccount().getName()) .setVisibility(visibleStatus.getVisibility()) .setSenderId(visibleStatus.getAccount().getId()) @@ -60,6 +60,7 @@ public final class ViewDataUtils { .setApplication(visibleStatus.getApplication()) .setStatusEmojis(visibleStatus.getEmojis()) .setAccountEmojis(visibleStatus.getAccount().getEmojis()) + .setRebloggedByEmojis(status.getReblog() == null ? null : status.getAccount().getEmojis()) .setCollapsible(SmartLengthInputFilterKt.shouldTrimStatus(visibleStatus.getContent())) .setCollapsed(true) .setPoll(visibleStatus.getPoll()) diff --git a/app/src/main/java/com/keylesspalace/tusky/viewdata/StatusViewData.java b/app/src/main/java/com/keylesspalace/tusky/viewdata/StatusViewData.java index 81e10100..c0ceeb81 100644 --- a/app/src/main/java/com/keylesspalace/tusky/viewdata/StatusViewData.java +++ b/app/src/main/java/com/keylesspalace/tusky/viewdata/StatusViewData.java @@ -16,10 +16,11 @@ package com.keylesspalace.tusky.viewdata; import android.os.Build; -import androidx.annotation.Nullable; import android.text.SpannableStringBuilder; import android.text.Spanned; +import androidx.annotation.Nullable; + import com.keylesspalace.tusky.entity.Attachment; import com.keylesspalace.tusky.entity.Card; import com.keylesspalace.tusky.entity.Emoji; @@ -85,6 +86,7 @@ public abstract class StatusViewData { private final Status.Application application; private final List statusEmojis; private final List accountEmojis; + private final List rebloggedByAccountEmojis; @Nullable private final Card card; private final boolean isCollapsible; /** Whether the status meets the requirement to be collapse */ @@ -99,7 +101,7 @@ public abstract class StatusViewData { boolean isShowingContent, String userFullName, String nickname, String avatar, Date createdAt, int reblogsCount, int favouritesCount, @Nullable String inReplyToId, @Nullable Status.Mention[] mentions, String senderId, boolean rebloggingEnabled, - Status.Application application, List statusEmojis, List accountEmojis, @Nullable Card card, + Status.Application application, List statusEmojis, List accountEmojis, List rebloggedByAccountEmojis, @Nullable Card card, boolean isCollapsible, boolean isCollapsed, @Nullable PollViewData poll, boolean isBot) { this.id = id; @@ -136,6 +138,7 @@ public abstract class StatusViewData { this.application = application; this.statusEmojis = statusEmojis; this.accountEmojis = accountEmojis; + this.rebloggedByAccountEmojis = rebloggedByAccountEmojis; this.card = card; this.isCollapsible = isCollapsible; this.isCollapsed = isCollapsed; @@ -258,6 +261,10 @@ public abstract class StatusViewData { return accountEmojis; } + public List getRebloggedByAccountEmojis() { + return rebloggedByAccountEmojis; + } + @Nullable public Card getCard() { return card; @@ -324,6 +331,7 @@ public abstract class StatusViewData { Objects.equals(application, concrete.application) && Objects.equals(statusEmojis, concrete.statusEmojis) && Objects.equals(accountEmojis, concrete.accountEmojis) && + Objects.equals(rebloggedByAccountEmojis, concrete.rebloggedByAccountEmojis) && Objects.equals(card, concrete.card) && Objects.equals(poll, concrete.poll) && isCollapsed == concrete.isCollapsed; @@ -429,6 +437,7 @@ public abstract class StatusViewData { private Status.Application application; private List statusEmojis; private List accountEmojis; + private List rebloggedByAccountEmojis; private Card card; private boolean isCollapsible; /** Whether the status meets the requirement to be collapsed */ private boolean isCollapsed; /** Whether the status is shown partially or fully */ @@ -613,6 +622,11 @@ public abstract class StatusViewData { return this; } + public Builder setRebloggedByEmojis(List emojis) { + this.rebloggedByAccountEmojis = emojis; + return this; + } + public Builder setCard(Card card) { this.card = card; return this; @@ -656,7 +670,7 @@ public abstract class StatusViewData { visibility, attachments, rebloggedByUsername, rebloggedAvatar, isSensitive, isExpanded, isShowingContent, userFullName, nickname, avatar, createdAt, reblogsCount, favouritesCount, inReplyToId, mentions, senderId, rebloggingEnabled, application, - statusEmojis, accountEmojis, card, isCollapsible, isCollapsed, poll, isBot); + statusEmojis, accountEmojis, rebloggedByAccountEmojis, card, isCollapsible, isCollapsed, poll, isBot); } } } From 5fbd459aed1fc9ac59109c795b4c83e3448aaa80 Mon Sep 17 00:00:00 2001 From: Levi Bard Date: Fri, 15 Jan 2021 21:23:02 +0100 Subject: [PATCH 22/81] Make tapping the main toolbar scroll to the top of the timeline. #1899 (#2045) --- app/src/main/java/com/keylesspalace/tusky/MainActivity.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt index 31a4e80c..bb7b5a5a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt @@ -554,6 +554,9 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje val activeTabPosition = if (selectNotificationTab) notificationTabPosition else 0 mainToolbar.title = tabs[activeTabPosition].title(this@MainActivity) + mainToolbar.setOnClickListener { + (adapter.getFragment(activeTabLayout.selectedTabPosition) as? ReselectableFragment)?.onReselect() + } } From cb2296f2482bca1ad97483ce7079d460561ca99e Mon Sep 17 00:00:00 2001 From: Levi Bard Date: Mon, 18 Jan 2021 11:40:13 +0100 Subject: [PATCH 23/81] Prompt before deleting lists (#2043) * Prompt before deleting lists. #1998 * Address pull request feedback --- .../com/keylesspalace/tusky/ListsActivity.kt | 16 ++++++++++++---- app/src/main/res/values/strings.xml | 1 + 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt b/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt index 3894f652..994069f7 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt @@ -130,9 +130,7 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector { else R.string.action_rename_list) { _, _ -> onPickedDialogName(editText.text, list?.id) } - .setNegativeButton(android.R.string.cancel) { d, _ -> - d.dismiss() - } + .setNegativeButton(android.R.string.cancel, null) .show() val positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE) @@ -143,6 +141,16 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector { editText.text?.let { editText.setSelection(it.length) } } + private fun showListDeleteDialog(list: MastoList) { + AlertDialog.Builder(this) + .setMessage(getString(R.string.dialog_delete_list_warning, list.title)) + .setPositiveButton(R.string.action_delete){ _, _ -> + viewModel.deleteList(list.id) + } + .setNegativeButton(android.R.string.cancel, null) + .show() + } + private fun update(state: ListsViewModel.State) { adapter.submitList(state.lists) @@ -199,7 +207,7 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector { when (item.itemId) { R.id.list_edit -> openListSettings(list) R.id.list_rename -> renameListDialog(list) - R.id.list_delete -> viewModel.deleteList(list.id) + R.id.list_delete -> showListDeleteDialog(list) else -> return@setOnMenuItemClickListener false } true diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d0bc048d..7f1ec78f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -594,5 +594,6 @@ Hide quantitative stats on posts Hide quantitative stats on profiles You cannot upload more than %1$d media attachments. + Do you really want to delete the list %s? From baa915a0a3d3a5b9204e527dcb526559731a0fc8 Mon Sep 17 00:00:00 2001 From: Levi Bard Date: Mon, 18 Jan 2021 13:53:13 +0100 Subject: [PATCH 24/81] Support opening unknown attachment types via `openLink` (#2044) * Support opening unknown attachment types via openLink. #1970 * Fix label text for unknown attachment types --- .../tusky/adapter/StatusBaseViewHolder.java | 13 +++++++------ .../conversation/ConversationViewHolder.java | 2 +- .../search/fragments/SearchStatusesFragment.kt | 2 ++ .../tusky/fragment/AccountMediaFragment.kt | 7 +++---- .../com/keylesspalace/tusky/fragment/SFragment.java | 6 +++--- .../keylesspalace/tusky/util/StatusViewHelper.kt | 6 ++++-- app/src/main/res/values/strings.xml | 2 ++ 7 files changed, 22 insertions(+), 16 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 046ab9cf..5bce0624 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java @@ -533,7 +533,6 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { @DrawableRes private static int getLabelIcon(Attachment.Type type) { switch (type) { - default: case IMAGE: return R.drawable.ic_photo_24dp; case GIFV: @@ -541,6 +540,8 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { return R.drawable.ic_videocam_24dp; case AUDIO: return R.drawable.ic_music_box_24dp; + default: + return R.drawable.ic_attach_file_24dp; } } @@ -718,7 +719,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { setBookmarked(status.isBookmarked()); List attachments = status.getAttachments(); boolean sensitive = status.isSensitive(); - if (statusDisplayOptions.mediaPreviewEnabled() && !hasAudioAttachment(attachments)) { + if (statusDisplayOptions.mediaPreviewEnabled() && hasPreviewableAttachment(attachments)) { setMediaPreviews(attachments, sensitive, listener, status.isShowingContent(), statusDisplayOptions.useBlurhash()); if (attachments.size() == 0) { @@ -767,13 +768,13 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { } } - protected static boolean hasAudioAttachment(List attachments) { + protected static boolean hasPreviewableAttachment(List attachments) { for (Attachment attachment : attachments) { - if (attachment.getType() == Attachment.Type.AUDIO) { - return true; + if (attachment.getType() == Attachment.Type.AUDIO || attachment.getType() == Attachment.Type.UNKNOWN) { + return false; } } - return false; + return true; } private void setDescriptionForStatus(@NonNull StatusViewData.Concrete status, 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 6727156d..19ef749e 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 @@ -83,7 +83,7 @@ public class ConversationViewHolder extends StatusBaseViewHolder { setBookmarked(status.getBookmarked()); List attachments = status.getAttachments(); boolean sensitive = status.getSensitive(); - if (statusDisplayOptions.mediaPreviewEnabled() && !hasAudioAttachment(attachments)) { + if (statusDisplayOptions.mediaPreviewEnabled() && hasPreviewableAttachment(attachments)) { setMediaPreviews(attachments, sensitive, listener, status.getShowingHiddenContent(), statusDisplayOptions.useBlurhash()); 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 5fbbc715..bc7ac2ba 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 @@ -54,6 +54,7 @@ import com.keylesspalace.tusky.interfaces.AccountSelectionListener import com.keylesspalace.tusky.interfaces.StatusActionListener import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.util.CardViewMode +import com.keylesspalace.tusky.util.LinkHelper import com.keylesspalace.tusky.util.NetworkState import com.keylesspalace.tusky.util.StatusDisplayOptions import com.keylesspalace.tusky.view.showMuteAccountDialog @@ -143,6 +144,7 @@ class SearchStatusesFragment : SearchFragment { + LinkHelper.openLink(actionable.attachments[attachmentIndex].url, context) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt index ed1fbad8..0ae20758 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt @@ -34,6 +34,7 @@ import com.keylesspalace.tusky.entity.Attachment import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.interfaces.RefreshableFragment import com.keylesspalace.tusky.network.MastodonApi +import com.keylesspalace.tusky.util.LinkHelper import com.keylesspalace.tusky.util.ThemeUtils import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.show @@ -260,10 +261,8 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable { } } Attachment.Type.UNKNOWN -> { - }/* Intentionally do nothing. This case is here is to handle when new attachment - * types are added to the API before code is added here to handle them. So, the - * best fallback is to just show the preview and ignore requests to view them. */ - + LinkHelper.openLink(items[currentIndex].attachment.url, context) + } } } 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 e1d50437..4c50740b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java @@ -63,6 +63,7 @@ import com.keylesspalace.tusky.entity.PollOption; 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.view.MuteAccountDialog; import com.keylesspalace.tusky.viewdata.AttachmentViewData; @@ -395,10 +396,9 @@ public abstract class SFragment extends BaseFragment implements Injectable { } break; } + default: case UNKNOWN: { - /* Intentionally do nothing. This case is here is to handle when new attachment - * types are added to the API before code is added here to handle them. So, the - * best fallback is to just show the preview and ignore requests to view them. */ + LinkHelper.openLink(active.getUrl(), getContext()); break; } } 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 2fb9ad42..db0441bd 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt @@ -228,7 +228,8 @@ class StatusViewHelper(private val itemView: View) { return when (type) { Attachment.Type.IMAGE -> context.getString(R.string.status_media_images) Attachment.Type.GIFV, Attachment.Type.VIDEO -> context.getString(R.string.status_media_video) - else -> context.getString(R.string.status_media_images) + Attachment.Type.AUDIO -> context.getString(R.string.status_media_audio) + else -> context.getString(R.string.status_media_attachments) } } @@ -237,7 +238,8 @@ class StatusViewHelper(private val itemView: View) { return when (type) { Attachment.Type.IMAGE -> R.drawable.ic_photo_24dp Attachment.Type.GIFV, Attachment.Type.VIDEO -> R.drawable.ic_videocam_24dp - else -> R.drawable.ic_photo_24dp + Attachment.Type.AUDIO -> R.drawable.ic_music_box_24dp + else -> R.drawable.ic_attach_file_24dp } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7f1ec78f..6b08043e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -326,6 +326,8 @@ Share link to toot Images Video + Audio + Attachments Follow requested From 940d6d395a98518467662f5f7275851d59e0aa9c Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Thu, 21 Jan 2021 18:57:09 +0100 Subject: [PATCH 25/81] Drafts v2 (#2032) * cleanup warnings, reorganize some code * move ComposeAutoCompleteAdapter to compose package * composeOptions doesn't need to be a class member * add DraftsActivity and DraftsViewModel * drafts * remove unnecessary Unit in ComposeViewModel * add schema/25.json * fix db migration * drafts * cleanup code * fix compose activity rotation bug * fix media descriptions getting lost when restoring a draft * improve deleting drafts * fix ComposeActivityTest * improve draft layout for almost empty drafts * reformat code * show toast when opening reply to deleted toot * improve item_draft layout --- app/build.gradle | 3 + .../25.json | 821 ++++++++++++++++++ app/src/main/AndroidManifest.xml | 1 + .../com/keylesspalace/tusky/MainActivity.kt | 32 +- .../tusky/SavedTootActivity.java | 4 +- .../components/compose/ComposeActivity.kt | 86 +- .../compose}/ComposeAutoCompleteAdapter.java | 2 +- .../components/compose/ComposeViewModel.kt | 159 ++-- .../tusky/components/compose/MediaUploader.kt | 8 +- .../tusky/components/drafts/DraftHelper.kt | 159 ++++ .../components/drafts/DraftMediaAdapter.kt | 81 ++ .../tusky/components/drafts/DraftsActivity.kt | 197 +++++ .../tusky/components/drafts/DraftsAdapter.kt | 92 ++ .../components/drafts/DraftsViewModel.kt | 69 ++ .../scheduled/ScheduledTootActivity.kt | 2 +- .../keylesspalace/tusky/db/AppDatabase.java | 24 +- .../com/keylesspalace/tusky/db/Converters.kt | 26 +- .../com/keylesspalace/tusky/db/DraftDao.kt | 40 + .../com/keylesspalace/tusky/db/DraftEntity.kt | 55 ++ .../tusky/db/TimelineStatusEntity.kt | 2 +- .../com/keylesspalace/tusky/db/TootDao.java | 11 +- .../tusky/di/ActivitiesModule.kt | 4 + .../com/keylesspalace/tusky/di/AppModule.kt | 2 +- .../tusky/di/ViewModelFactory.kt | 6 + .../tusky/network/MastodonApi.kt | 8 +- .../receiver/SendStatusBroadcastReceiver.kt | 34 +- .../tusky/service/SendTootService.kt | 34 +- .../tusky/util/BindingViewHolder.kt | 8 + .../tusky/util/RxAwareViewModel.kt | 2 + .../tusky/util/SaveTootHelper.java | 150 +--- app/src/main/res/drawable/ic_alert_circle.xml | 8 + app/src/main/res/drawable/ic_notebook.xml | 2 +- .../layout-sw640dp/fragment_view_thread.xml | 4 +- app/src/main/res/layout/activity_compose.xml | 3 + app/src/main/res/layout/activity_drafts.xml | 34 + .../main/res/layout/fragment_view_thread.xml | 4 +- app/src/main/res/layout/item_draft.xml | 95 ++ app/src/main/res/layout/toolbar_basic.xml | 27 +- app/src/main/res/menu/drafts.xml | 10 + app/src/main/res/values-ar/strings.xml | 2 +- app/src/main/res/values-ber/strings.xml | 2 +- app/src/main/res/values-bn-rBD/strings.xml | 2 +- app/src/main/res/values-bn-rIN/strings.xml | 2 +- app/src/main/res/values-ca/strings.xml | 2 +- app/src/main/res/values-cs/strings.xml | 2 +- app/src/main/res/values-cy/strings.xml | 2 +- app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-eo/strings.xml | 2 +- app/src/main/res/values-es/strings.xml | 2 +- app/src/main/res/values-eu/strings.xml | 2 +- app/src/main/res/values-fa/strings.xml | 2 +- app/src/main/res/values-fr/strings.xml | 2 +- app/src/main/res/values-ga/strings.xml | 2 +- app/src/main/res/values-gd/strings.xml | 2 +- app/src/main/res/values-hi/strings.xml | 2 +- app/src/main/res/values-hu/strings.xml | 2 +- app/src/main/res/values-is/strings.xml | 2 +- app/src/main/res/values-it/strings.xml | 2 +- app/src/main/res/values-ja/strings.xml | 2 +- app/src/main/res/values-kab/strings.xml | 2 +- app/src/main/res/values-ko/strings.xml | 2 +- app/src/main/res/values-ml/strings.xml | 2 +- app/src/main/res/values-nl/strings.xml | 2 +- app/src/main/res/values-no-rNB/strings.xml | 2 +- app/src/main/res/values-oc/strings.xml | 2 +- app/src/main/res/values-pl/strings.xml | 2 +- app/src/main/res/values-pt-rBR/strings.xml | 2 +- app/src/main/res/values-ru/strings.xml | 2 +- app/src/main/res/values-sa/strings.xml | 2 +- app/src/main/res/values-sk/strings.xml | 2 +- app/src/main/res/values-sl/strings.xml | 2 +- app/src/main/res/values-sv/strings.xml | 2 +- app/src/main/res/values-ta/strings.xml | 2 +- app/src/main/res/values-th/strings.xml | 2 +- app/src/main/res/values-tr/strings.xml | 2 +- app/src/main/res/values-uk/strings.xml | 2 +- app/src/main/res/values-vi/strings.xml | 2 +- app/src/main/res/values-zh-rCN/strings.xml | 2 +- app/src/main/res/values-zh-rHK/strings.xml | 2 +- app/src/main/res/values-zh-rMO/strings.xml | 2 +- app/src/main/res/values-zh-rSG/strings.xml | 2 +- app/src/main/res/values-zh-rTW/strings.xml | 2 +- app/src/main/res/values/dimens.xml | 1 + app/src/main/res/values/strings.xml | 14 +- .../tusky/ComposeActivityTest.kt | 3 +- 85 files changed, 2032 insertions(+), 381 deletions(-) create mode 100644 app/schemas/com.keylesspalace.tusky.db.AppDatabase/25.json rename app/src/main/java/com/keylesspalace/tusky/{adapter => components/compose}/ComposeAutoCompleteAdapter.java (99%) create mode 100644 app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftHelper.kt create mode 100644 app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftMediaAdapter.kt create mode 100644 app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsActivity.kt create mode 100644 app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsAdapter.kt create mode 100644 app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsViewModel.kt create mode 100644 app/src/main/java/com/keylesspalace/tusky/db/DraftDao.kt create mode 100644 app/src/main/java/com/keylesspalace/tusky/db/DraftEntity.kt create mode 100644 app/src/main/java/com/keylesspalace/tusky/util/BindingViewHolder.kt create mode 100644 app/src/main/res/drawable/ic_alert_circle.xml create mode 100644 app/src/main/res/layout/activity_drafts.xml create mode 100644 app/src/main/res/layout/item_draft.xml create mode 100644 app/src/main/res/menu/drafts.xml diff --git a/app/build.gradle b/app/build.gradle index a7f566ac..fa6fc401 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -67,6 +67,9 @@ android { androidExtensions { experimental = true } + buildFeatures { + viewBinding true + } testOptions { unitTests { returnDefaultValues = true diff --git a/app/schemas/com.keylesspalace.tusky.db.AppDatabase/25.json b/app/schemas/com.keylesspalace.tusky.db.AppDatabase/25.json new file mode 100644 index 00000000..01a491b4 --- /dev/null +++ b/app/schemas/com.keylesspalace.tusky.db.AppDatabase/25.json @@ -0,0 +1,821 @@ +{ + "formatVersion": 1, + "database": { + "version": 25, + "identityHash": "e2cb844862443c2c5cc884c11f120d43", + "entities": [ + { + "tableName": "TootEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `text` TEXT, `urls` TEXT, `descriptions` TEXT, `contentWarning` TEXT, `inReplyToId` TEXT, `inReplyToText` TEXT, `inReplyToUsername` TEXT, `visibility` INTEGER, `poll` TEXT)", + "fields": [ + { + "fieldPath": "uid", + "columnName": "uid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "urls", + "columnName": "urls", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "descriptions", + "columnName": "descriptions", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "contentWarning", + "columnName": "contentWarning", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inReplyToId", + "columnName": "inReplyToId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inReplyToText", + "columnName": "inReplyToText", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inReplyToUsername", + "columnName": "inReplyToUsername", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "visibility", + "columnName": "visibility", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "poll", + "columnName": "poll", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "uid" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "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, `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": "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" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_AccountEntity_domain_accountId` ON `${TABLE_NAME}` (`domain`, `accountId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "InstanceEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`instance` TEXT NOT NULL, `emojiList` TEXT, `maximumTootCharacters` INTEGER, `maxPollOptions` INTEGER, `maxPollOptionLength` INTEGER, `version` TEXT, PRIMARY KEY(`instance`))", + "fields": [ + { + "fieldPath": "instance", + "columnName": "instance", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "emojiList", + "columnName": "emojiList", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "maximumTootCharacters", + "columnName": "maximumTootCharacters", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "maxPollOptions", + "columnName": "maxPollOptions", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "maxPollOptionLength", + "columnName": "maxPollOptionLength", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "instance" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimelineStatusEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serverId` TEXT NOT NULL, `url` TEXT, `timelineUserId` INTEGER NOT NULL, `authorServerId` TEXT, `inReplyToId` TEXT, `inReplyToAccountId` TEXT, `content` TEXT, `createdAt` INTEGER NOT NULL, `emojis` TEXT, `reblogsCount` INTEGER NOT NULL, `favouritesCount` INTEGER NOT NULL, `reblogged` INTEGER NOT NULL, `bookmarked` INTEGER NOT NULL, `favourited` INTEGER NOT NULL, `sensitive` INTEGER NOT NULL, `spoilerText` TEXT, `visibility` INTEGER, `attachments` TEXT, `mentions` TEXT, `application` TEXT, `reblogServerId` TEXT, `reblogAccountId` TEXT, `poll` TEXT, `muted` INTEGER, PRIMARY KEY(`serverId`, `timelineUserId`), FOREIGN KEY(`authorServerId`, `timelineUserId`) REFERENCES `TimelineAccountEntity`(`serverId`, `timelineUserId`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "serverId", + "columnName": "serverId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "timelineUserId", + "columnName": "timelineUserId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "authorServerId", + "columnName": "authorServerId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inReplyToId", + "columnName": "inReplyToId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inReplyToAccountId", + "columnName": "inReplyToAccountId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "emojis", + "columnName": "emojis", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "reblogsCount", + "columnName": "reblogsCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "favouritesCount", + "columnName": "favouritesCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "reblogged", + "columnName": "reblogged", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "bookmarked", + "columnName": "bookmarked", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "favourited", + "columnName": "favourited", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sensitive", + "columnName": "sensitive", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "spoilerText", + "columnName": "spoilerText", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "visibility", + "columnName": "visibility", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "mentions", + "columnName": "mentions", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "application", + "columnName": "application", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "reblogServerId", + "columnName": "reblogServerId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "reblogAccountId", + "columnName": "reblogAccountId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "poll", + "columnName": "poll", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "muted", + "columnName": "muted", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "serverId", + "timelineUserId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_TimelineStatusEntity_authorServerId_timelineUserId", + "unique": false, + "columnNames": [ + "authorServerId", + "timelineUserId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_TimelineStatusEntity_authorServerId_timelineUserId` ON `${TABLE_NAME}` (`authorServerId`, `timelineUserId`)" + } + ], + "foreignKeys": [ + { + "table": "TimelineAccountEntity", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "authorServerId", + "timelineUserId" + ], + "referencedColumns": [ + "serverId", + "timelineUserId" + ] + } + ] + }, + { + "tableName": "TimelineAccountEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serverId` TEXT NOT NULL, `timelineUserId` INTEGER NOT NULL, `localUsername` TEXT NOT NULL, `username` TEXT NOT NULL, `displayName` TEXT NOT NULL, `url` TEXT NOT NULL, `avatar` TEXT NOT NULL, `emojis` TEXT NOT NULL, `bot` INTEGER NOT NULL, PRIMARY KEY(`serverId`, `timelineUserId`))", + "fields": [ + { + "fieldPath": "serverId", + "columnName": "serverId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timelineUserId", + "columnName": "timelineUserId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "localUsername", + "columnName": "localUsername", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "username", + "columnName": "username", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "displayName", + "columnName": "displayName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatar", + "columnName": "avatar", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "emojis", + "columnName": "emojis", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "bot", + "columnName": "bot", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "serverId", + "timelineUserId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ConversationEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`accountId` INTEGER NOT NULL, `id` TEXT NOT NULL, `accounts` TEXT NOT NULL, `unread` INTEGER NOT NULL, `s_id` TEXT NOT NULL, `s_url` TEXT, `s_inReplyToId` TEXT, `s_inReplyToAccountId` TEXT, `s_account` TEXT NOT NULL, `s_content` TEXT NOT NULL, `s_createdAt` INTEGER NOT NULL, `s_emojis` TEXT NOT NULL, `s_favouritesCount` INTEGER NOT NULL, `s_favourited` INTEGER NOT NULL, `s_bookmarked` INTEGER NOT NULL, `s_sensitive` INTEGER NOT NULL, `s_spoilerText` TEXT NOT NULL, `s_attachments` TEXT NOT NULL, `s_mentions` TEXT NOT NULL, `s_showingHiddenContent` INTEGER NOT NULL, `s_expanded` INTEGER NOT NULL, `s_collapsible` INTEGER NOT NULL, `s_collapsed` INTEGER NOT NULL, `s_poll` TEXT, PRIMARY KEY(`id`, `accountId`))", + "fields": [ + { + "fieldPath": "accountId", + "columnName": "accountId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accounts", + "columnName": "accounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.id", + "columnName": "s_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.url", + "columnName": "s_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastStatus.inReplyToId", + "columnName": "s_inReplyToId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastStatus.inReplyToAccountId", + "columnName": "s_inReplyToAccountId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastStatus.account", + "columnName": "s_account", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.content", + "columnName": "s_content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.createdAt", + "columnName": "s_createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.emojis", + "columnName": "s_emojis", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.favouritesCount", + "columnName": "s_favouritesCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.favourited", + "columnName": "s_favourited", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.bookmarked", + "columnName": "s_bookmarked", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.sensitive", + "columnName": "s_sensitive", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.spoilerText", + "columnName": "s_spoilerText", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.attachments", + "columnName": "s_attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.mentions", + "columnName": "s_mentions", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastStatus.showingHiddenContent", + "columnName": "s_showingHiddenContent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.expanded", + "columnName": "s_expanded", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.collapsible", + "columnName": "s_collapsible", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.collapsed", + "columnName": "s_collapsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStatus.poll", + "columnName": "s_poll", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id", + "accountId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e2cb844862443c2c5cc884c11f120d43')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 585ff833..770d45af 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -146,6 +146,7 @@ + + val showDraftWarning = preferences.getBoolean(sharedPrefsKey, true) + if (draftCount > 0 && showDraftWarning) { + AlertDialog.Builder(this) + .setMessage(R.string.new_drafts_warning) + .setNegativeButton("Don't show again") { _, _ -> + preferences.edit(commit = true) { + putBoolean(sharedPrefsKey, false) + } + } + .setPositiveButton(android.R.string.ok, null) + .show() + } + } + + } + override fun getActionButton(): FloatingActionButton? = composeButton override fun androidInjector() = androidInjector diff --git a/app/src/main/java/com/keylesspalace/tusky/SavedTootActivity.java b/app/src/main/java/com/keylesspalace/tusky/SavedTootActivity.java index 9a163989..8e2b5acb 100644 --- a/app/src/main/java/com/keylesspalace/tusky/SavedTootActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/SavedTootActivity.java @@ -89,7 +89,7 @@ public final class SavedTootActivity extends BaseActivity implements SavedTootAd setSupportActionBar(toolbar); ActionBar bar = getSupportActionBar(); if (bar != null) { - bar.setTitle(getString(R.string.title_saved_toot)); + bar.setTitle(getString(R.string.title_drafts)); bar.setDisplayHomeAsUpEnabled(true); bar.setDisplayShowHomeEnabled(true); } @@ -166,6 +166,7 @@ public final class SavedTootActivity extends BaseActivity implements SavedTootAd ComposeOptions composeOptions = new ComposeOptions( /*scheduledTootUid*/null, item.getUid(), + /*drafId*/null, item.getText(), jsonUrls, descriptions, @@ -177,6 +178,7 @@ public final class SavedTootActivity extends BaseActivity implements SavedTootAd item.getInReplyToUsername(), item.getInReplyToText(), /*mediaAttachments*/null, + /*draftAttachments*/null, /*scheduledAt*/null, /*sensitive*/null, /*poll*/null, 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 5306e57a..69a09088 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 @@ -30,7 +30,6 @@ import android.os.Build import android.os.Bundle import android.os.Parcelable import android.provider.MediaStore -import android.text.TextUtils import android.util.Log import android.view.KeyEvent import android.view.MenuItem @@ -57,13 +56,13 @@ import com.google.android.material.snackbar.Snackbar import com.keylesspalace.tusky.BaseActivity import com.keylesspalace.tusky.BuildConfig import com.keylesspalace.tusky.R -import com.keylesspalace.tusky.adapter.ComposeAutoCompleteAdapter import com.keylesspalace.tusky.adapter.EmojiAdapter import com.keylesspalace.tusky.adapter.OnEmojiSelectedListener 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.db.AccountEntity +import com.keylesspalace.tusky.db.DraftAttachment import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.di.ViewModelFactory import com.keylesspalace.tusky.entity.Attachment @@ -81,7 +80,6 @@ import java.io.File import java.io.IOException import java.util.* import javax.inject.Inject -import kotlin.collections.ArrayList import kotlin.math.max import kotlin.math.min @@ -104,10 +102,10 @@ class ComposeActivity : BaseActivity(), // this only exists when a status is trying to be sent, but uploads are still occurring private var finishingUploadDialog: ProgressDialog? = null private var photoUploadUri: Uri? = null + @VisibleForTesting var maximumTootCharacters = DEFAULT_CHARACTER_LIMIT - private var composeOptions: ComposeOptions? = null private val viewModel: ComposeViewModel by viewModels { viewModelFactory } private val maxUploadMediaNumber = 4 @@ -148,17 +146,17 @@ class ComposeActivity : BaseActivity(), /* If the composer is started up as a reply to another post, override the "starting" state * based on what the intent from the reply request passes. */ - if (intent != null) { - this.composeOptions = intent.getParcelableExtra(COMPOSE_OPTIONS_EXTRA) - viewModel.setup(composeOptions) - setupReplyViews(composeOptions?.replyingStatusAuthor) - val tootText = composeOptions?.tootText - if (!tootText.isNullOrEmpty()) { - composeEditField.setText(tootText) - } + + val composeOptions: ComposeOptions? = intent.getParcelableExtra(COMPOSE_OPTIONS_EXTRA) + + viewModel.setup(composeOptions) + setupReplyViews(composeOptions?.replyingStatusAuthor, composeOptions?.replyingStatusContent) + val tootText = composeOptions?.tootText + if (!tootText.isNullOrEmpty()) { + composeEditField.setText(tootText) } - if (!TextUtils.isEmpty(composeOptions?.scheduledAt)) { + if (!composeOptions?.scheduledAt.isNullOrEmpty()) { composeScheduleView.setDateTime(composeOptions?.scheduledAt) } @@ -169,38 +167,24 @@ class ComposeActivity : BaseActivity(), viewModel.setupComplete.value = true } - private fun applyShareIntent(intent: Intent?, savedInstanceState: Bundle?) { - if (intent != null && savedInstanceState == null) { + private fun applyShareIntent(intent: Intent, savedInstanceState: Bundle?) { + if (savedInstanceState == null) { /* Get incoming images being sent through a share action from another app. Only do this * when savedInstanceState is null, otherwise both the images from the intent and the * instance state will be re-queued. */ - val type = intent.type - if (type != null) { + intent.type?.also { type -> if (type.startsWith("image/") || type.startsWith("video/") || type.startsWith("audio/")) { - val uriList = ArrayList() - if (intent.action != null) { - when (intent.action) { - Intent.ACTION_SEND -> { - val uri = intent.getParcelableExtra(Intent.EXTRA_STREAM) - if (uri != null) { - uriList.add(uri) - } - } - Intent.ACTION_SEND_MULTIPLE -> { - val list = intent.getParcelableArrayListExtra( - Intent.EXTRA_STREAM) - if (list != null) { - for (uri in list) { - if (uri != null) { - uriList.add(uri) - } - } - } + when (intent.action) { + Intent.ACTION_SEND -> { + intent.getParcelableExtra(Intent.EXTRA_STREAM)?.let { uri -> + pickMedia(uri) + } + } + Intent.ACTION_SEND_MULTIPLE -> { + intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM)?.forEach { uri -> + pickMedia(uri) } } - } - for (uri in uriList) { - pickMedia(uri) } } else if (type == "text/plain" && intent.action == Intent.ACTION_SEND) { @@ -224,7 +208,7 @@ class ComposeActivity : BaseActivity(), } } - private fun setupReplyViews(replyingStatusAuthor: String?) { + private fun setupReplyViews(replyingStatusAuthor: String?, replyingStatusContent: String?) { if (replyingStatusAuthor != null) { composeReplyView.show() composeReplyView.text = getString(R.string.replying_to, replyingStatusAuthor) @@ -248,7 +232,7 @@ class ComposeActivity : BaseActivity(), } } } - composeOptions?.replyingStatusContent?.let { composeReplyContentView.text = it } + replyingStatusContent?.let { composeReplyContentView.text = it } } private fun setupContentWarningField(startingContentWarning: String?) { @@ -651,7 +635,6 @@ class ComposeActivity : BaseActivity(), } } - private fun removePoll() { viewModel.poll.value = null pollPreview.hide() @@ -835,22 +818,22 @@ class ComposeActivity : BaseActivity(), override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { super.onActivityResult(requestCode, resultCode, intent) if (resultCode == Activity.RESULT_OK && requestCode == MEDIA_PICK_RESULT && intent != null) { - if(intent.data != null){ + if (intent.data != null) { // Single media, upload it and done. pickMedia(intent.data!!) - }else if(intent.clipData != null){ + } else if (intent.clipData != null) { val clipData = intent.clipData!! val count = clipData.itemCount - if(mediaCount + count > maxUploadMediaNumber){ + if (mediaCount + count > maxUploadMediaNumber) { // check if exist media + upcoming media > 4, then prob error message. Toast.makeText(this, getString(R.string.error_upload_max_media_reached, maxUploadMediaNumber), Toast.LENGTH_SHORT).show() - }else{ + } else { // if not grater then 4, upload all multiple media. for (i in 0 until count) { - val imageUri = clipData.getItemAt(i).getUri() - pickMedia(imageUri) - } + val imageUri = clipData.getItemAt(i).getUri() + pickMedia(imageUri) } + } } } else if (resultCode == Activity.RESULT_OK && requestCode == MEDIA_TAKE_PHOTO_RESULT) { pickMedia(photoUploadUri!!) @@ -1018,8 +1001,9 @@ class ComposeActivity : BaseActivity(), @Parcelize data class ComposeOptions( // Let's keep fields var until all consumers are Kotlin - var scheduledTootUid: String? = null, + var scheduledTootId: String? = null, var savedTootUid: Int? = null, + var draftId: Int? = null, var tootText: String? = null, var mediaUrls: List? = null, var mediaDescriptions: List? = null, @@ -1031,6 +1015,7 @@ class ComposeActivity : BaseActivity(), var replyingStatusAuthor: String? = null, var replyingStatusContent: String? = null, var mediaAttachments: List? = null, + var draftAttachments: List? = null, var scheduledAt: String? = null, var sensitive: Boolean? = null, var poll: NewPoll? = null, @@ -1057,7 +1042,6 @@ class ComposeActivity : BaseActivity(), } } - @JvmStatic fun canHandleMimeType(mimeType: String?): Boolean { return mimeType != null && (mimeType.startsWith("image/") || mimeType.startsWith("video/") || mimeType.startsWith("audio/") || mimeType == "text/plain") } diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/ComposeAutoCompleteAdapter.java b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeAutoCompleteAdapter.java similarity index 99% rename from app/src/main/java/com/keylesspalace/tusky/adapter/ComposeAutoCompleteAdapter.java rename to app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeAutoCompleteAdapter.java index ebc292ab..df9ae8a8 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/ComposeAutoCompleteAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeAutoCompleteAdapter.java @@ -13,7 +13,7 @@ * You should have received a copy of the GNU General Public License along with Tusky; if not, * see . */ -package com.keylesspalace.tusky.adapter; +package com.keylesspalace.tusky.components.compose; import android.content.Context; import android.preference.PreferenceManager; 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 2c015899..a1a5e7e6 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,8 +21,8 @@ import androidx.core.net.toUri import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Observer -import com.keylesspalace.tusky.adapter.ComposeAutoCompleteAdapter import com.keylesspalace.tusky.components.compose.ComposeActivity.QueuedMedia +import com.keylesspalace.tusky.components.drafts.DraftHelper import com.keylesspalace.tusky.components.search.SearchType import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.db.AppDatabase @@ -39,18 +39,12 @@ import io.reactivex.rxkotlin.Singles import java.util.* import javax.inject.Inject -/** - * Throw when trying to add an image when video is already present or the other way around - */ -class VideoOrImageException : Exception() - - -class ComposeViewModel -@Inject constructor( +class ComposeViewModel @Inject constructor( private val api: MastodonApi, private val accountManager: AccountManager, private val mediaUploader: MediaUploader, private val serviceClient: ServiceClient, + private val draftHelper: DraftHelper, private val saveTootHelper: SaveTootHelper, private val db: AppDatabase ) : RxAwareViewModel() { @@ -59,7 +53,8 @@ class ComposeViewModel private var replyingStatusContent: String? = null internal var startingText: String? = null private var savedTootUid: Int = 0 - private var scheduledTootUid: String? = null + private var draftId: Int = 0 + private var scheduledTootId: String? = null private var startingContentWarning: String = "" private var inReplyToId: String? = null private var startingVisibility: Status.Visibility = Status.Visibility.UNKNOWN @@ -81,10 +76,6 @@ class ComposeViewModel val markMediaAsSensitive = mutableLiveData(accountManager.activeAccount?.defaultMediaSensitivity ?: false) - fun toggleMarkSensitive() { - this.markMediaAsSensitive.value = !this.markMediaAsSensitive.value!! - } - val statusVisibility = mutableLiveData(Status.Visibility.UNKNOWN) val showContentWarning = mutableLiveData(false) val setupComplete = mutableLiveData(false) @@ -96,7 +87,7 @@ class ComposeViewModel private val mediaToDisposable = mutableMapOf() - private val isEditingScheduledToot get() = !scheduledTootUid.isNullOrEmpty() + private val isEditingScheduledToot get() = !scheduledTootId.isNullOrEmpty() init { @@ -116,7 +107,7 @@ class ComposeViewModel .onErrorResumeNext( db.instanceDao().loadMetadataForInstance(accountManager.activeAccount?.domain!!) ) - .subscribe ({ instanceEntity -> + .subscribe({ instanceEntity -> emoji.postValue(instanceEntity.emojiList) instance.postValue(instanceEntity) }, { throwable -> @@ -126,7 +117,7 @@ class ComposeViewModel .autoDispose() } - fun pickMedia(uri: Uri): LiveData> { + 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>() @@ -138,7 +129,7 @@ class ComposeViewModel && mediaItems[0].type == QueuedMedia.Type.IMAGE) { throw VideoOrImageException() } else { - addMediaToQueue(type, uri, size) + addMediaToQueue(type, uri, size, description) } } .subscribe({ queuedMedia -> @@ -150,12 +141,23 @@ class ComposeViewModel return liveData } - private fun addMediaToQueue(type: QueuedMedia.Type, uri: Uri, mediaSize: Long): QueuedMedia { - val mediaItem = QueuedMedia(System.currentTimeMillis(), uri, type, mediaSize) + private 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.value = media.value!! + mediaItem mediaToDisposable[mediaItem.localId] = mediaUploader .uploadMedia(mediaItem) - .subscribe ({ event -> + .subscribe({ event -> val item = media.value?.find { it.localId == mediaItem.localId } ?: return@subscribe val newMediaItem = when (event) { @@ -190,6 +192,10 @@ class ComposeViewModel media.value = media.value!!.withoutFirstWhich { it.localId == item.localId } } + fun toggleMarkSensitive() { + this.markMediaAsSensitive.value = this.markMediaAsSensitive.value != true + } + fun didChange(content: String?, contentWarning: String?): Boolean { val textChanged = !(content.isNullOrEmpty() @@ -210,29 +216,37 @@ class ComposeViewModel } fun deleteDraft() { - saveTootHelper.deleteDraft(this.savedTootUid) + if (savedTootUid != 0) { + saveTootHelper.deleteDraft(savedTootUid) + } + if (draftId != 0) { + draftHelper.deleteDraftAndAttachments(draftId) + .subscribe() + } } fun saveDraft(content: String, contentWarning: String) { - val mediaUris = mutableListOf() - val mediaDescriptions = mutableListOf() - for (item in media.value!!) { + + val mediaUris: MutableList = mutableListOf() + val mediaDescriptions: MutableList = mutableListOf() + media.value?.forEach { item -> mediaUris.add(item.uri.toString()) mediaDescriptions.add(item.description) } - saveTootHelper.saveToot( - content, - contentWarning, - null, - mediaUris, - mediaDescriptions, - savedTootUid, - inReplyToId, - replyingStatusContent, - replyingStatusAuthor, - statusVisibility.value!!, - poll.value - ) + + 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 + ).subscribe() } /** @@ -246,7 +260,7 @@ class ComposeViewModel ): LiveData { val deletionObservable = if (isEditingScheduledToot) { - api.deleteScheduledStatus(scheduledTootUid.toString()).toObservable().map { Unit } + api.deleteScheduledStatus(scheduledTootId.toString()).toObservable().map { } } else { just(Unit) }.toLiveData() @@ -257,28 +271,30 @@ class ComposeViewModel val mediaIds = ArrayList() val mediaUris = ArrayList() val mediaDescriptions = ArrayList() + val mediaTypes = ArrayList() for (item in media.value!!) { mediaIds.add(item.id!!) mediaUris.add(item.uri) mediaDescriptions.add(item.description ?: "") + mediaTypes.add(item.type) } val tootToSend = TootToSend( - content, - spoilerText, - statusVisibility.value!!.serverString(), - mediaUris.isNotEmpty() && (markMediaAsSensitive.value!! || showContentWarning.value!!), - mediaIds, - mediaUris.map { it.toString() }, - mediaDescriptions, + text = content, + warningText = spoilerText, + visibility = statusVisibility.value!!.serverString(), + sensitive = mediaUris.isNotEmpty() && (markMediaAsSensitive.value!! || showContentWarning.value!!), + mediaIds = mediaIds, + mediaUris = mediaUris.map { it.toString() }, + mediaDescriptions = mediaDescriptions, scheduledAt = scheduledAt.value, inReplyToId = inReplyToId, poll = poll.value, replyingStatusContent = null, replyingStatusAuthorUsername = null, - savedJsonUrls = null, accountId = accountManager.activeAccount!!.id, - savedTootUid = 0, + savedTootUid = savedTootUid, + draftId = draftId, idempotencyKey = randomAlphanumericString(16), retries = 0 ) @@ -286,9 +302,7 @@ class ComposeViewModel serviceClient.sendToot(tootToSend) } - return combineLiveData(deletionObservable, sendObservable) { _, _ -> Unit } - - + return combineLiveData(deletionObservable, sendObservable) { _, _ -> } } fun updateDescription(localId: Long, description: String): LiveData { @@ -319,7 +333,6 @@ class ComposeViewModel return completedCaptioningLiveData } - fun searchAutocompleteSuggestions(token: String): List { when (token[0]) { '@' -> { @@ -370,14 +383,12 @@ class ComposeViewModel } } - override fun onCleared() { - for (uploadDisposable in mediaToDisposable.values) { - uploadDisposable.dispose() - } - super.onCleared() - } - fun setup(composeOptions: ComposeActivity.ComposeOptions?) { + + if (setupComplete.value == true) { + return + } + val preferredVisibility = accountManager.activeAccount!!.defaultPostPrivacy val replyVisibility = composeOptions?.replyVisibility ?: Status.Visibility.UNKNOWN @@ -385,6 +396,7 @@ class ComposeViewModel preferredVisibility.num.coerceAtLeast(replyVisibility.num)) inReplyToId = composeOptions?.inReplyToId + modifiedInitialState = composeOptions?.modifiedInitialState == true val contentWarning = composeOptions?.contentWarning @@ -396,10 +408,11 @@ class ComposeViewModel } // recreate media list - // when coming from SavedTootActivity val loadedDraftMediaUris = composeOptions?.mediaUrls val loadedDraftMediaDescriptions: List? = composeOptions?.mediaDescriptions + val draftAttachments = composeOptions?.draftAttachments if (loadedDraftMediaUris != null && loadedDraftMediaDescriptions != null) { + // when coming from SavedTootActivity loadedDraftMediaUris.zip(loadedDraftMediaDescriptions) .forEach { (uri, description) -> pickMedia(uri.toUri()).observeForever { errorOrItem -> @@ -408,23 +421,24 @@ class ComposeViewModel } } } + } else if (draftAttachments != null) { + // when coming from DraftActivity + draftAttachments.forEach { attachment -> pickMedia(attachment.uri, attachment.description) } } else composeOptions?.mediaAttachments?.forEach { a -> - // when coming from redraft + // when coming from redraft or ScheduledTootActivity val mediaType = when (a.type) { Attachment.Type.VIDEO, Attachment.Type.GIFV -> QueuedMedia.Type.VIDEO Attachment.Type.UNKNOWN, Attachment.Type.IMAGE -> QueuedMedia.Type.IMAGE Attachment.Type.AUDIO -> QueuedMedia.Type.AUDIO - else -> QueuedMedia.Type.IMAGE } addUploadedMedia(a.id, mediaType, a.url.toUri(), a.description) } - savedTootUid = composeOptions?.savedTootUid ?: 0 - scheduledTootUid = composeOptions?.scheduledTootUid + draftId = composeOptions?.draftId ?: 0 + scheduledTootId = composeOptions?.scheduledTootId startingText = composeOptions?.tootText - val tootVisibility = composeOptions?.visibility ?: Status.Visibility.UNKNOWN if (tootVisibility.num != Status.Visibility.UNKNOWN.num) { startingVisibility = tootVisibility @@ -441,7 +455,6 @@ class ComposeViewModel startingText = builder.toString() } - scheduledAt.value = composeOptions?.scheduledAt composeOptions?.sensitive?.let { markMediaAsSensitive.value = it } @@ -462,6 +475,13 @@ class ComposeViewModel scheduledAt.value = newScheduledAt } + override fun onCleared() { + for (uploadDisposable in mediaToDisposable.values) { + uploadDisposable.dispose() + } + super.onCleared() + } + private companion object { const val TAG = "ComposeViewModel" } @@ -479,4 +499,9 @@ data class ComposeInstanceParams( val pollMaxOptions: Int, val pollMaxLength: Int, val supportsScheduled: Boolean -) \ No newline at end of file +) + +/** + * Thrown when trying to add an image when video is already present or the other way around + */ +class VideoOrImageException : Exception() \ No newline at end of file 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 1fa03d54..8ff7dcf3 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 @@ -173,7 +173,13 @@ class MediaUploaderImpl( val body = MultipartBody.Part.createFormData("file", filename, fileBody) - val uploadDisposable = mastodonApi.uploadMedia(body) + val description = if (media.description != null) { + MultipartBody.Part.createFormData("description", media.description) + } else { + null + } + + val uploadDisposable = mastodonApi.uploadMedia(body, description) .subscribe({ attachment -> emitter.onNext(UploadEvent.FinishedEvent(attachment)) emitter.onComplete() 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 new file mode 100644 index 00000000..6f5f9005 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftHelper.kt @@ -0,0 +1,159 @@ +/* Copyright 2021 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.drafts + +import android.content.Context +import android.net.Uri +import android.util.Log +import android.webkit.MimeTypeMap +import androidx.core.content.FileProvider +import androidx.core.net.toUri +import com.keylesspalace.tusky.BuildConfig +import com.keylesspalace.tusky.db.AppDatabase +import com.keylesspalace.tusky.db.DraftAttachment +import com.keylesspalace.tusky.db.DraftEntity +import com.keylesspalace.tusky.entity.NewPoll +import com.keylesspalace.tusky.entity.Status +import com.keylesspalace.tusky.util.IOUtils +import io.reactivex.Completable +import io.reactivex.Single +import io.reactivex.schedulers.Schedulers +import java.io.File +import java.text.SimpleDateFormat +import java.util.* +import javax.inject.Inject + +class DraftHelper @Inject constructor( + val context: Context, + db: AppDatabase +) { + + private val draftDao = db.draftDao() + + fun saveDraft( + draftId: Int, + accountId: Long, + inReplyToId: String?, + content: String?, + contentWarning: String?, + sensitive: Boolean, + visibility: Status.Visibility, + mediaUris: List, + mediaDescriptions: List, + poll: NewPoll?, + failedToSend: Boolean + ): Completable { + return Single.fromCallable { + + val draftDirectory = context.getExternalFilesDir("Tusky") + + if (draftDirectory == null || !(draftDirectory.exists())) { + Log.e("DraftHelper", "Error obtaining directory to save media.") + throw Exception() + } + + val uris = mediaUris.map { uriString -> + uriString.toUri() + }.map { uri -> + if (uri.isNotInFolder(draftDirectory)) { + uri.copyToFolder(draftDirectory) + } else { + uri + } + } + + val types = uris.map { uri -> + val mimeType = context.contentResolver.getType(uri) + when (mimeType?.substring(0, mimeType.indexOf('/'))) { + "video" -> DraftAttachment.Type.VIDEO + "image" -> DraftAttachment.Type.IMAGE + "audio" -> DraftAttachment.Type.AUDIO + else -> throw IllegalStateException("unknown media type") + } + } + + val attachments: MutableList = mutableListOf() + for (i in mediaUris.indices) { + attachments.add( + DraftAttachment( + uriString = uris[i].toString(), + description = mediaDescriptions[i], + type = types[i] + ) + ) + } + + DraftEntity( + id = draftId, + accountId = accountId, + inReplyToId = inReplyToId, + content = content, + contentWarning = contentWarning, + sensitive = sensitive, + visibility = visibility, + attachments = attachments, + poll = poll, + failedToSend = failedToSend + ) + + }.flatMapCompletable { draft -> + draftDao.insertOrReplace(draft) + }.subscribeOn(Schedulers.io()) + } + + fun deleteDraftAndAttachments(draftId: Int): Completable { + return draftDao.find(draftId) + .flatMapCompletable { draft -> + deleteDraftAndAttachments(draft) + } + } + + fun deleteDraftAndAttachments(draft: DraftEntity): Completable { + return deleteAttachments(draft) + .andThen(draftDao.delete(draft.id)) + } + + fun deleteAttachments(draft: DraftEntity): Completable { + return Completable.fromCallable { + draft.attachments.forEach { attachment -> + if (context.contentResolver.delete(attachment.uri, null, null) == 0) { + Log.e("DraftHelper", "Did not delete file ${attachment.uriString}") + } + } + }.subscribeOn(Schedulers.io()) + } + + private fun Uri.isNotInFolder(folder: File): Boolean { + val filePath = path ?: return true + return File(filePath).parentFile == folder + } + + 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 filename = String.format("Tusky_Draft_Media_%s.%s", timeStamp, fileExtension) + val file = File(folder, filename) + IOUtils.copyToFile(contentResolver, this, file) + return FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileprovider", file) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftMediaAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftMediaAdapter.kt new file mode 100644 index 00000000..69403fdb --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftMediaAdapter.kt @@ -0,0 +1,81 @@ +/* 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.components.drafts + +import android.view.ViewGroup +import android.widget.ImageView +import androidx.appcompat.widget.AppCompatImageView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.keylesspalace.tusky.R +import com.keylesspalace.tusky.db.DraftAttachment + +class DraftMediaAdapter( + private val attachmentClick: () -> Unit +) : ListAdapter( + object: DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: DraftAttachment, newItem: DraftAttachment): Boolean { + return oldItem == newItem + } + + override fun areContentsTheSame(oldItem: DraftAttachment, newItem: DraftAttachment): Boolean { + return oldItem == newItem + } + + } +) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DraftMediaViewHolder { + return DraftMediaViewHolder(AppCompatImageView(parent.context)) + } + + override fun onBindViewHolder(holder: DraftMediaViewHolder, position: Int) { + getItem(position)?.let { attachment -> + if (attachment.type == DraftAttachment.Type.AUDIO) { + holder.imageView.setImageResource(R.drawable.ic_music_box_preview_24dp) + } else { + Glide.with(holder.itemView.context) + .load(attachment.uri) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .dontAnimate() + .into(holder.imageView) + } + } + } + + inner class DraftMediaViewHolder(val imageView: ImageView) + : RecyclerView.ViewHolder(imageView) { + init { + val thumbnailViewSize = + imageView.context.resources.getDimensionPixelSize(R.dimen.compose_media_preview_size) + val layoutParams = ConstraintLayout.LayoutParams(thumbnailViewSize, thumbnailViewSize) + val margin = itemView.context.resources + .getDimensionPixelSize(R.dimen.compose_media_preview_margin) + val marginBottom = itemView.context.resources + .getDimensionPixelSize(R.dimen.compose_media_preview_margin_bottom) + layoutParams.setMargins(margin, 0, margin, marginBottom) + imageView.layoutParams = layoutParams + imageView.scaleType = ImageView.ScaleType.CENTER_CROP + imageView.setOnClickListener { + attachmentClick() + } + } + } +} \ No newline at end of file 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 new file mode 100644 index 00000000..ddf8a838 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsActivity.kt @@ -0,0 +1,197 @@ +/* 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.components.drafts + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.view.Menu +import android.view.MenuItem +import android.widget.LinearLayout +import android.widget.Toast +import androidx.activity.viewModels +import androidx.lifecycle.Lifecycle +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.snackbar.Snackbar +import com.keylesspalace.tusky.BaseActivity +import com.keylesspalace.tusky.R +import com.keylesspalace.tusky.SavedTootActivity +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.hide +import com.keylesspalace.tusky.util.show +import com.uber.autodispose.android.lifecycle.autoDispose +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers +import retrofit2.HttpException +import javax.inject.Inject + +class DraftsActivity : BaseActivity(), DraftActionListener { + + @Inject + lateinit var viewModelFactory: ViewModelFactory + + private val viewModel: DraftsViewModel by viewModels { viewModelFactory } + + private lateinit var binding: ActivityDraftsBinding + private lateinit var bottomSheet: BottomSheetBehavior + + private var oldDraftsButton: MenuItem? = null + + override fun onCreate(savedInstanceState: Bundle?) { + + super.onCreate(savedInstanceState) + + binding = ActivityDraftsBinding.inflate(layoutInflater) + setContentView(binding.root) + + setSupportActionBar(binding.includedToolbar.toolbar) + supportActionBar?.apply { + title = getString(R.string.title_drafts) + setDisplayHomeAsUpEnabled(true) + setDisplayShowHomeEnabled(true) + } + + binding.draftsErrorMessageView.setup(R.drawable.elephant_friend_empty, R.string.no_saved_status) + + val adapter = DraftsAdapter(this) + + binding.draftsRecyclerView.adapter = adapter + binding.draftsRecyclerView.layoutManager = LinearLayoutManager(this) + binding.draftsRecyclerView.addItemDecoration(DividerItemDecoration(this, DividerItemDecoration.VERTICAL)) + + bottomSheet = BottomSheetBehavior.from(binding.bottomSheet.root) + + viewModel.drafts.observe(this) { draftList -> + if (draftList.isEmpty()) { + binding.draftsRecyclerView.hide() + binding.draftsErrorMessageView.show() + } else { + binding.draftsRecyclerView.show() + binding.draftsErrorMessageView.hide() + adapter.submitList(draftList) + } + } + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.drafts, menu) + oldDraftsButton = menu.findItem(R.id.action_old_drafts) + viewModel.showOldDraftsButton() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .autoDispose(this, Lifecycle.Event.ON_DESTROY) + .subscribe { showOldDraftsButton -> + oldDraftsButton?.isVisible = showOldDraftsButton + } + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + android.R.id.home -> { + onBackPressed() + return true + } + R.id.action_old_drafts -> { + val intent = Intent(this, SavedTootActivity::class.java) + startActivityWithSlideInAnimation(intent) + return true + } + } + return super.onOptionsItemSelected(item) + } + + override fun onOpenDraft(draft: DraftEntity) { + + if (draft.inReplyToId != null) { + bottomSheet.state = BottomSheetBehavior.STATE_COLLAPSED + viewModel.getToot(draft.inReplyToId) + .observeOn(AndroidSchedulers.mainThread()) + .autoDispose(this) + .subscribe({ status -> + val composeOptions = ComposeActivity.ComposeOptions( + draftId = draft.id, + tootText = draft.content, + contentWarning = draft.contentWarning, + inReplyToId = draft.inReplyToId, + replyingStatusContent = status.content.toString(), + replyingStatusAuthor = status.account.localUsername, + draftAttachments = draft.attachments, + poll = draft.poll, + sensitive = draft.sensitive, + visibility = draft.visibility + ) + + bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN + + startActivity(ComposeActivity.startIntent(this, composeOptions)) + + }, { throwable -> + + bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN + + Log.w(TAG, "failed loading reply information", throwable) + + if (throwable is HttpException && throwable.code() == 404) { + // the original status to which a reply was drafted has been deleted + // let's open the ComposeActivity without reply information + Toast.makeText(this, getString(R.string.drafts_toot_reply_removed), Toast.LENGTH_LONG).show() + openDraftWithoutReply(draft) + } else { + Snackbar.make(binding.root, getString(R.string.drafts_failed_loading_reply), Snackbar.LENGTH_SHORT) + .show() + } + }) + } else { + openDraftWithoutReply(draft) + } + } + + private fun openDraftWithoutReply(draft: DraftEntity) { + val composeOptions = ComposeActivity.ComposeOptions( + draftId = draft.id, + tootText = draft.content, + contentWarning = draft.contentWarning, + draftAttachments = draft.attachments, + poll = draft.poll, + sensitive = draft.sensitive, + visibility = draft.visibility + ) + + startActivity(ComposeActivity.startIntent(this, composeOptions)) + } + + override fun onDeleteDraft(draft: DraftEntity) { + viewModel.deleteDraft(draft) + Snackbar.make(binding.root, getString(R.string.draft_deleted), Snackbar.LENGTH_LONG) + .setAction(R.string.action_undo) { + viewModel.restoreDraft(draft) + } + .show() + } + + companion object { + const val TAG = "DraftsActivity" + + fun newIntent(context: Context) = Intent(context, DraftsActivity::class.java) + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsAdapter.kt new file mode 100644 index 00000000..5dfbceac --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsAdapter.kt @@ -0,0 +1,92 @@ +/* Copyright 2021 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.drafts + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.paging.PagedListAdapter +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.keylesspalace.tusky.databinding.ItemDraftBinding +import com.keylesspalace.tusky.db.DraftEntity +import com.keylesspalace.tusky.util.BindingViewHolder +import com.keylesspalace.tusky.util.hide +import com.keylesspalace.tusky.util.show +import com.keylesspalace.tusky.util.visible + +interface DraftActionListener { + fun onOpenDraft(draft: DraftEntity) + fun onDeleteDraft(draft: DraftEntity) +} + +class DraftsAdapter( + private val listener: DraftActionListener +) : PagedListAdapter>( + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: DraftEntity, newItem: DraftEntity): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: DraftEntity, newItem: DraftEntity): Boolean { + return oldItem == newItem + } + } +) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingViewHolder { + + val binding = ItemDraftBinding.inflate(LayoutInflater.from(parent.context), parent, false) + + val viewHolder = BindingViewHolder(binding) + + binding.draftMediaPreview.layoutManager = LinearLayoutManager(binding.root.context, RecyclerView.HORIZONTAL, false) + binding.draftMediaPreview.adapter = DraftMediaAdapter { + getItem(viewHolder.adapterPosition)?.let { draft -> + listener.onOpenDraft(draft) + } + } + + return viewHolder + } + + override fun onBindViewHolder(holder: BindingViewHolder, position: Int) { + getItem(position)?.let { draft -> + holder.binding.root.setOnClickListener { + listener.onOpenDraft(draft) + } + holder.binding.deleteButton.setOnClickListener { + listener.onDeleteDraft(draft) + } + holder.binding.draftSendingInfo.visible(draft.failedToSend) + + holder.binding.contentWarning.visible(!draft.contentWarning.isNullOrEmpty()) + holder.binding.contentWarning.text = draft.contentWarning + holder.binding.content.text = draft.content + + holder.binding.draftMediaPreview.visible(draft.attachments.isNotEmpty()) + (holder.binding.draftMediaPreview.adapter as DraftMediaAdapter).submitList(draft.attachments) + + if (draft.poll != null) { + holder.binding.draftPoll.show() + holder.binding.draftPoll.setPoll(draft.poll) + } else { + holder.binding.draftPoll.hide() + } + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsViewModel.kt new file mode 100644 index 00000000..9eca963a --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsViewModel.kt @@ -0,0 +1,69 @@ +/* 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.components.drafts + +import androidx.lifecycle.ViewModel +import androidx.paging.toLiveData +import com.keylesspalace.tusky.db.AccountManager +import com.keylesspalace.tusky.db.AppDatabase +import com.keylesspalace.tusky.db.DraftEntity +import com.keylesspalace.tusky.entity.Status +import com.keylesspalace.tusky.network.MastodonApi +import io.reactivex.Observable +import io.reactivex.Single +import javax.inject.Inject + +class DraftsViewModel @Inject constructor( + val database: AppDatabase, + val accountManager: AccountManager, + val api: MastodonApi, + val draftHelper: DraftHelper +) : ViewModel() { + + val drafts = database.draftDao().loadDrafts(accountManager.activeAccount?.id!!).toLiveData(pageSize = 20) + + private val deletedDrafts: MutableList = mutableListOf() + + fun showOldDraftsButton(): Observable { + return database.tootDao().savedTootCount() + .map { count -> count > 0 } + } + + fun deleteDraft(draft: DraftEntity) { + // this does not immediately delete media files to avoid unnecessary file operations + // in case the user decides to restore the draft + database.draftDao().delete(draft.id) + .subscribe() + deletedDrafts.add(draft) + } + + fun restoreDraft(draft: DraftEntity) { + database.draftDao().insertOrReplace(draft) + .subscribe() + deletedDrafts.remove(draft) + } + + fun getToot(tootId: String): Single { + return api.statusSingle(tootId) + } + + override fun onCleared() { + deletedDrafts.forEach { + draftHelper.deleteAttachments(it).subscribe() + } + } + +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootActivity.kt index 18b04df5..f0944a34 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootActivity.kt @@ -120,7 +120,7 @@ class ScheduledTootActivity : BaseActivity(), ScheduledTootActionListener, Injec override fun edit(item: ScheduledStatus) { val intent = ComposeActivity.startIntent(this, ComposeActivity.ComposeOptions( - scheduledTootUid = item.id, + scheduledTootId = item.id, tootText = item.params.text, contentWarning = item.params.spoilerText, mediaAttachments = item.mediaAttachments, 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 5c5b7cb6..d35fd389 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java +++ b/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java @@ -28,9 +28,9 @@ import com.keylesspalace.tusky.components.conversation.ConversationEntity; * DB version & declare DAO */ -@Database(entities = {TootEntity.class, AccountEntity.class, InstanceEntity.class, TimelineStatusEntity.class, +@Database(entities = { TootEntity.class, DraftEntity.class, AccountEntity.class, InstanceEntity.class, TimelineStatusEntity.class, TimelineAccountEntity.class, ConversationEntity.class - }, version = 24) + }, version = 25) public abstract class AppDatabase extends RoomDatabase { public abstract TootDao tootDao(); @@ -38,6 +38,7 @@ public abstract class AppDatabase extends RoomDatabase { public abstract InstanceDao instanceDao(); public abstract ConversationsDao conversationDao(); public abstract TimelineDao timelineDao(); + public abstract DraftDao draftDao(); public static final Migration MIGRATION_2_3 = new Migration(2, 3) { @Override @@ -46,7 +47,6 @@ public abstract class AppDatabase extends RoomDatabase { database.execSQL("INSERT INTO TootEntity2 SELECT * FROM TootEntity;"); database.execSQL("DROP TABLE TootEntity;"); database.execSQL("ALTER TABLE TootEntity2 RENAME TO TootEntity;"); - } }; @@ -347,4 +347,22 @@ public abstract class AppDatabase extends RoomDatabase { } }; + public static final Migration MIGRATION_24_25 = new Migration(24, 25) { + @Override + public void migrate(@NonNull SupportSQLiteDatabase database) { + database.execSQL( + "CREATE TABLE IF NOT EXISTS `DraftEntity` (" + + "`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)" + ); + } + }; } 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 3492deda..1b1f94f3 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/Converters.kt +++ b/app/src/main/java/com/keylesspalace/tusky/db/Converters.kt @@ -24,10 +24,7 @@ import com.google.gson.reflect.TypeToken import com.keylesspalace.tusky.TabData import com.keylesspalace.tusky.components.conversation.ConversationAccountEntity import com.keylesspalace.tusky.createTabDataFromId -import com.keylesspalace.tusky.entity.Attachment -import com.keylesspalace.tusky.entity.Emoji -import com.keylesspalace.tusky.entity.Poll -import com.keylesspalace.tusky.entity.Status +import com.keylesspalace.tusky.entity.* import com.keylesspalace.tusky.json.SpannedTypeAdapter import com.keylesspalace.tusky.util.trimTrailingWhitespace import java.net.URLDecoder @@ -151,4 +148,23 @@ class Converters { return gson.fromJson(pollJson, Poll::class.java) } -} \ No newline at end of file + @TypeConverter + fun newPollToJson(newPoll: NewPoll?): String? { + return gson.toJson(newPoll) + } + + @TypeConverter + fun jsonToNewPoll(newPollJson: String?): NewPoll? { + return gson.fromJson(newPollJson, NewPoll::class.java) + } + + @TypeConverter + fun draftAttachmentListToJson(draftAttachments: List?): String? { + return gson.toJson(draftAttachments) + } + + @TypeConverter + fun jsonToDraftAttachmentList(draftAttachmentListJson: String?): List? { + return gson.fromJson(draftAttachmentListJson, object : TypeToken>() {}.type) + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/db/DraftDao.kt b/app/src/main/java/com/keylesspalace/tusky/db/DraftDao.kt new file mode 100644 index 00000000..105fd7c5 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/db/DraftDao.kt @@ -0,0 +1,40 @@ +/* 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.db + +import androidx.paging.DataSource +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import io.reactivex.Completable +import io.reactivex.Single + +@Dao +interface DraftDao { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertOrReplace(draft: DraftEntity): Completable + + @Query("SELECT * FROM DraftEntity WHERE accountId = :accountId ORDER BY id ASC") + fun loadDrafts(accountId: Long): DataSource.Factory + + @Query("DELETE FROM DraftEntity WHERE id = :id") + fun delete(id: Int): Completable + + @Query("SELECT * FROM DraftEntity WHERE id = :id") + fun find(id: Int): Single +} diff --git a/app/src/main/java/com/keylesspalace/tusky/db/DraftEntity.kt b/app/src/main/java/com/keylesspalace/tusky/db/DraftEntity.kt new file mode 100644 index 00000000..be1eca58 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/db/DraftEntity.kt @@ -0,0 +1,55 @@ +/* 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.db + +import android.net.Uri +import android.os.Parcelable +import androidx.core.net.toUri +import androidx.room.Entity +import androidx.room.PrimaryKey +import androidx.room.TypeConverters +import com.keylesspalace.tusky.entity.NewPoll +import com.keylesspalace.tusky.entity.Status +import kotlinx.android.parcel.Parcelize + +@Entity +@TypeConverters(Converters::class) +data class DraftEntity( + @PrimaryKey(autoGenerate = true) val id: Int = 0, + val accountId: Long, + val inReplyToId: String?, + val content: String?, + val contentWarning: String?, + val sensitive: Boolean, + val visibility: Status.Visibility, + val attachments: List, + val poll: NewPoll?, + val failedToSend: Boolean +) + +@Parcelize +data class DraftAttachment( + val uriString: String, + val description: String?, + val type: Type +): Parcelable { + val uri: Uri + get() = uriString.toUri() + + enum class Type { + IMAGE, VIDEO, AUDIO; + } +} 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 da98cb4b..296111d3 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/TimelineStatusEntity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/db/TimelineStatusEntity.kt @@ -26,7 +26,7 @@ import com.keylesspalace.tusky.entity.Status // Avoiding rescanning status table when accounts table changes. Recommended by Room(c). indices = [Index("authorServerId", "timelineUserId")] ) -@TypeConverters(TootEntity.Converters::class) +@TypeConverters(Converters::class) data class TimelineStatusEntity( val serverId: String, // id never flips: we need it for sorting so it's a real id val url: String?, diff --git a/app/src/main/java/com/keylesspalace/tusky/db/TootDao.java b/app/src/main/java/com/keylesspalace/tusky/db/TootDao.java index c121e170..f46c2753 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/TootDao.java +++ b/app/src/main/java/com/keylesspalace/tusky/db/TootDao.java @@ -16,12 +16,12 @@ package com.keylesspalace.tusky.db; import androidx.room.Dao; -import androidx.room.Insert; -import androidx.room.OnConflictStrategy; import androidx.room.Query; import java.util.List; +import io.reactivex.Observable; + /** * Created by cto3543 on 28/06/2017. * @@ -30,8 +30,6 @@ import java.util.List; @Dao public interface TootDao { - @Insert(onConflict = OnConflictStrategy.REPLACE) - void insertOrReplace(TootEntity users); @Query("SELECT * FROM TootEntity ORDER BY uid DESC") List loadAll(); @@ -41,4 +39,7 @@ public interface TootDao { @Query("SELECT * FROM TootEntity WHERE uid = :uid") TootEntity find(int uid); -} + + @Query("SELECT COUNT(*) FROM TootEntity") + Observable savedTootCount(); +} \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt index 0257c28f..2e82d640 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt @@ -18,6 +18,7 @@ package com.keylesspalace.tusky.di import com.keylesspalace.tusky.* import com.keylesspalace.tusky.components.announcements.AnnouncementsActivity import com.keylesspalace.tusky.components.compose.ComposeActivity +import com.keylesspalace.tusky.components.drafts.DraftsActivity import com.keylesspalace.tusky.components.instancemute.InstanceListActivity import com.keylesspalace.tusky.components.preference.PreferencesActivity import com.keylesspalace.tusky.components.report.ReportActivity @@ -107,4 +108,7 @@ abstract class ActivitiesModule { @ContributesAndroidInjector abstract fun contributesAnnouncementsActivity(): AnnouncementsActivity + + @ContributesAndroidInjector + abstract fun contributesDraftActivity(): DraftsActivity } 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 7e86bbac..1137b12b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt @@ -80,7 +80,7 @@ class AppModule { AppDatabase.MIGRATION_13_14, AppDatabase.MIGRATION_14_15, AppDatabase.MIGRATION_15_16, AppDatabase.MIGRATION_16_17, AppDatabase.MIGRATION_17_18, AppDatabase.MIGRATION_18_19, AppDatabase.MIGRATION_19_20, AppDatabase.MIGRATION_20_21, AppDatabase.MIGRATION_21_22, - AppDatabase.MIGRATION_22_23, AppDatabase.MIGRATION_23_24) + AppDatabase.MIGRATION_22_23, AppDatabase.MIGRATION_23_24, AppDatabase.MIGRATION_24_25) .build() } diff --git a/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt b/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt index c461012d..ce83deda 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt @@ -7,6 +7,7 @@ import androidx.lifecycle.ViewModelProvider import com.keylesspalace.tusky.components.announcements.AnnouncementsViewModel import com.keylesspalace.tusky.components.compose.ComposeViewModel import com.keylesspalace.tusky.components.conversation.ConversationsViewModel +import com.keylesspalace.tusky.components.drafts.DraftsViewModel import com.keylesspalace.tusky.components.report.ReportViewModel import com.keylesspalace.tusky.components.scheduled.ScheduledTootViewModel import com.keylesspalace.tusky.components.search.SearchViewModel @@ -91,5 +92,10 @@ abstract class ViewModelModule { @ViewModelKey(AnnouncementsViewModel::class) internal abstract fun announcementsViewModel(viewModel: AnnouncementsViewModel): ViewModel + @Binds + @IntoMap + @ViewModelKey(DraftsViewModel::class) + internal abstract fun draftsViewModel(viewModel: DraftsViewModel): ViewModel + //Add more ViewModels here } 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 7b79e1b5..58caec85 100644 --- a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt +++ b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt @@ -124,7 +124,8 @@ interface MastodonApi { @Multipart @POST("api/v1/media") fun uploadMedia( - @Part file: MultipartBody.Part + @Part file: MultipartBody.Part, + @Part description: MultipartBody.Part? = null ): Single @FormUrlEncoded @@ -147,6 +148,11 @@ interface MastodonApi { @Path("id") statusId: String ): Call + @GET("api/v1/statuses/{id}") + fun statusSingle( + @Path("id") statusId: String + ): Single + @GET("api/v1/statuses/{id}/context") fun statusContext( @Path("id") statusId: String 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 f8bf81b3..911f58c1 100644 --- a/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt +++ b/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt @@ -60,7 +60,6 @@ class SendStatusBroadcastReceiver : BroadcastReceiver() { val notificationManager = NotificationManagerCompat.from(context) - if (intent.action == NotificationHelper.REPLY_ACTION) { val message = getReplyMessage(intent) @@ -89,22 +88,23 @@ class SendStatusBroadcastReceiver : BroadcastReceiver() { val sendIntent = SendTootService.sendTootIntent( context, TootToSend( - text, - spoiler, - visibility.serverString(), - false, - emptyList(), - emptyList(), - emptyList(), - null, - citedStatusId, - null, - null, - null, - null, account.id, - 0, - randomAlphanumericString(16), - 0 + text = text, + warningText = spoiler, + visibility = visibility.serverString(), + sensitive = false, + mediaIds = emptyList(), + mediaUris = emptyList(), + mediaDescriptions = emptyList(), + scheduledAt = null, + inReplyToId = citedStatusId, + poll = null, + replyingStatusContent = null, + replyingStatusAuthorUsername = null, + accountId = account.id, + savedTootUid = -1, + draftId = -1, + idempotencyKey = randomAlphanumericString(16), + retries = 0 ) ) diff --git a/app/src/main/java/com/keylesspalace/tusky/service/SendTootService.kt b/app/src/main/java/com/keylesspalace/tusky/service/SendTootService.kt index 328265b5..b54a941b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/service/SendTootService.kt +++ b/app/src/main/java/com/keylesspalace/tusky/service/SendTootService.kt @@ -18,6 +18,7 @@ import com.keylesspalace.tusky.R import com.keylesspalace.tusky.appstore.EventHub import com.keylesspalace.tusky.appstore.StatusComposedEvent import com.keylesspalace.tusky.appstore.StatusScheduledEvent +import com.keylesspalace.tusky.components.drafts.DraftHelper import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.db.AppDatabase import com.keylesspalace.tusky.di.Injectable @@ -46,7 +47,8 @@ class SendTootService : Service(), Injectable { lateinit var eventHub: EventHub @Inject lateinit var database: AppDatabase - + @Inject + lateinit var draftHelper: DraftHelper @Inject lateinit var saveTootHelper: SaveTootHelper @@ -163,6 +165,10 @@ class SendTootService : Service(), Injectable { if (tootToSend.savedTootUid != 0) { saveTootHelper.deleteDraft(tootToSend.savedTootUid) } + if (tootToSend.draftId != 0) { + draftHelper.deleteDraftAndAttachments(tootToSend.draftId) + .subscribe() + } if (scheduled) { response.body()?.let(::StatusScheduledEvent)?.let(eventHub::dispatch) @@ -245,17 +251,19 @@ class SendTootService : Service(), Injectable { private fun saveTootToDrafts(toot: TootToSend) { - saveTootHelper.saveToot(toot.text, - toot.warningText, - toot.savedJsonUrls, - toot.mediaUris, - toot.mediaDescriptions, - toot.savedTootUid, - toot.inReplyToId, - toot.replyingStatusContent, - toot.replyingStatusAuthorUsername, - Status.Visibility.byString(toot.visibility), - toot.poll) + draftHelper.saveDraft( + draftId = toot.draftId, + accountId = toot.accountId, + inReplyToId = toot.inReplyToId, + content = toot.text, + contentWarning = toot.warningText, + sensitive = toot.sensitive, + visibility = Status.Visibility.byString(toot.visibility), + mediaUris = toot.mediaUris, + mediaDescriptions = toot.mediaDescriptions, + poll = toot.poll, + failedToSend = true + ).subscribe() } private fun cancelSendingIntent(tootId: Int): PendingIntent { @@ -323,9 +331,9 @@ data class TootToSend( val poll: NewPoll?, val replyingStatusContent: String?, val replyingStatusAuthorUsername: String?, - val savedJsonUrls: List?, val accountId: Long, val savedTootUid: Int, + val draftId: Int, val idempotencyKey: String, var retries: Int ) : Parcelable diff --git a/app/src/main/java/com/keylesspalace/tusky/util/BindingViewHolder.kt b/app/src/main/java/com/keylesspalace/tusky/util/BindingViewHolder.kt new file mode 100644 index 00000000..14aee81b --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/util/BindingViewHolder.kt @@ -0,0 +1,8 @@ +package com.keylesspalace.tusky.util + +import androidx.recyclerview.widget.RecyclerView +import androidx.viewbinding.ViewBinding + +class BindingViewHolder( + val binding: T +) : RecyclerView.ViewHolder(binding.root) diff --git a/app/src/main/java/com/keylesspalace/tusky/util/RxAwareViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/util/RxAwareViewModel.kt index 2ad4b825..c78b0f78 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/RxAwareViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/RxAwareViewModel.kt @@ -1,5 +1,6 @@ package com.keylesspalace.tusky.util +import androidx.annotation.CallSuper import androidx.lifecycle.ViewModel import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.Disposable @@ -9,6 +10,7 @@ open class RxAwareViewModel : ViewModel() { fun Disposable.autoDispose() = disposables.add(this) + @CallSuper override fun onCleared() { super.onCleared() disposables.clear() diff --git a/app/src/main/java/com/keylesspalace/tusky/util/SaveTootHelper.java b/app/src/main/java/com/keylesspalace/tusky/util/SaveTootHelper.java index 69009830..29693550 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/SaveTootHelper.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/SaveTootHelper.java @@ -1,33 +1,18 @@ package com.keylesspalace.tusky.util; -import android.annotation.SuppressLint; -import android.content.ContentResolver; import android.content.Context; import android.net.Uri; -import android.os.AsyncTask; -import android.text.TextUtils; import android.util.Log; -import android.webkit.MimeTypeMap; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.content.FileProvider; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; -import com.keylesspalace.tusky.BuildConfig; import com.keylesspalace.tusky.db.AppDatabase; import com.keylesspalace.tusky.db.TootDao; import com.keylesspalace.tusky.db.TootEntity; -import com.keylesspalace.tusky.entity.NewPoll; -import com.keylesspalace.tusky.entity.Status; -import java.io.File; -import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Locale; import javax.inject.Inject; @@ -45,61 +30,6 @@ public final class SaveTootHelper { this.context = context; } - @SuppressLint("StaticFieldLeak") - public boolean saveToot(@NonNull String content, - @NonNull String contentWarning, - @Nullable List savedJsonUrls, - @NonNull List mediaUris, - @NonNull List mediaDescriptions, - int savedTootUid, - @Nullable String inReplyToId, - @Nullable String replyingStatusContent, - @Nullable String replyingStatusAuthorUsername, - @NonNull Status.Visibility statusVisibility, - @Nullable NewPoll poll) { - - if (TextUtils.isEmpty(content) && mediaUris.isEmpty() && poll == null) { - return false; - } - - // Get any existing file's URIs. - - String mediaUrlsSerialized = null; - String mediaDescriptionsSerialized = null; - - if (!ListUtils.isEmpty(mediaUris)) { - List savedList = saveMedia(mediaUris, savedJsonUrls); - if (!ListUtils.isEmpty(savedList)) { - mediaUrlsSerialized = gson.toJson(savedList); - if (!ListUtils.isEmpty(savedJsonUrls)) { - deleteMedia(setDifference(savedJsonUrls, savedList)); - } - } else { - return false; - } - mediaDescriptionsSerialized = gson.toJson(mediaDescriptions); - } else if (!ListUtils.isEmpty(savedJsonUrls)) { - /* If there were URIs in the previous draft, but they've now been removed, those files - * can be deleted. */ - deleteMedia(savedJsonUrls); - } - final TootEntity toot = new TootEntity(savedTootUid, content, mediaUrlsSerialized, mediaDescriptionsSerialized, contentWarning, - inReplyToId, - replyingStatusContent, - replyingStatusAuthorUsername, - statusVisibility, - poll); - - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - tootDao.insertOrReplace(toot); - return null; - } - }.execute(); - return true; - } - public void deleteDraft(int tootId) { TootEntity item = tootDao.find(tootId); if (item != null) { @@ -124,82 +54,4 @@ public final class SaveTootHelper { tootDao.delete(item.getUid()); } - @Nullable - private List saveMedia(@NonNull List mediaUris, - @Nullable List existingUris) { - - File directory = context.getExternalFilesDir("Tusky"); - - if (directory == null || !(directory.exists())) { - Log.e(TAG, "Error obtaining directory to save media."); - return null; - } - - ContentResolver contentResolver = context.getContentResolver(); - ArrayList filesSoFar = new ArrayList<>(); - ArrayList results = new ArrayList<>(); - for (String mediaUri : mediaUris) { - /* If the media was already saved in a previous draft, there's no need to save another - * copy, just add the existing URI to the results. */ - if (existingUris != null) { - int index = existingUris.indexOf(mediaUri); - if (index != -1) { - results.add(mediaUri); - continue; - } - } - // Otherwise, save the media. - - Uri uri = Uri.parse(mediaUri); - - String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date()); - - String mimeType = contentResolver.getType(uri); - MimeTypeMap map = MimeTypeMap.getSingleton(); - String fileExtension = map.getExtensionFromMimeType(mimeType); - String filename = String.format("Tusky_Draft_Media_%s.%s", timeStamp, fileExtension); - File file = new File(directory, filename); - filesSoFar.add(file); - boolean copied = IOUtils.copyToFile(contentResolver, uri, file); - if (!copied) { - /* If any media files were created in prior iterations, delete those before - * returning. */ - for (File earlierFile : filesSoFar) { - boolean deleted = earlierFile.delete(); - if (!deleted) { - Log.i(TAG, "Could not delete the file " + earlierFile.toString()); - } - } - return null; - } - Uri resultUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileprovider", file); - results.add(resultUri.toString()); - } - return results; - } - - private void deleteMedia(List mediaUris) { - for (String uriString : mediaUris) { - Uri uri = Uri.parse(uriString); - if (context.getContentResolver().delete(uri, null, null) == 0) { - Log.e(TAG, String.format("Did not delete file %s.", uriString)); - } - } - } - - /** - * A∖B={x∈A|x∉B} - * - * @return all elements of set A that are not in set B. - */ - private static List setDifference(List a, List b) { - List c = new ArrayList<>(); - for (String s : a) { - if (!b.contains(s)) { - c.add(s); - } - } - return c; - } - -} +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_alert_circle.xml b/app/src/main/res/drawable/ic_alert_circle.xml new file mode 100644 index 00000000..4c894f0d --- /dev/null +++ b/app/src/main/res/drawable/ic_alert_circle.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_notebook.xml b/app/src/main/res/drawable/ic_notebook.xml index 2395cd14..93ff7891 100644 --- a/app/src/main/res/drawable/ic_notebook.xml +++ b/app/src/main/res/drawable/ic_notebook.xml @@ -4,5 +4,5 @@ android:width="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + \ No newline at end of file diff --git a/app/src/main/res/layout-sw640dp/fragment_view_thread.xml b/app/src/main/res/layout-sw640dp/fragment_view_thread.xml index 52de2b95..150f0860 100644 --- a/app/src/main/res/layout-sw640dp/fragment_view_thread.xml +++ b/app/src/main/res/layout-sw640dp/fragment_view_thread.xml @@ -1,8 +1,10 @@ + android:background="?attr/windowBackgroundColor" + tools:viewBindingIgnore="true"> + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_view_thread.xml b/app/src/main/res/layout/fragment_view_thread.xml index 433f1ed0..806d420a 100644 --- a/app/src/main/res/layout/fragment_view_thread.xml +++ b/app/src/main/res/layout/fragment_view_thread.xml @@ -1,9 +1,11 @@ + android:layout_gravity="top" + tools:viewBindingIgnore="true"> + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/toolbar_basic.xml b/app/src/main/res/layout/toolbar_basic.xml index 71039105..47bd2d90 100644 --- a/app/src/main/res/layout/toolbar_basic.xml +++ b/app/src/main/res/layout/toolbar_basic.xml @@ -1,19 +1,16 @@ - + - + android:layout_height="?attr/actionBarSize" /> - - - - - \ No newline at end of file + diff --git a/app/src/main/res/menu/drafts.xml b/app/src/main/res/menu/drafts.xml new file mode 100644 index 00000000..bbc9202f --- /dev/null +++ b/app/src/main/res/menu/drafts.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 835a32f8..6f4b1cee 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -36,7 +36,7 @@ الحسابات المحظورة طلبات المتابعة عدل ملفك التعريفي - المسودات + المسودات الرّخص \@%s شارَكَه %s diff --git a/app/src/main/res/values-ber/strings.xml b/app/src/main/res/values-ber/strings.xml index 2e0412ca..9726d4ff 100644 --- a/app/src/main/res/values-ber/strings.xml +++ b/app/src/main/res/values-ber/strings.xml @@ -16,7 +16,7 @@ ⵏⴰⴸⵉ ⵣⵔⴻⴳ ⴰⵎⴰⵖⵏⵓ ⴼⴼⴻⵖ - ⵉⵔⴻⵡⵡⴰⵢⴻⵏ + ⵉⵔⴻⵡⵡⴰⵢⴻⵏ ⵉⵙⵎⴻⵏⵢⵉⴼⴻⵏ ⵓⵖⴰⵍ ⴽⴻⵎⵎⴻⵍ diff --git a/app/src/main/res/values-bn-rBD/strings.xml b/app/src/main/res/values-bn-rBD/strings.xml index 2413319b..634f0d8f 100644 --- a/app/src/main/res/values-bn-rBD/strings.xml +++ b/app/src/main/res/values-bn-rBD/strings.xml @@ -292,7 +292,7 @@ মিডিয়া লুকানো সংবেদনশীল কন্টেন্ট লাইসেন্সগুলি - খসড়াগুলো + খসড়াগুলো আপনার প্রোফাইল সম্পাদনা করুন অনুরোধ অনুসরণ করুন অবরুদ্ধ ব্যবহারকারী diff --git a/app/src/main/res/values-bn-rIN/strings.xml b/app/src/main/res/values-bn-rIN/strings.xml index e578655a..ae3ae2f2 100644 --- a/app/src/main/res/values-bn-rIN/strings.xml +++ b/app/src/main/res/values-bn-rIN/strings.xml @@ -36,7 +36,7 @@ অবরুদ্ধ ব্যবহারকারী অনুরোধ অনুসরণ করুন আপনার প্রোফাইল সম্পাদনা করুন - খসড়াগুলো + খসড়াগুলো লাইসেন্সগুলি \@%s %s সমর্থন দিয়েছে diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index a769dbe8..9748a481 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -29,7 +29,7 @@ Usuaris blocats Peticions de seguiment Edita el perfil - Esborranys + Esborranys \@%s %s tootejat Contingut sensible diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 867b3ec2..bac6fe1a 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -36,7 +36,7 @@ Blokovaní uživatelé Žádosti o sledování Upravit váš profil - Koncepty + Koncepty Licence \@%s %s boostnul/a diff --git a/app/src/main/res/values-cy/strings.xml b/app/src/main/res/values-cy/strings.xml index a322a5df..b41bbd6f 100644 --- a/app/src/main/res/values-cy/strings.xml +++ b/app/src/main/res/values-cy/strings.xml @@ -32,7 +32,7 @@ Defnyddwyr wedi\'u blocio Dilyn ceisiadau Golygu\'ch Proffil - Drafftiau + Drafftiau Trwyddedau %s wedi\'u hybu Cynnwys sensitif diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 6218bbbf..decfff34 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -36,7 +36,7 @@ Blockierte Profile Folgeanfragen Dein Profil bearbeiten - Entwürfe + Entwürfe Lizenzen \@%s %s teilte diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml index e2e01108..34b5a5bc 100644 --- a/app/src/main/res/values-eo/strings.xml +++ b/app/src/main/res/values-eo/strings.xml @@ -36,7 +36,7 @@ Blokitaj uzantoj Petoj de sekvado Redakti vian profilon - Malnetoj + Malnetoj Permesiloj \@%s %s diskonigis diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 08955280..2b2633a6 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -36,7 +36,7 @@ Bloqueados Solicitudes Editar tu perfil - Borradores + Borradores Licencias \@%s %s compartió diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index f4eb9e71..0c616958 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -32,7 +32,7 @@ Blokeatuak Eskakizunak Profila editatu - Zirriborroak + Zirriborroak Lizentziak %s-(e)k bultzatu du Kontuz edukiarekin diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 2f20b210..2d5f570e 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -32,7 +32,7 @@ کاربران مسدود درخواست‌های پی‌گیری ویرایش نمایه‌تان - پیش‌نویس‌ها + پیش‌نویس‌ها پروانه‌ها %s تقویت کرد محتوای حسّاس diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 6240d813..74f5e105 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -36,7 +36,7 @@ Comptes bloqués Demandes d’abonnement Modifier votre profil - Brouillons + Brouillons Licences \@%s %s a partagé diff --git a/app/src/main/res/values-ga/strings.xml b/app/src/main/res/values-ga/strings.xml index aad2a612..130f4abe 100644 --- a/app/src/main/res/values-ga/strings.xml +++ b/app/src/main/res/values-ga/strings.xml @@ -177,7 +177,7 @@ Roghanna Cuntais Sainroghanna Logáil Amach - Dréachtaí + Dréachtaí Roghaí Theip ar fhíordheimhniú leis an gcás sin. Cad is sampla ann\? diff --git a/app/src/main/res/values-gd/strings.xml b/app/src/main/res/values-gd/strings.xml index ed594dc7..83bfce33 100644 --- a/app/src/main/res/values-gd/strings.xml +++ b/app/src/main/res/values-gd/strings.xml @@ -8,7 +8,7 @@ Roighainnean cunntais Roighainnean Clàraich a-mach - Dreachd + Dreachd Prìomhaich Dè a th ’ann an àite\? Deasaich diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index a4c1b373..20b6e5d0 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -2,7 +2,7 @@ हिंदी पसंदीदा - प्रारूप + प्रारूप लॉग आउट पसंद खाता प्राथमिकताएं diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 9215ed09..0086e299 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -36,7 +36,7 @@ Letiltott felhasználók Követési kérelmek Profilod szerkesztése - Piszkozatok + Piszkozatok Licenszek \@%s %s megtolta diff --git a/app/src/main/res/values-is/strings.xml b/app/src/main/res/values-is/strings.xml index e52cda27..7f911d96 100644 --- a/app/src/main/res/values-is/strings.xml +++ b/app/src/main/res/values-is/strings.xml @@ -3,7 +3,7 @@ Skrá inn með Mastodon Hvað er tilvik\? Eftirlæti - Drög + Drög Skrá út Kjörstillingar Eiginleikar tengingar diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 3b716a1f..c43e60f4 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -36,7 +36,7 @@ Utenti bloccati Richieste di seguirti Modifica il tuo profilo - Bozze + Bozze Licenze \@%s %s ha boostato diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index df4ec931..a716f733 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -35,7 +35,7 @@ ブロックしたユーザー フォローリクエスト プロフィールを編集 - 下書き + 下書き ライセンス %sさんがブーストしました 閲覧注意 diff --git a/app/src/main/res/values-kab/strings.xml b/app/src/main/res/values-kab/strings.xml index 8f51de5e..ad5a99d9 100644 --- a/app/src/main/res/values-kab/strings.xml +++ b/app/src/main/res/values-kab/strings.xml @@ -2,7 +2,7 @@ Qqen ɣer Maṣṭudun Ismenyifen - Irewwayen + Irewwayen Ffeɣ Iɣewwaṛen Iɣewwaṛen n umiḍan diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index cf11c797..893b4acb 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -37,7 +37,7 @@ 숨긴 도메인 팔로우 요청 프로필 편집 - 임시 저장 + 임시 저장 라이선스 \@%s %s님이 부스트 했습니다 diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index 72119eef..de97414b 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -3,7 +3,7 @@ മസ്റ്റഡോൺ വഴി പ്രവേശിക്കുക എന്താണ് ഒരു ഇൻസ്റ്റൻസ്\? പ്രിയപ്പെട്ടവ - കരടുകൾ + കരടുകൾ പുറത്തിറങ്ങുക മുൻഗണനകൾ അക്കൗണ്ട് മുൻഗണനകൾ diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index b8d2d09d..50d94b3d 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -36,7 +36,7 @@ Geblokkeerde gebruikers Volgverzoeken Profiel bewerken - Concepten + Concepten Licenties \@%s %s boostte diff --git a/app/src/main/res/values-no-rNB/strings.xml b/app/src/main/res/values-no-rNB/strings.xml index cd633dd0..3adcf1eb 100644 --- a/app/src/main/res/values-no-rNB/strings.xml +++ b/app/src/main/res/values-no-rNB/strings.xml @@ -36,7 +36,7 @@ Blokkerte brukere Forespørsler om følgen Endre profilen din - Kladder + Kladder Lisenser \@%s %s boosted diff --git a/app/src/main/res/values-oc/strings.xml b/app/src/main/res/values-oc/strings.xml index c22afc29..a404fef5 100644 --- a/app/src/main/res/values-oc/strings.xml +++ b/app/src/main/res/values-oc/strings.xml @@ -31,7 +31,7 @@ Utilizaires blocats Demandas d’abonament Modificar lo perfil - Borrolhons + Borrolhons Licéncias %s partejat Contengut sensible diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 9082f634..03ca9380 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -31,7 +31,7 @@ Zablokowani użytkownicy Prośby o możliwość śledzenia Edytuj profil - Szkice + Szkice Licencje %s podbił Wrażliwe treści diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 95bf8a29..d3636380 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -34,7 +34,7 @@ Usuários bloqueados Seguidores pendentes Editar perfil - Rascunhos + Rascunhos Licenças %s deu boost Mídia sensível diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 6adb5768..9e2cf800 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -36,7 +36,7 @@ Список блокировки Запросы на подписку Редактировать профиль - Черновики + Черновики Лицензии \@%s %s продвинул(а) diff --git a/app/src/main/res/values-sa/strings.xml b/app/src/main/res/values-sa/strings.xml index 2fb217ad..18bb7601 100644 --- a/app/src/main/res/values-sa/strings.xml +++ b/app/src/main/res/values-sa/strings.xml @@ -36,7 +36,7 @@ \@%s अनुज्ञापत्राणि कालबद्धदौत्यानि - लेखविकर्षाः + लेखविकर्षाः स्वीयव्यक्तिविवरणं सम्पाद्यताम् अनुसरणार्थमनुरोधाः प्रच्छन्नप्रदेशाः diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 68c27870..14af6584 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -1,7 +1,7 @@ Prihlásiť sa účtom Mastodon - Koncepty + Koncepty Odhlásiť sa Nastavenia Nastavenia účtu diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml index 70bb80be..35b3f08e 100644 --- a/app/src/main/res/values-sl/strings.xml +++ b/app/src/main/res/values-sl/strings.xml @@ -34,7 +34,7 @@ Blokirani uporabniki Zahteve za Sledenje Uredi svoj profil - Osnutki + Osnutki Licence \@%s Občutljiva vsebina diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index ff8a5a13..34eb3e8c 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -36,7 +36,7 @@ Blockerade användare Följarförfrågningar Ändra din profil - Utkast + Utkast Licenser \@%s %s knuffade diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml index 2affaa29..d726ba32 100644 --- a/app/src/main/res/values-ta/strings.xml +++ b/app/src/main/res/values-ta/strings.xml @@ -29,7 +29,7 @@ தடைசெய்யபட்ட பயனர்கள் பின்பற்ற கோரிக்கை சுயவிவரத்தை திருத்த - வரைவுகள் + வரைவுகள் %s மேலேற்றப்பட்டது உணர்ச்சிகரமான உள்ளடக்கம் ஊடகம் மறைக்கப்பட்டது diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index 5c994db6..dc5487a8 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -431,7 +431,7 @@ ตั้งค่าบัญชี ตั้งค่า ออกจากระบบ - ฉบับร่าง + ฉบับร่าง ชื่นชอบ การยืนยันตัวตนกับเซิร์ฟเวอร์นั้นล้มเหลว Instance คือ\? diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 596bafa4..684ecc21 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -36,7 +36,7 @@ Engellenmiş kullanıcılar Takip Etme İstekleri Profili düzeltme - Taslaklar + Taslaklar Lisanslar \@%s %s yineledi diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index cb7869ad..5c49c828 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -37,7 +37,7 @@ Налаштування акаунта Налаштування Вийти - Чернетки + Чернетки Вподобане Увійти Зʼєднання… diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 460ebb2d..d785a1f1 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -197,7 +197,7 @@ Cộng đồng Thông báo Bảng tin - Nháp + Nháp Lượt thích Máy chủ là gì\? Tải xem trước hình ảnh diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 03c70771..e123fd9e 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -36,7 +36,7 @@ 被屏蔽的用户 关注请求 编辑个人资料 - 草稿 + 草稿 开源协议 \@%s %s 转嘟了 diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 91bed1d4..5c50933d 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -36,7 +36,7 @@ 被封鎖的使用者 關注請求 編輯個人資料 - 草稿 + 草稿 開源授權 \@%s %s 轉嘟了 diff --git a/app/src/main/res/values-zh-rMO/strings.xml b/app/src/main/res/values-zh-rMO/strings.xml index 8333ff69..538284c6 100644 --- a/app/src/main/res/values-zh-rMO/strings.xml +++ b/app/src/main/res/values-zh-rMO/strings.xml @@ -36,7 +36,7 @@ 被封鎖的使用者 關注請求 編輯個人資料 - 草稿 + 草稿 開源授權 \@%s %s 轉嘟了 diff --git a/app/src/main/res/values-zh-rSG/strings.xml b/app/src/main/res/values-zh-rSG/strings.xml index 7fe476a6..2f45c629 100644 --- a/app/src/main/res/values-zh-rSG/strings.xml +++ b/app/src/main/res/values-zh-rSG/strings.xml @@ -36,7 +36,7 @@ 被屏蔽的用户 关注请求 编辑个人资料 - 草稿 + 草稿 开源协议 \@%s %s 转嘟了 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index f4e93299..6d690ace 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -36,7 +36,7 @@ 被封鎖的使用者 關注請求 編輯個人資料 - 草稿 + 草稿 開源授權 \@%s %s 轉嘟了 diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index dd4f599c..5ec69307 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -45,6 +45,7 @@ 5dp 12dp + 120dp 72dp 108dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6b08043e..7439808b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -40,7 +40,7 @@ Hidden domains Follow Requests Edit your profile - Drafts + Drafts Scheduled toots Announcements Licenses @@ -585,6 +585,7 @@ Wellbeing Your private note about this account Saved! + Some information that might affect your mental wellbeing will be hidden. This includes:\n\n - Favorite/Boost/Follow notifications\n - Favorite/Boost count on toots\n @@ -598,4 +599,15 @@ You cannot upload more than %1$d media attachments. Do you really want to delete the list %s? + This toot failed to send! + + + The draft feature in Tusky has been completely redesigned to be faster, more user friendly and less buggy.\n + You can still access your old drafts via a button on the new drafts screen, + but they will be removed in a future update! + + Old Drafts + Failed loading Reply information + Draft deleted + The Toot you drafted a reply to has been removed diff --git a/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt b/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt index 7f33a976..bd9be3b2 100644 --- a/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt @@ -13,7 +13,6 @@ * You should have received a copy of the GNU General Public License along with Tusky; if not, * see . */ - package com.keylesspalace.tusky import android.content.Intent @@ -25,6 +24,7 @@ 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.MediaUploader +import com.keylesspalace.tusky.components.drafts.DraftHelper import com.keylesspalace.tusky.db.* import com.keylesspalace.tusky.di.ViewModelFactory import com.keylesspalace.tusky.entity.Account @@ -115,6 +115,7 @@ class ComposeActivityTest { accountManagerMock, mock(MediaUploader::class.java), mock(ServiceClient::class.java), + mock(DraftHelper::class.java), mock(SaveTootHelper::class.java), dbMock ) From 378d2355086221f40da64c319a6636dfa8fd813e Mon Sep 17 00:00:00 2001 From: nailyk-weblate Date: Sun, 24 Jan 2021 21:40:32 +0000 Subject: [PATCH 26/81] Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/ Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/ --- app/src/main/res/values-ckb/strings.xml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/app/src/main/res/values-ckb/strings.xml b/app/src/main/res/values-ckb/strings.xml index d07ed227..56f4f5e9 100644 --- a/app/src/main/res/values-ckb/strings.xml +++ b/app/src/main/res/values-ckb/strings.xml @@ -115,7 +115,6 @@ مۆڵەتەکان ڕاگه یه نراوەکان توتی خشتەکراو - ڕەشنووسەکان دەستکاری پرۆفایلەکەت بکە بەدواداچونی داواکاریەکان بکە دۆمەینە شاراوەکان @@ -262,13 +261,6 @@ هەڵبژاردنی %d چەند هەڵبژاردنێک زیادکردنی هەڵبژاردن - ٧ ڕۆژ - ٣ ڕۆژ - ١ ڕۆژ - ٦ کاتژمێر - ١ کاتژمێر - ٣٠ خولەک - ٥ خولەک ڕاپرسی چالاککردنی ئاماژەکردنی لێدانی چالاک بۆ گۆڕین لە نێوان خشتەبەندەکان تاسکی کۆد و سەرمایەکانی تێدایە لەم پڕۆژە کراوەی سەرچاوە: From cd70f3d7d25cb6171c8202b7f4f1f7fa98c96c5a Mon Sep 17 00:00:00 2001 From: Vegard Skjefstad Date: Sun, 24 Jan 2021 21:40:32 +0000 Subject: [PATCH 27/81] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (456 of 456 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/nb_NO/ Translated using Weblate (Norwegian Bokmål) Currently translated at 100.0% (450 of 450 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/nb_NO/ Translated using Weblate (Norwegian Bokmål) Currently translated at 100.0% (447 of 447 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/nb_NO/ --- app/src/main/res/values-no-rNB/strings.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/src/main/res/values-no-rNB/strings.xml b/app/src/main/res/values-no-rNB/strings.xml index 3adcf1eb..36c7dc31 100644 --- a/app/src/main/res/values-no-rNB/strings.xml +++ b/app/src/main/res/values-no-rNB/strings.xml @@ -484,4 +484,16 @@ noen jeg følger publiserer en ny toot %s tootet akkurat Du kan ikke laste opp flere enn %1$d mediavedlegg. + Uendelig + Varighet + Er du sikker på at du vil slette listen %s\? + Vedlegg + Lyd + Tootet du kladdet et svar til har blitt fjernet + Kladd slettet + Lasting av svarinformasjon feilet + Gamle kladder + KladdfunksjonaLiteten i Tusky er skrevet om og er nå kjappere, mer brukervennlig, og med færre feil. +\nGamle kladder er fortsatt tilgjengelige via en knapp på den nye kladdskjermen, men de vil bli fjernet i en fremtidig oppdatering! + Sending av toot feilet! \ No newline at end of file From 295f5d5a8c7e3f996ea4de4752a8e94f0a3ea7ea Mon Sep 17 00:00:00 2001 From: Ho Nhat Duy Date: Sun, 24 Jan 2021 21:40:32 +0000 Subject: [PATCH 28/81] Translated using Weblate (Vietnamese) Currently translated at 100.0% (456 of 456 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/ Translated using Weblate (Vietnamese) Currently translated at 100.0% (450 of 450 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/ Translated using Weblate (Vietnamese) Currently translated at 100.0% (450 of 450 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/ Translated using Weblate (Vietnamese) Currently translated at 100.0% (450 of 450 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/ Translated using Weblate (Vietnamese) Currently translated at 100.0% (447 of 447 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/ --- app/src/main/res/values-vi/strings.xml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index d785a1f1..47b8007f 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -188,7 +188,7 @@ Người theo dõi Theo dõi Ghim - Tương tác + Rép Tút Tút Xếp tab @@ -456,7 +456,7 @@ Bỏ ẩn %s Ẩn tiêu đề tab Đã lưu! - Ghi chú của bạn + Thêm ghi chú Chưa có thông báo. Tin tức Ẩn số liệu trên trang cá nhân @@ -476,4 +476,16 @@ người tôi đăng ký theo dõi đăng tút mới %s vừa đăng tút Bạn không thể đính kèm quá %1$d tệp. + Không giới hạn + Độ dài + Bạn thật sự muốn xóa danh sách %s\? + Đính kèm + Âm thanh + Tút bạn lên lịch đã bị hủy bỏ + Tút lên lịch cũ + Tút lên lịch đã xóa + Chưa tải được bình luận + Tính năng lên lịch đăng tút của Tusky sẽ được thiết kế lại hoàn toàn để nhanh hơn, thân thiện và ít lỗi hơn. +\nBạn vẫn có thể xem lại bản nháp cũ nhưng chúng sẽ bị xóa bỏ trong bản cập nhật tương lai! + Đăng tút không thành công! \ No newline at end of file From a45c8613addd728e28cd619ff3627f0b840916e9 Mon Sep 17 00:00:00 2001 From: Juanjo Salvador Date: Sun, 24 Jan 2021 21:40:32 +0000 Subject: [PATCH 29/81] Translated using Weblate (Spanish) Currently translated at 100.0% (456 of 456 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/es/ --- app/src/main/res/values-es/strings.xml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 2b2633a6..3bd9e38a 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -495,4 +495,17 @@ \n- Seguidor/Publicar estadísticas en perfiles \n \nLas notificaciones Push no serán afectadas, pero puedes revisar manualmente tus referencias de notificaciones. + El toot al que redactaste una respuesta ha sido eliminado + Borrador eliminado + Error al cargar la información de respuesta + Borradores antiguos + La función de borrador en Tusky se ha rediseñado por completo para que sea más rápida, más fácil de usar y con menos errores. +\nAún puede acceder a sus borradores antiguos a través de un botón en la pantalla de borradores nuevos, ¡pero se eliminarán en una actualización futura! + ¡Este toot no se pudo enviar! + ¿Realmente quieres eliminar la lista %s\? + Indefinido + Duración + Adjuntos + Audio + Limitar notificaciones de cronología \ No newline at end of file From 46468b8a782b954cbc5a1bb76cd5c2244b4ce39b Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Mon, 25 Jan 2021 16:23:32 +0100 Subject: [PATCH 30/81] use CustomTabColorSchemeParams instead of deprecated methods (#2049) --- .../com/keylesspalace/tusky/LoginActivity.kt | 19 ++++++++++-------- .../keylesspalace/tusky/util/LinkHelper.java | 20 ++++++++++--------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt b/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt index f68b2337..13699723 100644 --- a/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt @@ -20,7 +20,6 @@ import android.content.Context import android.content.Intent import android.content.SharedPreferences import android.net.Uri -import android.os.Build import android.os.Bundle import android.text.method.LinkMovementMethod import android.util.Log @@ -28,6 +27,7 @@ import android.view.MenuItem import android.view.View import android.widget.TextView import androidx.appcompat.app.AlertDialog +import androidx.browser.customtabs.CustomTabColorSchemeParams import androidx.browser.customtabs.CustomTabsIntent import com.bumptech.glide.Glide import com.keylesspalace.tusky.di.Injectable @@ -346,16 +346,19 @@ class LoginActivity : BaseActivity(), Injectable { private fun openInCustomTab(uri: Uri, context: Context): Boolean { val toolbarColor = ThemeUtils.getColor(context, R.attr.colorSurface) - val customTabsIntentBuilder = CustomTabsIntent.Builder() + val navigationbarColor = ThemeUtils.getColor(context, android.R.attr.navigationBarColor) + val navigationbarDividerColor = ThemeUtils.getColor(context, R.attr.dividerColor) + + val colorSchemeParams = CustomTabColorSchemeParams.Builder() .setToolbarColor(toolbarColor) + .setNavigationBarColor(navigationbarColor) + .setNavigationBarDividerColor(navigationbarDividerColor) + .build() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { - customTabsIntentBuilder.setNavigationBarColor( - ThemeUtils.getColor(context, android.R.attr.navigationBarColor) - ) - } + val customTabsIntent = CustomTabsIntent.Builder() + .setDefaultColorSchemeParams(colorSchemeParams) + .build() - val customTabsIntent = customTabsIntentBuilder.build() try { customTabsIntent.launchUrl(context, uri) } catch (e: ActivityNotFoundException) { diff --git a/app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.java b/app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.java index 64e329c8..ec0c8a3e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.java @@ -19,7 +19,6 @@ import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.net.Uri; -import android.os.Build; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.method.LinkMovementMethod; @@ -31,6 +30,7 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.browser.customtabs.CustomTabColorSchemeParams; import androidx.browser.customtabs.CustomTabsIntent; import androidx.preference.PreferenceManager; @@ -229,18 +229,20 @@ public class LinkHelper { */ public static void openLinkInCustomTab(Uri uri, Context context) { int toolbarColor = ThemeUtils.getColor(context, R.attr.colorSurface); + int navigationbarColor = ThemeUtils.getColor(context, android.R.attr.navigationBarColor); + int navigationbarDividerColor = ThemeUtils.getColor(context, R.attr.dividerColor); - CustomTabsIntent.Builder customTabsIntentBuilder = new CustomTabsIntent.Builder() + CustomTabColorSchemeParams colorSchemeParams = new CustomTabColorSchemeParams.Builder() .setToolbarColor(toolbarColor) - .setShowTitle(true); + .setNavigationBarColor(navigationbarColor) + .setNavigationBarDividerColor(navigationbarDividerColor) + .build(); - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { - customTabsIntentBuilder.setNavigationBarColor( - ThemeUtils.getColor(context, android.R.attr.navigationBarColor) - ); - } + CustomTabsIntent customTabsIntent = new CustomTabsIntent.Builder() + .setDefaultColorSchemeParams(colorSchemeParams) + .setShowTitle(true) + .build(); - CustomTabsIntent customTabsIntent = customTabsIntentBuilder.build(); try { customTabsIntent.launchUrl(context, uri); } catch (ActivityNotFoundException e) { From 968c4ed3e0e09b8c9bc2a4cd91c5800000a87170 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Mon, 25 Jan 2021 16:23:43 +0100 Subject: [PATCH 31/81] add proguard rule to keep DraftAttachment.Type (#2054) --- app/proguard-rules.pro | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 78b4be88..a05994a1 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -43,6 +43,10 @@ public *; } +-keep enum com.keylesspalace.tusky.db.DraftAttachment$Type { + public *; +} + # preserve line numbers for crash reporting -keepattributes SourceFile,LineNumberTable -renamesourcefileattribute SourceFile From 0275504a059163b697acd740ff0c43af664c5f1f Mon Sep 17 00:00:00 2001 From: lenchan139 Date: Tue, 26 Jan 2021 23:38:03 +0800 Subject: [PATCH 32/81] Move compose content's EditText cursor to first when shared content parsed in ComposeActivity (#2030) * multiple media upload support * multiple media upload support * multiple media upload support * remove typing * Update app/src/main/res/values/strings.xml Co-authored-by: Konrad Pozniak * remove magic number on string.xml and add to activity. * move edittext cursor to first when shareBody parsed in ComposeActivity Co-authored-by: Konrad Pozniak --- .../tusky/components/compose/ComposeActivity.kt | 5 ++++- app/src/main/res/values/strings.xml | 2 +- 2 files changed, 5 insertions(+), 2 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 69a09088..88329451 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 @@ -30,7 +30,7 @@ import android.os.Build import android.os.Bundle import android.os.Parcelable import android.provider.MediaStore -import android.util.Log + import android.util.Log import android.view.KeyEvent import android.view.MenuItem import android.view.View @@ -202,6 +202,9 @@ class ComposeActivity : BaseActivity(), val left = min(start, end) val right = max(start, end) composeEditField.text.replace(left, right, shareBody, 0, shareBody.length) + // move edittext cursor to first when shareBody parsed + composeEditField.text.insert(0, "\n") + composeEditField.setSelection(0) } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7439808b..c1615e04 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -598,9 +598,9 @@ Hide quantitative stats on profiles You cannot upload more than %1$d media attachments. Do you really want to delete the list %s? - This toot failed to send! + The draft feature in Tusky has been completely redesigned to be faster, more user friendly and less buggy.\n You can still access your old drafts via a button on the new drafts screen, From b00aa9b461a27e4f1e7fcfe1e0d70eb88645f628 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 31 Jan 2021 20:57:48 +0300 Subject: [PATCH 33/81] SpanUtils: highlight nicknames with dashes (#2061) While it's not allowed to create such accounts on Mastodon, it federates fine with servers that allow dashes. --- app/src/main/java/com/keylesspalace/tusky/util/SpanUtils.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/util/SpanUtils.kt b/app/src/main/java/com/keylesspalace/tusky/util/SpanUtils.kt index 48466996..307fbeae 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/SpanUtils.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/SpanUtils.kt @@ -18,7 +18,7 @@ private const val TAG_REGEX = "(?:^|[^/)A-Za-z0-9_])#([\\w_]*[\\p{Alpha}_][\\w_] * @see * Account#MENTION_RE */ -private const val MENTION_REGEX = "(?:^|[^/[:word:]])@([a-z0-9_]+(?:@[a-z0-9\\.\\-]+[a-z0-9]+)?)" +private const val MENTION_REGEX = "(?:^|[^/[:word:]])@([a-z0-9_-]+(?:@[a-z0-9\\.\\-]+[a-z0-9]+)?)" private const val HTTP_URL_REGEX = "(?:(^|\\b)http://[^\\s]+)" private const val HTTPS_URL_REGEX = "(?:(^|\\b)https://[^\\s]+)" From 455942505c82ee3cd7b97a9a17cb6e227e9e2419 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 31 Jan 2021 20:58:45 +0300 Subject: [PATCH 34/81] ComposeViewModel: remove unused variable (#2060) --- .../tusky/components/compose/ComposeViewModel.kt | 4 +--- 1 file changed, 1 insertion(+), 3 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 a1a5e7e6..71293511 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 @@ -271,12 +271,10 @@ class ComposeViewModel @Inject constructor( val mediaIds = ArrayList() val mediaUris = ArrayList() val mediaDescriptions = ArrayList() - val mediaTypes = ArrayList() for (item in media.value!!) { mediaIds.add(item.id!!) mediaUris.add(item.uri) mediaDescriptions.add(item.description ?: "") - mediaTypes.add(item.type) } val tootToSend = TootToSend( @@ -504,4 +502,4 @@ data class ComposeInstanceParams( /** * Thrown when trying to add an image when video is already present or the other way around */ -class VideoOrImageException : Exception() \ No newline at end of file +class VideoOrImageException : Exception() From 2d2b79aa478f99395a9479e09513723560f6b356 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 31 Jan 2021 21:22:06 +0300 Subject: [PATCH 35/81] layout: move media preview to standalone xml and include it in containers (#2063) --- app/src/main/res/layout/item_conversation.xml | 183 +---------------- .../main/res/layout/item_media_preview.xml | 190 ++++++++++++++++++ app/src/main/res/layout/item_status.xml | 188 +---------------- .../main/res/layout/item_status_detailed.xml | 186 +---------------- 4 files changed, 195 insertions(+), 552 deletions(-) create mode 100644 app/src/main/res/layout/item_media_preview.xml diff --git a/app/src/main/res/layout/item_conversation.xml b/app/src/main/res/layout/item_conversation.xml index d746f99e..1d6eab67 100644 --- a/app/src/main/res/layout/item_conversation.xml +++ b/app/src/main/res/layout/item_conversation.xml @@ -203,188 +203,7 @@ app:layout_constraintTop_toBottomOf="@id/button_toggle_content" tools:visibility="gone"> - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/app/src/main/res/layout/item_media_preview.xml b/app/src/main/res/layout/item_media_preview.xml new file mode 100644 index 00000000..27b58e7a --- /dev/null +++ b/app/src/main/res/layout/item_media_preview.xml @@ -0,0 +1,190 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_status.xml b/app/src/main/res/layout/item_status.xml index b6c59725..8edca2e1 100644 --- a/app/src/main/res/layout/item_status.xml +++ b/app/src/main/res/layout/item_status.xml @@ -257,192 +257,8 @@ app:layout_constraintStart_toStartOf="@id/status_display_name" app:layout_constraintTop_toBottomOf="@id/status_card_view" tools:visibility="visible"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + diff --git a/app/src/main/res/layout/item_status_detailed.xml b/app/src/main/res/layout/item_status_detailed.xml index f3ad6ed5..a075503e 100644 --- a/app/src/main/res/layout/item_status_detailed.xml +++ b/app/src/main/res/layout/item_status_detailed.xml @@ -206,189 +206,7 @@ android:importantForAccessibility="noHideDescendants" app:layout_constraintTop_toBottomOf="@id/status_card_view"> - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -588,4 +406,4 @@ app:layout_constraintTop_toTopOf="@id/status_reply" app:srcCompat="@drawable/ic_more_horiz_24dp" /> - \ No newline at end of file + From 886ff2f06b69e67ca459e02d0633f4d69c5b21da Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Sun, 31 Jan 2021 19:34:33 +0100 Subject: [PATCH 36/81] get rid of BaseFragment by using RxJava instead of Retrofit Calls (#2055) * get rid of BaseFragment by using RxJava instead of Retrofit Calls * fix tests --- .../components/drafts/DraftsViewModel.kt | 2 +- .../fragment/InstanceListFragment.kt | 8 +- .../tusky/fragment/AccountListFragment.kt | 52 ++++------- .../tusky/fragment/AccountMediaFragment.kt | 54 +++++------ .../tusky/fragment/BaseFragment.java | 43 --------- .../tusky/fragment/NotificationsFragment.java | 89 ++++++++----------- .../tusky/fragment/SFragment.java | 14 ++- .../tusky/fragment/TimelineFragment.java | 63 ++++++------- .../tusky/fragment/ViewMediaFragment.kt | 3 +- .../tusky/fragment/ViewThreadFragment.java | 65 +++++--------- .../tusky/network/MastodonApi.kt | 49 +++------- .../tusky/repository/TimelineRepository.kt | 8 +- .../tusky/fragment/TimelineRepositoryTest.kt | 29 +++--- 13 files changed, 170 insertions(+), 309 deletions(-) delete mode 100644 app/src/main/java/com/keylesspalace/tusky/fragment/BaseFragment.java diff --git a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsViewModel.kt index 9eca963a..f928b6d0 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsViewModel.kt @@ -57,7 +57,7 @@ class DraftsViewModel @Inject constructor( } fun getToot(tootId: String): Single { - return api.statusSingle(tootId) + return api.status(tootId) } override fun onCleared() { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/fragment/InstanceListFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/fragment/InstanceListFragment.kt index bb850bdc..093fc42d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/fragment/InstanceListFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/fragment/InstanceListFragment.kt @@ -5,6 +5,7 @@ import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.fragment.app.Fragment import androidx.lifecycle.Lifecycle import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager @@ -14,7 +15,6 @@ import com.keylesspalace.tusky.R import com.keylesspalace.tusky.components.instancemute.adapter.DomainMutesAdapter import com.keylesspalace.tusky.components.instancemute.interfaces.InstanceActionListener import com.keylesspalace.tusky.di.Injectable -import com.keylesspalace.tusky.fragment.BaseFragment import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.util.HttpHeaderLink import com.keylesspalace.tusky.util.hide @@ -30,7 +30,7 @@ import retrofit2.Response import java.io.IOException import javax.inject.Inject -class InstanceListFragment: BaseFragment(), Injectable, InstanceActionListener { +class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectable, InstanceActionListener { @Inject lateinit var api: MastodonApi @@ -39,10 +39,6 @@ class InstanceListFragment: BaseFragment(), Injectable, InstanceActionListener { private var adapter = DomainMutesAdapter(this) private lateinit var scrollListener: EndlessOnScrollListener - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - return inflater.inflate(R.layout.fragment_instance_list, container, false) - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.kt index d5785ae3..93a6162b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.kt @@ -17,9 +17,8 @@ package com.keylesspalace.tusky.fragment import android.os.Bundle import android.util.Log -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup +import androidx.fragment.app.Fragment import androidx.lifecycle.Lifecycle import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager @@ -45,14 +44,12 @@ import com.uber.autodispose.autoDispose import io.reactivex.Single import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.android.synthetic.main.fragment_account_list.* -import retrofit2.Call -import retrofit2.Callback import retrofit2.Response import java.io.IOException import java.util.* import javax.inject.Inject -class AccountListFragment : BaseFragment(), AccountActionListener, Injectable { +class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountActionListener, Injectable { @Inject lateinit var api: MastodonApi @@ -71,10 +68,6 @@ class AccountListFragment : BaseFragment(), AccountActionListener, Injectable { id = arguments?.getString(ARG_ID) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - return inflater.inflate(R.layout.fragment_account_list, container, false) - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -202,27 +195,23 @@ class AccountListFragment : BaseFragment(), AccountActionListener, Injectable { override fun onRespondToFollowRequest(accept: Boolean, accountId: String, position: Int) { - val callback = object : Callback { - override fun onResponse(call: Call, response: Response) { - if (response.isSuccessful) { - onRespondToFollowRequestSuccess(position) - } else { - onRespondToFollowRequestFailure(accept, accountId) - } - } - - override fun onFailure(call: Call, t: Throwable) { - onRespondToFollowRequestFailure(accept, accountId) - } - } - - val call = if (accept) { + if (accept) { api.authorizeFollowRequest(accountId) } else { api.rejectFollowRequest(accountId) - } - callList.add(call) - call.enqueue(callback) + }.observeOn(AndroidSchedulers.mainThread()) + .autoDispose(from(this, Lifecycle.Event.ON_DESTROY)) + .subscribe({ + onRespondToFollowRequestSuccess(position) + }, { throwable -> + val verb = if (accept) { + "accept" + } else { + "reject" + } + Log.e(TAG, "Failed to $verb account id $accountId.", throwable) + }) + } private fun onRespondToFollowRequestSuccess(position: Int) { @@ -230,15 +219,6 @@ class AccountListFragment : BaseFragment(), AccountActionListener, Injectable { followRequestsAdapter.removeItem(position) } - private fun onRespondToFollowRequestFailure(accept: Boolean, accountId: String) { - val verb = if (accept) { - "accept" - } else { - "reject" - } - Log.e(TAG, "Failed to $verb account id $accountId.") - } - private fun getFetchCallByListType(fromId: String?): Single>> { return when (type) { Type.FOLLOWS -> { diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt index 0ae20758..b85a87f3 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt @@ -18,12 +18,13 @@ package com.keylesspalace.tusky.fragment import android.graphics.Color import android.os.Bundle import android.util.Log -import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView import androidx.core.app.ActivityOptionsCompat import androidx.core.view.ViewCompat +import androidx.fragment.app.Fragment +import androidx.lifecycle.Lifecycle import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide @@ -40,9 +41,11 @@ import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.show import com.keylesspalace.tusky.view.SquareImageView import com.keylesspalace.tusky.viewdata.AttachmentViewData +import com.uber.autodispose.android.lifecycle.autoDispose +import io.reactivex.SingleObserver +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable import kotlinx.android.synthetic.main.fragment_timeline.* -import retrofit2.Call -import retrofit2.Callback import retrofit2.Response import java.io.IOException import java.util.* @@ -54,7 +57,7 @@ import javax.inject.Inject * Fragment with multiple columns of media previews for the specified account. */ -class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable { +class AccountMediaFragment : Fragment(R.layout.fragment_timeline), RefreshableFragment, Injectable { companion object { @JvmStatic fun newInstance(accountId: String, enableSwipeToRefresh:Boolean=true): AccountMediaFragment { @@ -78,14 +81,13 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable { lateinit var api: MastodonApi private val adapter = MediaGridAdapter() - private var currentCall: Call>? = null private val statuses = mutableListOf() private var fetchingStatus = FetchingStatus.NOT_FETCHING private lateinit var accountId: String - private val callback = object : Callback> { - override fun onFailure(call: Call>?, t: Throwable?) { + private val callback = object : SingleObserver>> { + override fun onError(t: Throwable) { fetchingStatus = FetchingStatus.NOT_FETCHING if (isAdded) { @@ -107,7 +109,7 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable { Log.d(TAG, "Failed to fetch account media", t) } - override fun onResponse(call: Call>, response: Response>) { + override fun onSuccess(response: Response>) { fetchingStatus = FetchingStatus.NOT_FETCHING if (isAdded) { swipeRefreshLayout.isRefreshing = false @@ -128,22 +130,23 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable { if (statuses.isEmpty()) { statusView.show() - statusView.setup(R.drawable.elephant_friend_empty, R.string.message_empty, - null) + statusView.setup(R.drawable.elephant_friend_empty, R.string.message_empty) } } } } + + override fun onSubscribe(d: Disposable) {} } - private val bottomCallback = object : Callback> { - override fun onFailure(call: Call>?, t: Throwable?) { + private val bottomCallback = object : SingleObserver>> { + override fun onError(t: Throwable) { fetchingStatus = FetchingStatus.NOT_FETCHING Log.d(TAG, "Failed to fetch account media", t) } - override fun onResponse(call: Call>, response: Response>) { + override fun onSuccess(response: Response>) { fetchingStatus = FetchingStatus.NOT_FETCHING val body = response.body() body?.let { fetched -> @@ -160,6 +163,7 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable { } } + override fun onSubscribe(d: Disposable) { } } override fun onCreate(savedInstanceState: Bundle?) { @@ -167,10 +171,6 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable { isSwipeToRefreshEnabled = arguments?.getBoolean(ARG_ENABLE_SWIPE_TO_REFRESH,true) == true accountId = arguments?.getString(ACCOUNT_ID_ARG)!! } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_timeline, container, false) - } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -202,8 +202,10 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable { statuses.lastOrNull()?.let { (id) -> Log.d(TAG, "Requesting statuses with max_id: ${id}, (bottom)") fetchingStatus = FetchingStatus.FETCHING_BOTTOM - currentCall = api.accountStatuses(accountId, id, null, null, null, true, null) - currentCall?.enqueue(bottomCallback) + api.accountStatuses(accountId, id, null, null, null, true, null) + .observeOn(AndroidSchedulers.mainThread()) + .autoDispose(this@AccountMediaFragment, Lifecycle.Event.ON_DESTROY) + .subscribe(bottomCallback) } } } @@ -216,14 +218,15 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable { private fun refresh() { statusView.hide() if (fetchingStatus != FetchingStatus.NOT_FETCHING) return - currentCall = if (statuses.isEmpty()) { + if (statuses.isEmpty()) { fetchingStatus = FetchingStatus.INITIAL_FETCHING api.accountStatuses(accountId, null, null, null, null, true, null) } else { fetchingStatus = FetchingStatus.REFRESHING api.accountStatuses(accountId, null, statuses[0].id, null, null, true, null) - } - currentCall?.enqueue(callback) + }.observeOn(AndroidSchedulers.mainThread()) + .autoDispose(this, Lifecycle.Event.ON_DESTROY) + .subscribe(callback) if (!isSwipeToRefreshEnabled) topProgressBar?.show() @@ -235,8 +238,10 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable { } if (fetchingStatus == FetchingStatus.NOT_FETCHING && statuses.isEmpty()) { fetchingStatus = FetchingStatus.INITIAL_FETCHING - currentCall = api.accountStatuses(accountId, null, null, null, null, true, null) - currentCall?.enqueue(callback) + api.accountStatuses(accountId, null, null, null, null, true, null) + .observeOn(AndroidSchedulers.mainThread()) + .autoDispose(this@AccountMediaFragment, Lifecycle.Event.ON_DESTROY) + .subscribe(callback) } else if (needToRefresh) refresh() @@ -339,5 +344,4 @@ class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable { needToRefresh = true } - } \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/BaseFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/BaseFragment.java deleted file mode 100644 index b674b8ba..00000000 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/BaseFragment.java +++ /dev/null @@ -1,43 +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.fragment; - -import android.os.Bundle; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; - -import java.util.ArrayList; -import java.util.List; - -import retrofit2.Call; - -public class BaseFragment extends Fragment { - protected List callList; - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - callList = new ArrayList<>(); - } - - @Override - public void onDestroy() { - for (Call call : callList) { - call.cancel(); - } - super.onDestroy(); - } -} 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 fe0c75f5..216ee228 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java @@ -102,13 +102,11 @@ import at.connyduck.sparkbutton.helpers.Utils; import io.reactivex.Observable; import io.reactivex.Single; import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.disposables.Disposable; import kotlin.Unit; import kotlin.collections.CollectionsKt; import kotlin.jvm.functions.Function1; -import okhttp3.ResponseBody; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; import static com.keylesspalace.tusky.util.StringUtils.isLessThan; import static com.uber.autodispose.AutoDispose.autoDisposable; @@ -125,8 +123,9 @@ public class NotificationsFragment extends SFragment implements private static final int LOAD_AT_ONCE = 30; private int maxPlaceholderId = 0; + private final Set notificationFilter = new HashSet<>(); - private Set notificationFilter = new HashSet<>(); + private final CompositeDisposable disposables = new CompositeDisposable(); private enum FetchEnd { TOP, @@ -685,32 +684,21 @@ public class NotificationsFragment extends SFragment implements updateAdapter(); //Execute clear notifications request - Call call = mastodonApi.clearNotifications(); - call.enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - if (isAdded()) { - if (!response.isSuccessful()) { - //Reload notifications on failure - fullyRefreshWithProgressBar(true); - } - } - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - //Reload notifications on failure - fullyRefreshWithProgressBar(true); - } - }); - callList.add(call); + mastodonApi.clearNotifications() + .observeOn(AndroidSchedulers.mainThread()) + .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY))) + .subscribe( + response -> { + // nothing to do + }, + throwable -> { + //Reload notifications on failure + fullyRefreshWithProgressBar(true); + }); } private void resetNotificationsLoad() { - for (Call callItem : callList) { - callItem.cancel(); - } - callList.clear(); + disposables.clear(); bottomLoading = false; topLoading = false; @@ -840,8 +828,8 @@ public class NotificationsFragment extends SFragment implements @Override public void onRespondToFollowRequest(boolean accept, String id, int position) { Single request = accept ? - mastodonApi.authorizeFollowRequestObservable(id) : - mastodonApi.rejectFollowRequestObservable(id); + mastodonApi.authorizeFollowRequest(id) : + mastodonApi.rejectFollowRequest(id); request.observeOn(AndroidSchedulers.mainThread()) .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY))) .subscribe( @@ -959,27 +947,20 @@ public class NotificationsFragment extends SFragment implements bottomLoading = true; } - Call> call = mastodonApi.notifications(fromId, uptoId, LOAD_AT_ONCE, showNotificationsFilter ? notificationFilter : null); - - call.enqueue(new Callback>() { - @Override - public void onResponse(@NonNull Call> call, - @NonNull Response> response) { - if (response.isSuccessful()) { - String linkHeader = response.headers().get("Link"); - onFetchNotificationsSuccess(response.body(), linkHeader, fetchEnd, pos); - } else { - onFetchNotificationsFailure(new Exception(response.message()), fetchEnd, pos); - } - } - - @Override - public void onFailure(@NonNull Call> call, @NonNull Throwable t) { - if (!call.isCanceled()) - onFetchNotificationsFailure((Exception) t, fetchEnd, pos); - } - }); - callList.add(call); + Disposable notificationCall = mastodonApi.notifications(fromId, uptoId, LOAD_AT_ONCE, showNotificationsFilter ? notificationFilter : null) + .observeOn(AndroidSchedulers.mainThread()) + .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY))) + .subscribe( + response -> { + if (response.isSuccessful()) { + String linkHeader = response.headers().get("Link"); + onFetchNotificationsSuccess(response.body(), linkHeader, fetchEnd, pos); + } else { + onFetchNotificationsFailure(new Exception(response.message()), fetchEnd, pos); + } + }, + throwable -> onFetchNotificationsFailure(throwable, fetchEnd, pos)); + disposables.add(notificationCall); } private void onFetchNotificationsSuccess(List notifications, String linkHeader, @@ -1038,7 +1019,7 @@ public class NotificationsFragment extends SFragment implements progressBar.setVisibility(View.GONE); } - private void onFetchNotificationsFailure(Exception exception, FetchEnd fetchEnd, int position) { + private void onFetchNotificationsFailure(Throwable throwable, FetchEnd fetchEnd, int position) { swipeRefreshLayout.setRefreshing(false); if (fetchEnd == FetchEnd.MIDDLE && !notifications.get(position).isRight()) { Placeholder placeholder = notifications.get(position).asLeft(); @@ -1050,7 +1031,7 @@ public class NotificationsFragment extends SFragment implements this.statusView.setVisibility(View.VISIBLE); swipeRefreshLayout.setEnabled(false); this.showingError = true; - if (exception instanceof IOException) { + if (throwable instanceof IOException) { this.statusView.setup(R.drawable.elephant_offline, R.string.error_network, __ -> { this.progressBar.setVisibility(View.VISIBLE); this.onRefresh(); @@ -1065,7 +1046,7 @@ public class NotificationsFragment extends SFragment implements } updateFilterVisibility(); } - Log.e(TAG, "Fetch failure: " + exception.getMessage()); + Log.e(TAG, "Fetch failure: " + throwable.getMessage()); if (fetchEnd == FetchEnd.TOP) { topLoading = false; 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 4c50740b..9d4b45b3 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java @@ -20,7 +20,6 @@ import android.app.DownloadManager; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; @@ -30,8 +29,6 @@ import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.widget.CheckBox; -import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; @@ -41,14 +38,14 @@ import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.PopupMenu; import androidx.core.app.ActivityOptionsCompat; import androidx.core.view.ViewCompat; +import androidx.fragment.app.Fragment; import androidx.lifecycle.Lifecycle; -import androidx.preference.PreferenceManager; import com.keylesspalace.tusky.BaseActivity; import com.keylesspalace.tusky.BottomSheetActivity; import com.keylesspalace.tusky.MainActivity; -import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.PostLookupFallbackBehavior; +import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.ViewMediaActivity; import com.keylesspalace.tusky.ViewTagActivity; import com.keylesspalace.tusky.components.compose.ComposeActivity; @@ -76,9 +73,8 @@ import java.util.regex.Pattern; import javax.inject.Inject; -import kotlin.Unit; - import io.reactivex.android.schedulers.AndroidSchedulers; +import kotlin.Unit; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; @@ -92,7 +88,7 @@ import static com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvid * adapters. I feel like the profile pages and thread viewer, which I haven't made yet, will also * overlap functionality. So, I'm momentarily leaving it and hopefully working on those will clear * up what needs to be where. */ -public abstract class SFragment extends BaseFragment implements Injectable { +public abstract class SFragment extends Fragment implements Injectable { protected abstract void removeItem(int position); @@ -103,7 +99,7 @@ public abstract class SFragment extends BaseFragment implements Injectable { private static List filters; private boolean filterRemoveRegex; private Matcher filterRemoveRegexMatcher; - private static Matcher alphanumeric = Pattern.compile("^\\w+$").matcher(""); + private static final Matcher alphanumeric = Pattern.compile("^\\w+$").matcher(""); @Inject public MastodonApi mastodonApi; diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java index d6910a4c..06ca56f3 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java @@ -101,12 +101,11 @@ import javax.inject.Inject; import at.connyduck.sparkbutton.helpers.Utils; import io.reactivex.Observable; +import io.reactivex.Single; import io.reactivex.android.schedulers.AndroidSchedulers; import kotlin.Unit; import kotlin.collections.CollectionsKt; import kotlin.jvm.functions.Function1; -import retrofit2.Call; -import retrofit2.Callback; import retrofit2.Response; import static com.uber.autodispose.AutoDispose.autoDisposable; @@ -1004,7 +1003,7 @@ public class TimelineFragment extends SFragment implements } } - private Call> getFetchCallByTimelineType(String fromId, String uptoId) { + private Single>> getFetchCallByTimelineType(String fromId, String uptoId) { MastodonApi api = mastodonApi; switch (kind) { default: @@ -1051,37 +1050,31 @@ public class TimelineFragment extends SFragment implements .observeOn(AndroidSchedulers.mainThread()) .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY))) .subscribe( - (result) -> onFetchTimelineSuccess(result, fetchEnd, pos), - (err) -> onFetchTimelineFailure(new Exception(err), fetchEnd, pos) + result -> onFetchTimelineSuccess(result, fetchEnd, pos), + err -> onFetchTimelineFailure(err, fetchEnd, pos) ); } else { - Callback> callback = new Callback>() { - @Override - public void onResponse(@NonNull Call> call, @NonNull Response> response) { - if (response.isSuccessful()) { - @Nullable - String newNextId = extractNextId(response); - if (newNextId != null) { - // when we reach the bottom of the list, we won't have a new link. If - // we blindly write `null` here we will start loading from the top - // again. - nextId = newNextId; - } - onFetchTimelineSuccess(liftStatusList(response.body()), fetchEnd, pos); - } else { - onFetchTimelineFailure(new Exception(response.message()), fetchEnd, pos); - } - } - - @Override - public void onFailure(@NonNull Call> call, @NonNull Throwable t) { - onFetchTimelineFailure((Exception) t, fetchEnd, pos); - } - }; - - Call> listCall = getFetchCallByTimelineType(maxId, sinceId); - callList.add(listCall); - listCall.enqueue(callback); + getFetchCallByTimelineType(maxId, sinceId) + .observeOn(AndroidSchedulers.mainThread()) + .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY))) + .subscribe( + response -> { + if (response.isSuccessful()) { + @Nullable + String newNextId = extractNextId(response); + if (newNextId != null) { + // when we reach the bottom of the list, we won't have a new link. If + // we blindly write `null` here we will start loading from the top + // again. + nextId = newNextId; + } + onFetchTimelineSuccess(liftStatusList(response.body()), fetchEnd, pos); + } else { + onFetchTimelineFailure(new Exception(response.message()), fetchEnd, pos); + } + }, + err -> onFetchTimelineFailure(err, fetchEnd, pos) + ); } } @@ -1158,7 +1151,7 @@ public class TimelineFragment extends SFragment implements } } - private void onFetchTimelineFailure(Exception exception, FetchEnd fetchEnd, int position) { + private void onFetchTimelineFailure(Throwable throwable, FetchEnd fetchEnd, int position) { if (isAdded()) { swipeRefreshLayout.setRefreshing(false); topProgressBar.hide(); @@ -1177,7 +1170,7 @@ public class TimelineFragment extends SFragment implements } else if (this.statuses.isEmpty()) { swipeRefreshLayout.setEnabled(false); this.statusView.setVisibility(View.VISIBLE); - if (exception instanceof IOException) { + if (throwable instanceof IOException) { this.statusView.setup(R.drawable.elephant_offline, R.string.error_network, __ -> { this.progressBar.setVisibility(View.VISIBLE); this.onRefresh(); @@ -1192,7 +1185,7 @@ public class TimelineFragment extends SFragment implements } } - Log.e(TAG, "Fetch Failure: " + exception.getMessage()); + Log.e(TAG, "Fetch Failure: " + throwable.getMessage()); updateBottomLoadingState(fetchEnd); progressBar.setVisibility(View.GONE); } diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.kt index 86b3d09b..b25fec26 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.kt @@ -17,10 +17,11 @@ package com.keylesspalace.tusky.fragment import android.os.Bundle import android.text.TextUtils +import androidx.fragment.app.Fragment import com.keylesspalace.tusky.ViewMediaActivity import com.keylesspalace.tusky.entity.Attachment -abstract class ViewMediaFragment : BaseFragment() { +abstract class ViewMediaFragment : Fragment() { private var toolbarVisibiltyDisposable: Function0? = null abstract fun setupMediaView( diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java index bf28301f..5ce66eee 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java @@ -55,7 +55,6 @@ import com.keylesspalace.tusky.di.Injectable; import com.keylesspalace.tusky.entity.Filter; import com.keylesspalace.tusky.entity.Poll; import com.keylesspalace.tusky.entity.Status; -import com.keylesspalace.tusky.entity.StatusContext; import com.keylesspalace.tusky.interfaces.StatusActionListener; import com.keylesspalace.tusky.network.MastodonApi; import com.keylesspalace.tusky.settings.PrefKeys; @@ -75,9 +74,6 @@ import java.util.Locale; import javax.inject.Inject; import io.reactivex.android.schedulers.AndroidSchedulers; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; import static com.uber.autodispose.AutoDispose.autoDisposable; import static com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from; @@ -463,49 +459,32 @@ public final class ViewThreadFragment extends SFragment implements } private void sendStatusRequest(final String id) { - Call call = mastodonApi.status(id); - call.enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - if (response.isSuccessful()) { - int position = setStatus(response.body()); - recyclerView.scrollToPosition(position); - } else { - onThreadRequestFailure(id); - } - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - onThreadRequestFailure(id); - } - }); - callList.add(call); + mastodonApi.status(id) + .observeOn(AndroidSchedulers.mainThread()) + .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY))) + .subscribe( + status -> { + int position = setStatus(status); + recyclerView.scrollToPosition(position); + }, + throwable -> onThreadRequestFailure(id, throwable) + ); } private void sendThreadRequest(final String id) { - Call call = mastodonApi.statusContext(id); - call.enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - StatusContext context = response.body(); - if (response.isSuccessful() && context != null) { - swipeRefreshLayout.setRefreshing(false); - setContext(context.getAncestors(), context.getDescendants()); - } else { - onThreadRequestFailure(id); - } - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - onThreadRequestFailure(id); - } - }); - callList.add(call); + mastodonApi.statusContext(id) + .observeOn(AndroidSchedulers.mainThread()) + .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY))) + .subscribe( + context -> { + swipeRefreshLayout.setRefreshing(false); + setContext(context.getAncestors(), context.getDescendants()); + }, + throwable -> onThreadRequestFailure(id, throwable) + ); } - private void onThreadRequestFailure(final String id) { + private void onThreadRequestFailure(final String id, final Throwable throwable) { View view = getView(); swipeRefreshLayout.setRefreshing(false); if (view != null) { @@ -516,7 +495,7 @@ public final class ViewThreadFragment extends SFragment implements }) .show(); } else { - Log.e(TAG, "Couldn't display thread fetch error message"); + Log.e(TAG, "Network request failed", throwable); } } 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 58caec85..28ac77b6 100644 --- a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt +++ b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt @@ -56,14 +56,7 @@ interface MastodonApi { @Query("max_id") maxId: String?, @Query("since_id") sinceId: String?, @Query("limit") limit: Int? - ): Call> - - @GET("api/v1/timelines/home") - fun homeTimelineSingle( - @Query("max_id") maxId: String?, - @Query("since_id") sinceId: String?, - @Query("limit") limit: Int? - ): Single> + ): Single>> @GET("api/v1/timelines/public") fun publicTimeline( @@ -71,7 +64,7 @@ interface MastodonApi { @Query("max_id") maxId: String?, @Query("since_id") sinceId: String?, @Query("limit") limit: Int? - ): Call> + ): Single>> @GET("api/v1/timelines/tag/{hashtag}") fun hashtagTimeline( @@ -81,7 +74,7 @@ interface MastodonApi { @Query("max_id") maxId: String?, @Query("since_id") sinceId: String?, @Query("limit") limit: Int? - ): Call> + ): Single>> @GET("api/v1/timelines/list/{listId}") fun listTimeline( @@ -89,7 +82,7 @@ interface MastodonApi { @Query("max_id") maxId: String?, @Query("since_id") sinceId: String?, @Query("limit") limit: Int? - ): Call> + ): Single>> @GET("api/v1/notifications") fun notifications( @@ -97,7 +90,7 @@ interface MastodonApi { @Query("since_id") sinceId: String?, @Query("limit") limit: Int?, @Query("exclude_types[]") excludes: Set? - ): Call> + ): Single>> @GET("api/v1/markers") fun markersWithAuth( @@ -114,12 +107,7 @@ interface MastodonApi { ): Single> @POST("api/v1/notifications/clear") - fun clearNotifications(): Call - - @GET("api/v1/notifications/{id}") - fun notification( - @Path("id") notificationId: String - ): Call + fun clearNotifications(): Single @Multipart @POST("api/v1/media") @@ -146,17 +134,12 @@ interface MastodonApi { @GET("api/v1/statuses/{id}") fun status( @Path("id") statusId: String - ): Call - - @GET("api/v1/statuses/{id}") - fun statusSingle( - @Path("id") statusId: String ): Single @GET("api/v1/statuses/{id}/context") fun statusContext( @Path("id") statusId: String - ): Call + ): Single @GET("api/v1/statuses/{id}/reblogged_by") fun statusRebloggedBy( @@ -295,7 +278,7 @@ interface MastodonApi { @Query("exclude_replies") excludeReplies: Boolean?, @Query("only_media") onlyMedia: Boolean?, @Query("pinned") pinned: Boolean? - ): Call> + ): Single>> @GET("api/v1/accounts/{id}/followers") fun accountFollowers( @@ -398,14 +381,14 @@ interface MastodonApi { @Query("max_id") maxId: String?, @Query("since_id") sinceId: String?, @Query("limit") limit: Int? - ): Call> + ): Single>> @GET("api/v1/bookmarks") fun bookmarks( @Query("max_id") maxId: String?, @Query("since_id") sinceId: String?, @Query("limit") limit: Int? - ): Call> + ): Single>> @GET("api/v1/follow_requests") fun followRequests( @@ -415,20 +398,10 @@ interface MastodonApi { @POST("api/v1/follow_requests/{id}/authorize") fun authorizeFollowRequest( @Path("id") accountId: String - ): Call - - @POST("api/v1/follow_requests/{id}/reject") - fun rejectFollowRequest( - @Path("id") accountId: String - ): Call - - @POST("api/v1/follow_requests/{id}/authorize") - fun authorizeFollowRequestObservable( - @Path("id") accountId: String ): Single @POST("api/v1/follow_requests/{id}/reject") - fun rejectFollowRequestObservable( + fun rejectFollowRequest( @Path("id") accountId: String ): Single diff --git a/app/src/main/java/com/keylesspalace/tusky/repository/TimelineRepository.kt b/app/src/main/java/com/keylesspalace/tusky/repository/TimelineRepository.kt index 8aa3eb76..945c55d3 100644 --- a/app/src/main/java/com/keylesspalace/tusky/repository/TimelineRepository.kt +++ b/app/src/main/java/com/keylesspalace/tusky/repository/TimelineRepository.kt @@ -66,9 +66,9 @@ class TimelineRepositoryImpl( sinceIdMinusOne: String?, limit: Int, accountId: Long, requestMode: TimelineRequestMode ): Single> { - return mastodonApi.homeTimelineSingle(maxId, sinceIdMinusOne, limit + 1) - .map { statuses -> - this.saveStatusesToDb(accountId, statuses, maxId, sinceId) + return mastodonApi.homeTimeline(maxId, sinceIdMinusOne, limit + 1) + .map { response -> + this.saveStatusesToDb(accountId, response.body().orEmpty(), maxId, sinceId) } .flatMap { statuses -> this.addFromDbIfNeeded(accountId, statuses, maxId, sinceId, limit, requestMode) @@ -85,7 +85,7 @@ class TimelineRepositoryImpl( private fun addFromDbIfNeeded(accountId: Long, statuses: List>, maxId: String?, sinceId: String?, limit: Int, requestMode: TimelineRequestMode - ): Single>? { + ): Single> { return if (requestMode != NETWORK && statuses.size < 2) { val newMaxID = if (statuses.isEmpty()) { maxId diff --git a/app/src/test/java/com/keylesspalace/tusky/fragment/TimelineRepositoryTest.kt b/app/src/test/java/com/keylesspalace/tusky/fragment/TimelineRepositoryTest.kt index 16d0e271..7a7c3f7d 100644 --- a/app/src/test/java/com/keylesspalace/tusky/fragment/TimelineRepositoryTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/fragment/TimelineRepositoryTest.kt @@ -28,6 +28,7 @@ import org.mockito.ArgumentMatchers.* import org.mockito.Mock import org.mockito.MockitoAnnotations import org.robolectric.annotation.Config +import retrofit2.Response import java.util.* import java.util.concurrent.TimeUnit import kotlin.collections.ArrayList @@ -76,8 +77,8 @@ class TimelineRepositoryTest { makeStatus("3"), makeStatus("2") ) - whenever(mastodonApi.homeTimelineSingle(isNull(), isNull(), anyInt())) - .thenReturn(Single.just(statuses)) + whenever(mastodonApi.homeTimeline(isNull(), isNull(), anyInt())) + .thenReturn(Single.just(Response.success(statuses))) val result = subject.getStatuses(null, null, null, limit, TimelineRequestMode.NETWORK) .blockingGet() @@ -107,8 +108,8 @@ class TimelineRepositoryTest { ) val sinceId = "2" val sinceIdMinusOne = "1" - whenever(mastodonApi.homeTimelineSingle(null, sinceIdMinusOne, limit + 1)) - .thenReturn(Single.just(response)) + whenever(mastodonApi.homeTimeline(null, sinceIdMinusOne, limit + 1)) + .thenReturn(Single.just(Response.success(response))) val result = subject.getStatuses(null, sinceId, sinceIdMinusOne, limit, TimelineRequestMode.NETWORK) .blockingGet() @@ -141,8 +142,8 @@ class TimelineRepositoryTest { ) val sinceId = "2" val sinceIdMinusOne = "1" - whenever(mastodonApi.homeTimelineSingle(null, sinceIdMinusOne, limit + 1)) - .thenReturn(Single.just(response)) + whenever(mastodonApi.homeTimeline(null, sinceIdMinusOne, limit + 1)) + .thenReturn(Single.just(Response.success(response))) val result = subject.getStatuses(null, sinceId, sinceIdMinusOne, limit, TimelineRequestMode.NETWORK) .blockingGet() @@ -181,8 +182,8 @@ class TimelineRepositoryTest { val sinceId = "2" val sinceIdMinusOne = "1" val maxId = "3" - whenever(mastodonApi.homeTimelineSingle(maxId, sinceIdMinusOne, limit + 1)) - .thenReturn(Single.just(response)) + whenever(mastodonApi.homeTimeline(maxId, sinceIdMinusOne, limit + 1)) + .thenReturn(Single.just(Response.success(response))) val result = subject.getStatuses(maxId, sinceId, sinceIdMinusOne, limit, TimelineRequestMode.NETWORK) .blockingGet() @@ -224,8 +225,8 @@ class TimelineRepositoryTest { val sinceId = "2" val sinceIdMinusOne = "1" val maxId = "4" - whenever(mastodonApi.homeTimelineSingle(maxId, sinceIdMinusOne, limit + 1)) - .thenReturn(Single.just(response)) + whenever(mastodonApi.homeTimeline(maxId, sinceIdMinusOne, limit + 1)) + .thenReturn(Single.just(Response.success(response))) val result = subject.getStatuses(maxId, sinceId, sinceIdMinusOne, limit, TimelineRequestMode.NETWORK) .blockingGet() @@ -263,8 +264,8 @@ class TimelineRepositoryTest { dbResult.status = dbStatus.toEntity(account.id, gson) dbResult.account = status.account.toEntity(account.id, gson) - whenever(mastodonApi.homeTimelineSingle(any(), any(), any())) - .thenReturn(Single.just(listOf(status))) + whenever(mastodonApi.homeTimeline(any(), any(), any())) + .thenReturn(Single.just(Response.success((listOf(status))))) whenever(timelineDao.getStatusesForAccount(account.id, status.id, null, 30)) .thenReturn(Single.just(listOf(dbResult))) val result = subject.getStatuses(null, null, null, limit, TimelineRequestMode.ANY) @@ -281,8 +282,8 @@ class TimelineRepositoryTest { val dbResult2 = TimelineStatusWithAccount() dbResult2.status = Placeholder("1").toEntity(account.id) - whenever(mastodonApi.homeTimelineSingle(any(), any(), any())) - .thenReturn(Single.just(listOf(status))) + whenever(mastodonApi.homeTimeline(any(), any(), any())) + .thenReturn(Single.just(Response.success(listOf(status)))) whenever(timelineDao.getStatusesForAccount(account.id, status.id, null, 30)) .thenReturn(Single.just(listOf(dbResult, dbResult2))) val result = subject.getStatuses(null, null, null, limit, TimelineRequestMode.ANY) From dc5f0c87a230b7be948191917617fd9b6a2938a9 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Sun, 31 Jan 2021 19:35:39 +0100 Subject: [PATCH 37/81] remove let's encrypt root cert again (#2056) * remove let's encrypt root cert again * fix user-agent example --- .../keylesspalace/tusky/di/NetworkModule.kt | 41 ++++++- .../keylesspalace/tusky/util/OkHttpUtils.kt | 115 ------------------ 2 files changed, 38 insertions(+), 118 deletions(-) delete mode 100644 app/src/main/java/com/keylesspalace/tusky/util/OkHttpUtils.kt 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 ca60bac8..fb232a64 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt @@ -16,6 +16,8 @@ package com.keylesspalace.tusky.di import android.content.Context +import android.content.SharedPreferences +import android.os.Build import android.text.Spanned import com.google.gson.Gson import com.google.gson.GsonBuilder @@ -24,15 +26,20 @@ 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.okhttpClient +import com.keylesspalace.tusky.util.getNonNullString import dagger.Module import dagger.Provides +import okhttp3.Cache +import okhttp3.OkHttp import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory import retrofit2.converter.gson.GsonConverterFactory import retrofit2.create +import java.net.InetSocketAddress +import java.net.Proxy +import java.util.concurrent.TimeUnit import javax.inject.Singleton /** @@ -54,9 +61,37 @@ class NetworkModule { @Singleton fun providesHttpClient( accountManager: AccountManager, - context: Context + context: Context, + preferences: SharedPreferences ): OkHttpClient { - return okhttpClient(context) + val httpProxyEnabled = preferences.getBoolean("httpProxyEnabled", false) + val httpServer = preferences.getNonNullString("httpProxyServer", "") + val httpPort = preferences.getNonNullString("httpProxyPort", "-1").toIntOrNull() ?: -1 + val cacheSize = 25 * 1024 * 1024L // 25 MiB + val builder = OkHttpClient.Builder() + .addInterceptor { chain -> + /** + * Add a custom User-Agent that contains Tusky, Android and OkHttp Version to all requests + * Example: + * User-Agent: Tusky/1.1.2 Android/5.0.2 OkHttp/4.9.0 + * */ + val requestWithUserAgent = chain.request().newBuilder() + .header( + "User-Agent", + "Tusky/${BuildConfig.VERSION_NAME} Android/${Build.VERSION.RELEASE} OkHttp/${OkHttp.VERSION}" + ) + .build() + chain.proceed(requestWithUserAgent) + } + .readTimeout(30, TimeUnit.SECONDS) + .writeTimeout(30, TimeUnit.SECONDS) + .cache(Cache(context.cacheDir, cacheSize)) + + if (httpProxyEnabled && httpServer.isNotEmpty() && httpPort > 0 && httpPort < 65535) { + val address = InetSocketAddress.createUnresolved(httpServer, httpPort) + builder.proxy(Proxy(Proxy.Type.HTTP, address)) + } + return builder .apply { addInterceptor(InstanceSwitchAuthInterceptor(accountManager)) if (BuildConfig.DEBUG) { diff --git a/app/src/main/java/com/keylesspalace/tusky/util/OkHttpUtils.kt b/app/src/main/java/com/keylesspalace/tusky/util/OkHttpUtils.kt deleted file mode 100644 index 3e1b89c6..00000000 --- a/app/src/main/java/com/keylesspalace/tusky/util/OkHttpUtils.kt +++ /dev/null @@ -1,115 +0,0 @@ -/* Copyright 2020 Tusky Contributors - * - * This file is part of Tusky. - * - * Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU - * Lesser 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 Lesser - * General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with Tusky. If - * not, see . */ - -package com.keylesspalace.tusky.util - -import android.content.Context -import android.os.Build -import androidx.preference.PreferenceManager -import com.keylesspalace.tusky.BuildConfig -import okhttp3.Cache -import okhttp3.OkHttp -import okhttp3.OkHttpClient -import okhttp3.tls.HandshakeCertificates -import java.io.ByteArrayInputStream -import java.net.InetSocketAddress -import java.net.Proxy -import java.security.cert.CertificateFactory -import java.security.cert.X509Certificate -import java.util.concurrent.TimeUnit - -fun okhttpClient(context: Context): OkHttpClient.Builder { - val preferences = PreferenceManager.getDefaultSharedPreferences(context) - - val httpProxyEnabled = preferences.getBoolean("httpProxyEnabled", false) - val httpServer = preferences.getNonNullString("httpProxyServer", "") - val httpPort = preferences.getNonNullString("httpProxyPort", "-1").toIntOrNull() ?: -1 - - val cacheSize = 25 * 1024 * 1024 // 25 MiB - val builder = OkHttpClient.Builder() - .addInterceptor { chain -> - /** - * Add a custom User-Agent that contains Tusky, Android and Okhttp Version to all requests - * Example: - * User-Agent: Tusky/1.1.2 Android/5.0.2 - * */ - val requestWithUserAgent = chain.request().newBuilder() - .header( - "User-Agent", - "Tusky/${BuildConfig.VERSION_NAME} Android/${Build.VERSION.RELEASE} OkHttp/${OkHttp.VERSION}" - ) - .build() - chain.proceed(requestWithUserAgent) - } - .readTimeout(30, TimeUnit.SECONDS) - .writeTimeout(30, TimeUnit.SECONDS) - .cache(Cache(context.cacheDir, cacheSize.toLong())) - - if (httpProxyEnabled && httpServer.isNotEmpty() && httpPort > 0 && httpPort < 65535) { - val address = InetSocketAddress.createUnresolved(httpServer, httpPort) - builder.proxy(Proxy(Proxy.Type.HTTP, address)) - } - - // trust the new Let's Encrypt root certificate that is not available on Android < 7.1.1 - // new cert https://letsencrypt.org/certs/isrgrootx1.pem - // see https://letsencrypt.org/2020/11/06/own-two-feet.html - // see https://stackoverflow.com/questions/64844311/certpathvalidatorexception-connecting-to-a-lets-encrypt-host-on-android-m-or-ea - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { - val isgCert = """ - -----BEGIN CERTIFICATE----- - MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw - TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh - cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 - WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu - ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY - MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc - h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ - 0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U - A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW - T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH - B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC - B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv - KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn - OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn - jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw - qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI - rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV - HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq - hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL - ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ - 3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK - NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 - ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur - TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC - jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc - oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq - 4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA - mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d - emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= - -----END CERTIFICATE----- - """.trimIndent() - val cf = CertificateFactory.getInstance("X.509") - val isgCertificate = cf.generateCertificate(ByteArrayInputStream(isgCert.toByteArray(charset("UTF-8")))) - val certificates = HandshakeCertificates.Builder() - .addTrustedCertificate(isgCertificate as X509Certificate) - .addPlatformTrustedCertificates() - .build() - builder.sslSocketFactory( - certificates.sslSocketFactory(), - certificates.trustManager - ) - } - return builder -} From 3e135b04805f43993a624358fbb4a209e80d7f45 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Sun, 31 Jan 2021 19:35:53 +0100 Subject: [PATCH 38/81] upgrade gradle to 6.8.1 (#2057) --- gradle.properties | 2 ++ gradle/wrapper/gradle-wrapper.jar | Bin 58910 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 2 +- gradlew.bat | 21 +++------------------ 5 files changed, 7 insertions(+), 20 deletions(-) diff --git a/gradle.properties b/gradle.properties index 8144ece0..bada7909 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,6 +14,8 @@ org.gradle.jvmargs=-Xmx4096m # use parallel execution org.gradle.parallel=true +# enable file system watching +org.gradle.vfs.watch=true android.enableR8.fullMode=true android.useAndroidX=true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 62d4c053550b91381bbd28b1afc82d634bf73a8a..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f 100644 GIT binary patch delta 6656 zcmY+Ibx_pN*Z*PZ4(U#j1qtbvrOTyO8fghZ8kYJfEe%U|$dV!@ASKczEZq$fg48M@ z;LnHO_j#Uq?%bL4dY^md%$$4Y+&@nKC|1uHR&59YNhubGh72|a#ylPdh9V+akp|I; zPk^W-a00GrFMkz_NSADdv2G2-i6rb=cB_@WnG(**4ZO$=96R=t|NZ@|0_z&q3GwO^ ziUFcuj$a9QaZ3j?xt`5#q`sT-ufrtBP0nt3IA&dr*+VCsBzBVW?vZ6eZr0oD%t33z zm~-5IVsjy(F>;S~Pm@bxX85>Z*@(QL6i3JQc?1ryQFcC@X^2^mZWhFv|v? z49>l|nA&XNQ6#OvccUTyBMB*WO#NA;FW5|eE_K6dtVYP2G?uUZ09!`Iq1IF2gA(aS zLu@G^cQJmh=x?-YsYa@E6QnE5+1@ds&0f#OQRDl^GnIT_m84G5XY%W z;Ck6bk^Oeu*Ma-XmxI5GjqzWNbJMsQF4)WfMZEA{oxW0E32e)*JfG}3otPishIQBw zkBe6N#4pKPN>q1R6G1@5&(u#5yPEToMBB6_oEK|q z@(i5j!?;NNCv~=HvW%zF&1yWBq(nJa_#``G&SRmQvE|jePUPs{J!$TacM|e}Fsceb zx+76|mDp6@w>)^DIl{8?)6XYNRU|2plG8Jy&7(^9SdOWNKKJK&>0!z6XiN4J*Jkao z=E1y5x-XDC==Ub+8fLb#OW&{2ww{h^xlJFYAMOUd)}Xg@j?ak{7Kno6?9S~F?|6Df zHo|ijXX~`Sp;Vf!nR;m%vUhq>zvlRXsL0u*Tt?F#yR}3tF0#of{(UjitqST|!{aBA zicWh+URU}Jnc*sg9iMkf0pggpd?3TI*C-q$2QOdCC7rV+CHBmjS3O%a3VeZ$ZSs5ubJuJp%e%$LHgrj0niYjX;4kt z&2~j%@q3MO)-QGCA{>o%eZu){ou^MgC6~Z8Y=tc!qF=|TOlG3wJXbaLYr-;$Ch=2J z_UcE59Xzq&h0LsjLrcZrQSa}#=0~Lk|4?e4M z6d;v->NCC1oMti)RRc`Ys0?JXQjsZ@VdCy%Z)TptCrI>0Tte$pR!@yJesoU2dtyuW z7iFsE8)CkbiJP+OP28;(%?!9WddQZcAid@R@`*e%3W65$g9ee`zvwb(VPO+uVBq6p z{QDR%CR(2z@?&9Obm3xPi2lzvfip`7q`_7UDD|lRS}4=bsl3xQIOi0@GSvMuDQX}* z4B^(DI<${qUhcLqO`itJU;e<%%iS+R3I^_xIV1O%sp*x~;-dn` zt$8>RnSUh#rU3{-47067W^WNwTdq-t$-U>Hj%r!GD!gLa;kV zW5g6pCqV+!q8LgrI49(}fIc5K_`FLV4_E#XZ6{<>w8wzc%V9k!!Byg5-0WY+J?1*z%9~Aj4WQr1Jsn2(G!U8fFpi(wsy@JLg^d+IB0kl89 z0@Ssqf!L9JjYKK$J=978+NO*5^C)GPH2a%4hm$HROjM|N3g9ch9kDLh*nlwqy{mVM z`P(l#>3NnK%#O8tSb(VmZrG+`dRD#=Cc1P%(y5S?*Hj5E{vg&Eiw!YV>S#7_WRDVoFxT5m=gFi4)}y5V%KT8!xbsH_rmR& zsmM?%J}K$1l8d?2+m(}2c}-G`x>CY%Y&QBJRC$sKM}zN<9{IlF@yJEG<^0={$+`Hc zDodJ)gCADJ_bD#am(c2ojXKb|j+ENJ#58PAA&pZXufrFzBwnuuo+khfMgd!DMlU#v z9|JelQO~E2;d^w!RZJbt%IANIudpKSP)cssoWhq)>({nvcfCr0=9=FAIMuZm8Eo=} z|DND}8_PB5HqG(QwDvaM@orYBZ9kCkHV*rxKTy>q7n~0emErUwLbhq;VN<2nKT&*a2Ajz z;lKBzU2i8KLV`d)Y&ae)!HcGk$dO}Or%8KF@kE@jU1h@zwpw{6p4ME|uC$Za-ERR2 ztQvL&uOZLe(k{w_+J^ng+l}~N8MP>F1Z$fLu}D-WWaeu#XduP@#8JpmH(X>rIL)k3 zyXNyTIB1(IH%S&pQ{rWaTVfB$~-;RnlY z^(y7mR>@=brI>!TrA)BQsQ={b*6$=1Eqbuu6IdhJ&$YD$08AwtNr9*J?%-WT<;O1< zPl1<@yeqfZ>@s4azqTf<=I4(kU^+^Qkstm%WM-0_VLm({jFc8`5Df2Q1Y9zMZu0^! zsO_yh2Sz9K>Jq6fkYbBZocEJ6C!SdEzYDkiEtNJs{?!tA#e|oiN+VaaAobwKef_kUup&4scD?1+}Q8)DaekkMYn-FOS{J%NY za^mmJ^n`t*1p@hF*gl#L+5wr40*(ub4J#L|@oCl~@|4UvCjHBYDQv&S zhyGMAkRO^tF_dyi&XM)4mQ;k>kj?RgRo@-?==oD+ns*>bf@&fPXF|4U0&ib2 zo~1ZdmCPWf!W9#sGP@9X$;Rc`tjbz^&JY}z{}j9bl?;VC{x)TfQH$D^WowKL&4Zx@ zdSn+QV7H(e0xRfN6aBfH)Q=@weoD?dvu6^ZS)zqb>GwMmIuS8zJfaMUQx9>%k~w34 z3}_B2Jj~u=SnJ~vZPj*)UoDi_FtT=UAb#J^b4B%R6z3H%cj-1OCjU5F$ky>By1zsg z>2A0ccp29(Y<;my|J_g-r{1I@+*O$>!R3`_sFNP4e}LD1e1mM&SA`;;TR0I`_hESV zh4U*9ecK$0=lYk`{SR_cm$}iS*?yQR(}T-5ub?Wn^#RTe*^1~ya%`!xWq-F*WH@%nnZTNREA z3eUX2uM9b_w!Zo$nVTotEtzuL(88N)H~v_G=89|(@IFz~Wq6ME);z(!2^PkR2B&kE zxR)xV8PE|Hszyjp#jNf=ZIQ7JR~4Ls#Vd@mPF(7R5VO$akUq8JM+sn>ZVg(lJZ)5qjqdw(*7tuwjY#0tx+|!sTz9yV~%HOdrb#!5w9>*0LrCS z%wF$Yc6~hqVQZzoC^D<(-h0aOtk}kn<<*xF61HQr<5}efY{zXXA+PaJG7vT&{Oz(@Uu!V#Fp9%Ht!~@;6AcD z$lvlPu&yd(YnAHfpN51*)JN0aYw9gGk{NE7!Oqu4rBp}F30669;{zcH-a7w9KSpDQPIE_f9T zit? zJSjTKWbe{f{9BmSDAFO1(K0oqB4578tU0(oRBE^28X>xDA!1C&VJEiYak4_ZTM*7M`hv_ zw3;2ndv3X$zT!wa7TrId{gNE`Vxf}j5wsyX+;Kn<^$EJT`NzznjyYx=pYMkZjizEU zb;Gg8Pl_pqxg)9P)C)Hxh_-mQ;u-I_Ol>d^>q08zFF!>Z3j1-HmuME_TGZ*Ev;O0O z%e(edJfV<6t3&FKwtInnj9EeQhq9;o5oLJoiKwWF5bP2~Feh#P4oN()JT0pdq!9x* ze3D-1%AV#{G=Op$6q?*Z>s{qFn}cl@9#m@DK_Bs@fdwSN`Qe18_WnveRB583mdMG- z?<3pJC!YljOnO8=M=|Cg)jw;4>4sna`uI>Kh&F20jNOk9HX&}Ry|mHJ+?emHnbYLJ zwfkx@slh31+3nq-9G5FVDQBHWWY}&hJ-fpDf!lQdmw8dlTt#=)20X74S>c&kR(?PT zBg)Y%)q&|hW1K;`nJPAGF*c3{3`FvrhD9=Ld{3M*K&5$jRhXNsq$0CLXINax1AmXX ziF39vkNtcK6i^+G^AEY!WalGazOQ$_#tx?BQ{YY$&V&42sICVl8@AI6yv;sGnT;@f zL=}rZcJqNwrEEA=GDdEe8Z=f9>^?($oS8xGdFf1eUWTYtZF<3tu2V%noPBnd=thZ+ zO&xoc?jvXG7Xt!RTw#5VN50UjgqSntw9Y35*~pxz=8OzkXg{@S2J%+{l3Q>B_qbnl z20Deb7JM&ZSp`%X>xWpb>FF8q7Nq&4#a1}A-(-!aMDmVbz05D!NpUzVe{~72h%cOh zwQFNai2a$K|hFgDk(oPF_tuf{BV!=m0*xqSzGAJ(~XUh8rk#{YOg0ReK>4eJl z;-~u5v$}DM)#vER>F)-}y(X6rGkp<{AkiPM7rFgAV^)FUX8XmCKKaWlS4;MSEagj$ z#pvH`vLX1q{&eOm>htnk4hmv=_)ao!MCp}9ql5yfre&Py!~hBAGNBa}PH&J8K=~<% z&?!J-QaH|0bq_uo6rt*r-M>d7jm1cbW^T>s)S?L{n8v`^?VIPA+qi^6e@cM|5boqEO!p1e|_{7U3Yl6K?0xMN1bbjf0@$TE-T))w> zFe?E?g$PUT-)AJ(PS^By^D^Ed!K5iv$*_eW~VA(I3~UMy*ZcgVu0$XZC*_0PgDmUL)qTCn927LD~p$yXR_GCJ&iQ; z4*`%l-dC5pALH!y*nmhdHRh02QjW1vZL4ySucz*w3f|#`=u@@YvMV1?i!&DIa2+S< z8z!gvN3FV4I;%fl;ruFeV{jKjI~?GlgkmGBuJ<7vY|l3xMOc?S@Q#C(zo*m&JLrjT2rU9PYOniB8O~yO5<1CCcQz# z17B2m1Z{R!Y)UO#CU-Y&mOlv4*Gz%rC_YkRcO)jTUEWHDvv!GWmEihE>OKPx1J?Av z8J{-#7NsT>>R#*7**=QL)1@IR77G9JGZZiVt!=jD+i(oRV;I`JkiTSZkAXuHm-VG1 z+2-LD!!2dNEk@1@Rp|C$MD9mH^)H*G*wI(i*Rc6Vvdik+BDycYQ*=0JA3dxxha|Zg zCIW1Ye-DdpMGTEwbA^6hVC<(@0FL4dkDOYcxxC5c%MJQ^)zpA%>>~Q|Y=@)XW!px; z_Fx+xOo7>sz4QX|Ef~igE+uFnzFWP<-#||*V0`0p7E*+n5+awuOWmvR{-M*chIXgo zYiZvQMond#{F8+4Zh_;>MsaZUuhp=onH@P!7W>sq|CWv|u}Wg0vo&f4UtmLzhCwwu zJaR=IO;sQxS}h(K>9VZjnED+>9rGgB3ks+AwTy_EYH{oc)mo`451n&YH%A1@WC{;1 z=fB6n zIYp46_&u`COM&Di?$P}pPAlAF*Ss<)2Xc?=@_2|EMO?(A1u!Vc=-%bDAP#zDiYQvJ z0}+}3GaLxsMIlh6?f=iRs0K=RyvMOcWl*xqe-IBLv?K{S^hP)@K|$I+h_)pdD9r~! zxhw2u66+F(E`&6hY}B_qe>wil|#*0R0B;<@E?L zVrhXKfwRg0l8r>LuNs1QqW&39ME0sOXe8zycivGVqUOjEWpU)h|9fwp@d(8=M-WxY zeazSz6x5e`k821fgylLIbdqx~Kdh^Oj`Q!4vc*Km)^Tr-qRxPHozdvvU^#xNsKVr6aw8={70&S4y*5xeoF@Q^y596*09`XF56-N z1=Rm5?-An178o?$ix}y7gizQ9gEmGHF5AW+92DYaOcwEHnjAr~!vI>CK%h`E_tO8L Yte!%o?r4GTrVtxD61Ym!|5fq-1K$0e!T1w z1SC8j)_dObefzK9b=~*c&wBRW>;B{VGKiBofK!FMN5oJBE0V;;!kWUz!jc1W?5KdY zyZ3mCBHprpchz-9{ASiJJh&&h1|4rdw6wxD2+9= z#6#}Uq8&^1F3wgvGFoNDo?bIeEQXpcuAR0-+w$JWoK-@yUal1M&~W_O)r+Rx;{@hWH5n^oQWR36GMYBDDZyPK4L@WVjRrF+XlSzi4X4!_!U%Uujl6LHQ#|l(sUU%{ zefYd8jnVYP91K}Qn-OmmSLYFK1h~_}RPS~>+Xdz%dpvpJ{ll!IKX=JN99qowqslbO zV3DmqPZ}6>KB!9>jEObpi$u5oGPfO3O5!o3N2Mn`ozpje<}1I1H)m2rJDcB7AwXc6 z6j)tnPiql7#)r+b+p9?MVahp&=qJ^$oG+a^C*);FoJ!+V*^W+|2Olx5{*&$bXth)U zejc7mU6cBp?^Rj|dd{GL-0eHRTBi6_yJ&GLP5kIncv^z{?=0AVy^5{S8_n=rtua!J zFGY=A(yV^ZhB}1J_y(F`3QTu+zkHlw;1GiFeP&pw0N1k%NShHlO(4W+(!wy5phcg4 zA-|}(lE_1@@e6y`veg;v7m;q%(PFG&K3#}eRhJioXUU0jg_8{kn$;KVwf;zpL2X_( zC*_R#5*PaBaY73(x*oZ}oE#HPLJQRQ7brNK=v!lsu==lSG1(&q>F)`adBT~d*lMS| z%!%7(p~<7kWNmpZ5-N31*e=8`kih|g5lVrI%2wnLF-2D+G4k6@FrYsJ_80AJ}KMRi>) z-kIeHp{maorNWkF81v0FKgB==_6blyaF$5GaW)B!i4v*jNk6r)vU6?G$0pV8(Y+UK z5lgRVt%;N_gWp)^osv=h+^07UY6+$4^#t=M3>0i0`{`aEkFLL#a)93uXhYO+aKTtu zckg2T9S&GKNtZmdAS^8PzvDva-%-K&g9eqPXQ4$dM^inr@6Zl z{!Cq&C_+V;g*{>!0cZP}?ogDb$#ZS=n@NHE{>k@84lOkl&$Bt2NF)W%GClViJq14_ zQIfa^q+0aq){}CO8j%g%R9|;G0uJuND*HO$2i&U_uW_a5xJ33~(Vy?;%6_(2_Cuq1 zLhThN@xH7-BaNtkKTn^taQHrs$<<)euc6z(dhps>SM;^Wx=7;O&IfNVJq3wk4<1VS z-`*7W4DR_i^W4=dRh>AXi~J$K>`UqP>CKVVH&+T(ODhRJZO7DScU$F7D)di-%^8?O z6)Ux`zdrVOe1GNkPo0FgrrxSu1AGQkJe@pqu}8LkBDm+V!N_1l}`tjLW8${rgDLv3m@E*#zappt-Mm zSC<$o+6UO~w0C=(0$&*y**@nKe_Q{|eAuD!(0YL0_a{z%+sdfSyP={Nyd$re6Rzbp zvsgTY7~VflX0^Vf7qqomYZ_$ryrFVV2$sFyzw2r%Q8*uYDA+)iQdfKms_5(>!s#!( z!P5S(N0i9CKQKaqg(U%Gk#V3*?)lO6dLv`8KB~F<-%VhbtL8Rl>mEz+PN=qx&t*|= zQHV=qG)YKlPk4iCyWIUGjC?kpeA>hIBK*A?B0)rB=RqAal#D%1C9yVQwBcz${#Jb5 zR{TRmMrOrJsLc&6x9qDo@FJ^=do_Y?3oU0G^nV5_EU&+DS+VA7Tp{^TAF>yZbyM3c zf*1CqHY9T|aL_lyY7c)i!_MtGPA!sdy3|mrsKVj1mi&>dms@-ozSa}OZ?2I*tAndg z@S7er$t^d^-;!wLQbG60nWd@1pQVD7tw-G_B#OscoYyremiZ_hj8*sXqQdchuD^!R zpXGuSj5psk+jR>3rWu3^`17>j&*^9^rWbszP=Mf@5KIEj%b=z98v=Ymp%$FYt>%Ld zm8})EDbNOJu9n)gwhz_RS``#Ag)fr)3<*?(!9O~mTQWeh;8c;0@o=iBLQNqx3d_2#W7S9#FXzr6VXfs>4 z;QXw}-STvK9_-7H=uqgal2{GkbjVLN+=D5ddd)4^WvX;(NYA*X*(JxTdiUzqVJopd zQg#~psX4o<)cF>r=rxP`(Xsf<+HG-pf&7aFPL8z|-&B*P?Vmsu5d>Nlg^2$WRY!S@#`g2{81;(1w#o5HsvN}5pFZi});>|VK^kL{Zkx~wgn ztlZp;HW`H8(GdRfIwc~?#N6}o#h158ohI*GIsK%56I_9sf2k_K@4vD!l{(dX9E7PJ;w>$|Y;-VBJSO4@){07bo-89^LZ9g<<%;dOl zyIq{s8`8Ltp*GDwu(l_Z$6sA2nam$BM$Q~6TpZg)w2TtW?G5whV(lRwaf$6EU86is zBP9Rs&vS_~sk?Nn_b}^HkM8LiO@>J}=g(T4hLmvH@5Jj#2aHa~K)lD9VB0k>$V2BP zgh;(=y9Op(KQ=H5vj+%qs>?s4tYN~-Q|fyQePA)s?HrF~;l!+@t8VMzqUpqMLudFT z)=o~s!MM4XkgbetIsODwtQ=FF$IcIp&!pjh6Q6{tL+l*7GQ%8Wsg(tC#qU3oW$~n) zL=>XIxI}Hi7HS0F_mmi+(c%1HDuKiWm>|6Xa}nW7ei55ggru9)xjBvC#JcEIN*#cp zv*ACvr=HTC?dX9NNo9Yhulu_gX5Z~}QQ2&QZ&C77{(>Y3_ z6j5Z1Uc5FtPEpS_31HsgmSLHZijGb_p$WlRJ1p^_1!ZLP8kr6OtCEK7Qh267o$H>e zf<4cNGQRk{g5h$XfvTFQ@`qm@iju83-~}ebAYpZryARHVR$AEt3229U{y@Fp4 z-8FBBtGG&(hTyUdx5ZOfiz`c=<0F%+w|Fl=rWk{K7>70k04SN?RU(^mrKSeKDqA!K^Hsv8C?#ioj4@WUL zC*?{hTai6q0%_oBTqDHygp_Kl;({sAScYQIwMDM1U>{x0ww zve?_}E;DG?+|zsUrsph5X_G7l#Y~vqkq3@NNDabbw7|`eJBmn`Qrlr%?`va=mm$Mc{+FBbQbogAZ6{MuzT|P%QZZotd21eb1hfj|;GYAX&>bx#D5EB+=XMj2XJkpnyMUykaVo) zj3ZLqEl1&)Rturc8m@+uUuD^vaNaSxGwP4dq0-OSb~62lPv8E_K4usLvG{Qg zdR%z8dd2H!{JaT|X_bfm{##*W$YM;_J8Y8&Z)*ImOAf4+| zEyi)qK%Ld1bHuqD+}-WiCnjszDeC-%8g+8JRpG1bOc!xUGB?@?6f~FTrI%U#5R~YF z%t5(S2Q>?0`(XNHa8xKdTEZ~Z4SJOheit#ldfdg63}#W6j8kO;SjQD`vftxS+#x1B zYu|5szEvkyz|}|B3x|DNlyi$;+n+cW$Hu+?)=X1!sa%{H-^;oBO9XACZJ}wkQ!sTa zQ#J3h|HX{{&WwIG3h7d6aWktuJaO)ie6&=KJBoX@w(rBWfin`*a6OmCC5M0HzL(gv zY<*e4hmW>SWVhxk-`UGOAbD%Hk+uu<^7zJ_ytVXamfqCd0$g+W08>?QAB}Cv{b}eM z@X}ILg+uT%>-6`A25p@uhS3%;u>ccSq}8|H_^o&`nBT5S0y z;2H0I^(4MO*S+(4l$gULc4KSeKvidto5Nl0P|%9CqQ*ikY!w_GUlo}sb9HYB=L^oFpJ zfTQskXW!LFVnUo4(OHPDaZSf3zB|3{RGu1>ueE$(+dr?tT zp!SGlqDU8vu{5xLWSvj+j$arHglg54#Lx&TvuO3LIIU>hF9Uoj&=-b*Q?uYr`#V?xz?2 zhirZrv^eA{k%{hFh%9LYVXEYWd5#PuUd1QqaqB*J!CMXEM>fEB$@#1>mtB`Bfil}t zhhTIObqh5HRvT+4q_Do$Q*Jika?qV=Np-DtPkU z(KoXyWLfPwr@UY1)hBAvR3nCBZgd|CevTG?H~HqDF}dzy%2sd2`f{^CBbTk*^K~RO zN~O0+2EjAJlywF%SjgYz810l&G5AqzI<=Ber{912^PpSPRJl3dm8W@dKHL}7_@k3)Y!SXYkyxQy>Q4I2o zr`ev7fLF$1t96h|sH<-#*YzGD-b^3$_!#wsh(Yw;)b@udLz9mm`mFYh z1Zz24KIQJ(*_-E0(3&1InqG;U?wF)GYd>DFo(em`#|UaaYmkA9;GTX7b?0@C@QkTVpGD#mf$dQoRNV=n{^Zi_W*ps;3?^$s`0;ER7;==~OmQ~9 zS5P=FjxE5%|;xq6h4@!_h?@|aK&FYI2IT(OHXv2%1 zWEo-v!L7x^YT(xLVHlpJttcwaF@1Y;-S*q3CRa!g7xdzl|Jan>2#dI0`LKl!T1GMk zRKe4|bQO&ET}Z^Aiym*HII>cSxIzl|F~JEUGxz;+DB=8fxXhnBI4R12q6ews$lA`Jfi}r@A@-)6TOAUMNYFYJ zZ-Zd?lxFTyjN3mXnL!%#>Z%$0gJ4*9g;e;@zSmQ{eGGDaRRNM3s@6!;hYuVc=c+3B z=qzNNS~n^EsJU4aOGE|mdy={C^lPKEfPL-IJAsTpQsDgZ@~s+eHZYmp9yb=YW_4r?lqQaYZQ`nau){W`LY#P)>i zq^wHEuOYs#FlPZeMuT@Etb@~A6feCebq`miJE3w+gAL%bVF_s*5e*@)?xmKSo%I3? zLELHVdWia$}~s6 zr!^LfxSSB4Td&9iTXrzQpl5ZDo#SdmNr;23QsPHQ!x!UT9xtb!Ycz^JF8x)%cFOXK z^EXw%dRz_VD}7?RU^4{)1+xFO=z!EI8IUa3U*rag=1BpHX$Xi<__kSbS{y_xa*MJv z_`thq0Z^sPzjAk48ssDQj}!$N8Q$XC84(bU$t_Bm69Jf+C!h_}ep zwzpQj9sRA94<{x3{~z&ix-DwX;RAzka)4-#6ZHJqKh|SVuO|>Yrv+m30+!|sK<-|E z=)5E->#y<_1V|T1f%Af!ZYqXg}`O zI$qKOWdnclF`%_Z`WGOe{`A`l-#a?s=Q1a#@BOWmExH2;Wl`OB!B-%lq3nO{4=WO& z#k_x|N&(qzm*6S{G*|GCegF2N2ulC+(58z2DG~yUs}i8zvRf&$CJCaexJ6Xu!`qz( z)*v8*kAE#D0KCo*s{8^Rbg=`*E2MzeIt0|x55%n-gO&yX#$l=3W7-_~&(G8j1E(XB hw}tl`5K!1C(72%nnjQrp<7@!WCh47rWB+@R{{wClNUHz< diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 33682bbb..1c4bcc29 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index fbd7c515..4f906e0c 100755 --- a/gradlew +++ b/gradlew @@ -130,7 +130,7 @@ fi if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath diff --git a/gradlew.bat b/gradlew.bat index a9f778a7..ac1b06f9 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -54,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -64,21 +64,6 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line @@ -86,7 +71,7 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell From 352cb863bc04915ac47efa8605dec675b5e70da0 Mon Sep 17 00:00:00 2001 From: Ho Nhat Duy Date: Sun, 31 Jan 2021 17:57:54 +0000 Subject: [PATCH 39/81] Translated using Weblate (Vietnamese) Currently translated at 100.0% (456 of 456 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/ Translated using Weblate (Vietnamese) Currently translated at 100.0% (456 of 456 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/ --- app/src/main/res/values-vi/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 47b8007f..d0891bb7 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -420,7 +420,7 @@ Để sau Bạn cần khởi động lại Tusky để áp dụng các thiết lập Yêu cầu khởi động lại ứng dụng - Mở tút + Xem tút Mở rộng/Thu gọn toàn bộ tút Đang tìm kiếm… Bạn cần tải về bộ emoji này trước @@ -458,7 +458,7 @@ Đã lưu! Thêm ghi chú Chưa có thông báo. - Tin tức + Có gì mới\? Ẩn số liệu trên trang cá nhân Ẩn tương tác trên tút Hạn chế thông báo trên bảng tin From 7b1baf0e993ac0f212cea86c5a35b8a5355a0bf8 Mon Sep 17 00:00:00 2001 From: Daniele Lira Mereb Date: Sun, 31 Jan 2021 17:57:54 +0000 Subject: [PATCH 40/81] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (456 of 456 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/pt_BR/ Translated using Weblate (Spanish) Currently translated at 100.0% (456 of 456 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/es/ --- app/src/main/res/values-es/strings.xml | 16 +++++----- app/src/main/res/values-pt-rBR/strings.xml | 35 +++++++++++++++++++++- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 3bd9e38a..c96229a4 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -479,7 +479,7 @@ Tu nota privada acerca de esta cuenta No hay anuncios. Anuncios - % recién publicado + %s recién publicado No puedes cargar más de %1$d archivos adjuntos multimedia. Esconder las estadísticas cuantitativas de los perfiles Esconder las estadísticas cuantitativas de las publicaciones @@ -488,13 +488,13 @@ Notificaciones cuando alguien al que estoy suscrito publicó un nuevo toot Nuevos toots alguien al que estoy suscrito publicó un nuevo toot - Alguna información que podría afectar tu bienestar mental va a ser oculta. Esto incluye: -\n -\n- Favoritos/impulsar/Notificaciones de Follow -\n- Favoritos/Conteo de Impulsos en toots -\n- Seguidor/Publicar estadísticas en perfiles -\n -\nLas notificaciones Push no serán afectadas, pero puedes revisar manualmente tus referencias de notificaciones. + Algunas informaciones que podríam afectar tu bienestar van a ser ocultas. Esto incluye: +\n +\n- Notificaciones de favoritos, impulsos e seguidores +\n- Conteo de favoritos e impulsos en toots +\n- Estadísticas de seguidores e toots en perfiles +\n +\nLas notificaciones Push no serán afectadas, pero puedes revisar manualmente tus preferencias. El toot al que redactaste una respuesta ha sido eliminado Borrador eliminado Error al cargar la información de respuesta diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index d3636380..cd0c219a 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -236,7 +236,7 @@ %dh %dm %ds - Te segue + te segue Sempre mostrar mídia sensível Mídia Respondendo @%s @@ -472,4 +472,37 @@ Silencie notificações de %s Dessilencie notificações de %s Ocultar o título da barra superior de tarefas + Notificar sobre toots de quem me interessa + quem me interessa tootar + Erro ao carregar toot para responder + Erro ao enviar o toot! + O toot em que se rascunhou uma resposta foi excluído + Rascunho excluído + A função de rascunhos no Tusky foi totalmente redesenhada para ser mais rápida, mais fácil e com menos erros. +\nÉ possível acessar rascunhos antigos através de um botão na tela de novos rascunhos, mas serão removidos numa futura atualização! + Rascunhos antigos + Deseja excluir esta lista\? + Não é possível anexar mais de %1$d arquivos de mídia. + Ocultar status dos perfis + Ocultar status dos toots + Limitar notificações da linha do tempo + Revisar notificações + 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. + Salvo! + Nota pessoal sobre esta conta aqui + Bem-estar + Sem comunicados. + Indefinido + Duração + Anexos + Áudio + Novos toots + %s recém tootou + Comunicados \ No newline at end of file From db13cac681f2ecbb812f0db760be2836c842e1a4 Mon Sep 17 00:00:00 2001 From: Bifo Ho Date: Sun, 31 Jan 2021 17:57:54 +0000 Subject: [PATCH 41/81] Translated using Weblate (Bengali (Bangladesh)) Currently translated at 91.2% (416 of 456 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/bn_BD/ --- app/src/main/res/values-bn-rBD/strings.xml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-bn-rBD/strings.xml b/app/src/main/res/values-bn-rBD/strings.xml index 634f0d8f..72fb77e4 100644 --- a/app/src/main/res/values-bn-rBD/strings.xml +++ b/app/src/main/res/values-bn-rBD/strings.xml @@ -357,7 +357,7 @@ পোল যুক্ত করুন সর্বদা সামগ্রী সতর্কতা সহ চিহ্নিত টুটগুলি প্রসারিত করুন অনুসন্ধান করতে ব্যর্থ - অক্কোউন্টগুলি + অ্যাকাউন্টগুলো যখন শব্দ বা বাক্যাংশটি শুধুমাত্র আলফানিউমেরিক হয় তখন এটি শুধুমাত্র তখনই প্রয়োগ করা হবে যদি এটি সম্পূর্ণ শব্দটির সাথে মেলে সম্পূর্ণ শব্দ বিজ্ঞপ্তি ফিল্টার দেখান @@ -424,4 +424,15 @@ এই জায়গা খালি হতে পারে না। একটি নেটওয়ার্ক ত্রুটি ঘটেছে! আপনার সংযোগ পরীক্ষা করে আবার চেষ্টা করুন! একটি ত্রুটি ঘটেছে। + + %1$sটি পছন্দ + %1$sটি পছন্দ + + %s দৃশ্যমান + %s পোস্ট করেছে + %s তোমাকে ফলো করতে চায় + %s তোমাকে ফলো করেছে + %s তোমার টুট বুস্ট করেছে + %s তোমার টুট বুস্ট করেছে + ঘোষণা \ No newline at end of file From 483beaa957d1e213f2461d0d4d8a5ae4c395e780 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Sun, 31 Jan 2021 20:27:02 +0100 Subject: [PATCH 42/81] move onOptionsItemSelected to BaseActivity (#2059) * move onOptionsItemSelected to BaseActivity * revert change in ComposeActivity --- .../java/com/keylesspalace/tusky/AboutActivity.kt | 11 ----------- .../java/com/keylesspalace/tusky/AccountActivity.kt | 4 ---- .../com/keylesspalace/tusky/AccountListActivity.kt | 11 ----------- .../java/com/keylesspalace/tusky/BaseActivity.java | 10 ++++++++++ .../com/keylesspalace/tusky/EditProfileActivity.kt | 4 ---- .../java/com/keylesspalace/tusky/FiltersActivity.kt | 11 ----------- .../java/com/keylesspalace/tusky/LicenseActivity.kt | 11 ----------- .../java/com/keylesspalace/tusky/ListsActivity.kt | 11 +---------- .../java/com/keylesspalace/tusky/LoginActivity.kt | 9 --------- .../keylesspalace/tusky/ModalTimelineActivity.kt | 9 --------- .../com/keylesspalace/tusky/SavedTootActivity.java | 12 ------------ .../com/keylesspalace/tusky/StatusListActivity.kt | 9 --------- .../keylesspalace/tusky/TabPreferenceActivity.kt | 9 --------- .../com/keylesspalace/tusky/ViewTagActivity.java | 12 ------------ .../com/keylesspalace/tusky/ViewThreadActivity.java | 4 ---- .../announcements/AnnouncementsActivity.kt | 11 ----------- .../tusky/components/compose/ComposeActivity.kt | 2 +- .../components/instancemute/InstanceListActivity.kt | 11 ----------- .../components/preference/PreferencesActivity.kt | 11 ----------- .../tusky/components/report/ReportActivity.kt | 12 ------------ .../components/scheduled/ScheduledTootActivity.kt | 11 ----------- .../tusky/components/search/SearchActivity.kt | 13 +------------ 22 files changed, 13 insertions(+), 195 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/AboutActivity.kt b/app/src/main/java/com/keylesspalace/tusky/AboutActivity.kt index ce7d7294..48095425 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AboutActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AboutActivity.kt @@ -8,7 +8,6 @@ import android.text.SpannableStringBuilder import android.text.method.LinkMovementMethod import android.text.style.URLSpan import android.text.util.Linkify -import android.view.MenuItem import android.widget.TextView import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.util.CustomURLSpan @@ -50,16 +49,6 @@ class AboutActivity : BottomSheetActivity(), Injectable { } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> { - onBackPressed() - return true - } - } - return super.onOptionsItemSelected(item) - } - } private fun TextView.setClickableTextWithoutUnderlines(@StringRes textId: Int) { diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt index c89623d7..bbcfad80 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt @@ -801,10 +801,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { - android.R.id.home -> { - onBackPressed() - return true - } R.id.action_mention -> { mention() return true diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountListActivity.kt b/app/src/main/java/com/keylesspalace/tusky/AccountListActivity.kt index 6cf3367e..d592f053 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountListActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AccountListActivity.kt @@ -18,7 +18,6 @@ package com.keylesspalace.tusky import android.content.Context import android.content.Intent import android.os.Bundle -import android.view.MenuItem import com.keylesspalace.tusky.fragment.AccountListFragment import dagger.android.DispatchingAndroidInjector import dagger.android.HasAndroidInjector @@ -68,16 +67,6 @@ class AccountListActivity : BaseActivity(), HasAndroidInjector { .commit() } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> { - onBackPressed() - return true - } - } - return super.onOptionsItemSelected(item) - } - override fun androidInjector() = dispatchingAndroidInjector companion object { diff --git a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java index 36387268..92994f16 100644 --- a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java @@ -24,6 +24,7 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Bundle; import android.util.Log; +import android.view.MenuItem; import android.view.View; import androidx.annotation.NonNull; @@ -127,6 +128,15 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left); } + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + return true; + } + return super.onOptionsItemSelected(item); + } + @Override public void finish() { super.finish(); diff --git a/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt b/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt index 3d86b6e1..64d952b9 100644 --- a/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt @@ -296,10 +296,6 @@ class EditProfileActivity : BaseActivity(), Injectable { override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { - android.R.id.home -> { - onBackPressed() - return true - } R.id.action_save -> { save() return true diff --git a/app/src/main/java/com/keylesspalace/tusky/FiltersActivity.kt b/app/src/main/java/com/keylesspalace/tusky/FiltersActivity.kt index 5adce8ed..0726b26e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/FiltersActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/FiltersActivity.kt @@ -1,7 +1,6 @@ package com.keylesspalace.tusky import android.os.Bundle -import android.view.MenuItem import android.widget.AdapterView import android.widget.ArrayAdapter import android.widget.Toast @@ -205,14 +204,4 @@ class FiltersActivity: BaseActivity() { } } - // Activate back arrow in toolbar - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> { - onBackPressed() - return true - } - } - return super.onOptionsItemSelected(item) - } } \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/LicenseActivity.kt b/app/src/main/java/com/keylesspalace/tusky/LicenseActivity.kt index 915baf96..d6cc7bca 100644 --- a/app/src/main/java/com/keylesspalace/tusky/LicenseActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/LicenseActivity.kt @@ -18,7 +18,6 @@ package com.keylesspalace.tusky import android.os.Bundle import androidx.annotation.RawRes import android.util.Log -import android.view.MenuItem import android.widget.TextView import com.keylesspalace.tusky.util.IOUtils import kotlinx.android.extensions.CacheImplementation @@ -48,16 +47,6 @@ class LicenseActivity : BaseActivity() { } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> { - onBackPressed() - return true - } - } - return super.onOptionsItemSelected(item) - } - private fun loadFileIntoTextView(@RawRes fileId: Int, textView: TextView) { val sb = StringBuilder() diff --git a/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt b/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt index 994069f7..fa3c92c3 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt @@ -21,7 +21,6 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.view.LayoutInflater -import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.widget.* @@ -135,7 +134,7 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector { val positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE) editText.onTextChanged { s, _, _, _ -> - positiveButton.isEnabled = !s.isBlank() + positiveButton.isEnabled = s.isNotBlank() } editText.setText(list?.title) editText.text?.let { editText.setSelection(it.length) } @@ -218,14 +217,6 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector { override fun androidInjector() = dispatchingAndroidInjector - override fun onOptionsItemSelected(item: MenuItem): Boolean { - if (item.itemId == android.R.id.home) { - onBackPressed() - return true - } - return false - } - private object ListsDiffer : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: MastoList, newItem: MastoList): Boolean { return oldItem.id == newItem.id diff --git a/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt b/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt index 13699723..1eebcf69 100644 --- a/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt @@ -23,7 +23,6 @@ import android.net.Uri import android.os.Bundle import android.text.method.LinkMovementMethod import android.util.Log -import android.view.MenuItem import android.view.View import android.widget.TextView import androidx.appcompat.app.AlertDialog @@ -111,14 +110,6 @@ class LoginActivity : BaseActivity(), Injectable { } } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - if (item.itemId == android.R.id.home) { - onBackPressed() - return true - } - return super.onOptionsItemSelected(item) - } - /** * Obtain the oauth client credentials for this app. This is only necessary the first time the * app is run on a given server instance. So, after the first authentication, they are diff --git a/app/src/main/java/com/keylesspalace/tusky/ModalTimelineActivity.kt b/app/src/main/java/com/keylesspalace/tusky/ModalTimelineActivity.kt index e4655a5e..c3017b0c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ModalTimelineActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/ModalTimelineActivity.kt @@ -3,7 +3,6 @@ package com.keylesspalace.tusky import android.content.Context import android.content.Intent import android.os.Bundle -import android.view.MenuItem import com.google.android.material.floatingactionbutton.FloatingActionButton import com.keylesspalace.tusky.fragment.TimelineFragment import com.keylesspalace.tusky.interfaces.ActionButtonActivity @@ -56,14 +55,6 @@ class ModalTimelineActivity : BottomSheetActivity(), ActionButtonActivity, HasAn override fun getActionButton(): FloatingActionButton? = null - override fun onOptionsItemSelected(item: MenuItem): Boolean { - if (item.itemId == android.R.id.home) { - onBackPressed() - return true - } - return false - } - override fun androidInjector() = dispatchingAndroidInjector } diff --git a/app/src/main/java/com/keylesspalace/tusky/SavedTootActivity.java b/app/src/main/java/com/keylesspalace/tusky/SavedTootActivity.java index 8e2b5acb..63a32b17 100644 --- a/app/src/main/java/com/keylesspalace/tusky/SavedTootActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/SavedTootActivity.java @@ -18,7 +18,6 @@ package com.keylesspalace.tusky; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; -import android.view.MenuItem; import android.view.View; import androidx.annotation.Nullable; @@ -118,17 +117,6 @@ public final class SavedTootActivity extends BaseActivity implements SavedTootAd if (asyncTask != null) asyncTask.cancel(true); } - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: { - onBackPressed(); - return true; - } - } - return super.onOptionsItemSelected(item); - } - private void fetchToots() { asyncTask = new FetchPojosTask(this, database.tootDao()) .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); diff --git a/app/src/main/java/com/keylesspalace/tusky/StatusListActivity.kt b/app/src/main/java/com/keylesspalace/tusky/StatusListActivity.kt index 56ea4d2f..9eba5bbe 100644 --- a/app/src/main/java/com/keylesspalace/tusky/StatusListActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/StatusListActivity.kt @@ -18,7 +18,6 @@ package com.keylesspalace.tusky import android.content.Context import android.content.Intent import android.os.Bundle -import android.view.MenuItem import androidx.fragment.app.commit import com.keylesspalace.tusky.fragment.TimelineFragment @@ -66,14 +65,6 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector { } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - if (item.itemId == android.R.id.home){ - onBackPressed() - return true - } - return super.onOptionsItemSelected(item) - } - override fun androidInjector() = dispatchingAndroidInjector companion object { diff --git a/app/src/main/java/com/keylesspalace/tusky/TabPreferenceActivity.kt b/app/src/main/java/com/keylesspalace/tusky/TabPreferenceActivity.kt index 6b1aaef9..2b61f141 100644 --- a/app/src/main/java/com/keylesspalace/tusky/TabPreferenceActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/TabPreferenceActivity.kt @@ -18,7 +18,6 @@ package com.keylesspalace.tusky import android.graphics.Color import android.os.Bundle import android.util.Log -import android.view.MenuItem import android.view.View import android.widget.FrameLayout import androidx.appcompat.app.AlertDialog @@ -345,14 +344,6 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene } } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - if (item.itemId == android.R.id.home) { - onBackPressed() - return true - } - return false - } - override fun onPause() { super.onPause() if (tabsChanged) { diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java b/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java index a49dcc88..0ff6ff56 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java @@ -18,7 +18,6 @@ package com.keylesspalace.tusky; import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.view.MenuItem; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; @@ -72,17 +71,6 @@ public class ViewTagActivity extends BottomSheetActivity implements HasAndroidIn fragmentTransaction.commit(); } - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: { - onBackPressed(); - return true; - } - } - return super.onOptionsItemSelected(item); - } - @Override public AndroidInjector androidInjector() { return dispatchingAndroidInjector; diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewThreadActivity.java b/app/src/main/java/com/keylesspalace/tusky/ViewThreadActivity.java index e2ae63d1..88fb88cc 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ViewThreadActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/ViewThreadActivity.java @@ -110,10 +110,6 @@ public class ViewThreadActivity extends BottomSheetActivity implements HasAndroi @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - case android.R.id.home: { - onBackPressed(); - return true; - } case R.id.action_open_in_web: { LinkHelper.openLink(getIntent().getStringExtra(URL_EXTRA), this); return true; diff --git a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt index 0b96b430..865574a5 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt @@ -19,7 +19,6 @@ import android.content.Context import android.content.Intent import android.content.SharedPreferences import android.os.Bundle -import android.view.MenuItem import android.view.View import android.widget.PopupWindow import androidx.activity.viewModels @@ -123,16 +122,6 @@ class AnnouncementsActivity : BottomSheetActivity(), AnnouncementActionListener, progressBar.show() } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> { - onBackPressed() - return true - } - } - return super.onOptionsItemSelected(item) - } - private fun refreshAnnouncements() { viewModel.load() swipeRefreshLayout.isRefreshing = true 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 88329451..75b14239 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 @@ -30,7 +30,7 @@ import android.os.Build import android.os.Bundle import android.os.Parcelable import android.provider.MediaStore - import android.util.Log +import android.util.Log import android.view.KeyEvent import android.view.MenuItem import android.view.View diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceListActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceListActivity.kt index f4505ad6..ca04f9c7 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceListActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceListActivity.kt @@ -1,7 +1,6 @@ package com.keylesspalace.tusky.components.instancemute import android.os.Bundle -import android.view.MenuItem import com.keylesspalace.tusky.BaseActivity import com.keylesspalace.tusky.R import com.keylesspalace.tusky.components.instancemute.fragment.InstanceListFragment @@ -32,16 +31,6 @@ class InstanceListActivity: BaseActivity(), HasAndroidInjector { .commit() } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> { - onBackPressed() - return true - } - } - return super.onOptionsItemSelected(item) - } - override fun androidInjector() = androidInjector } \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt index 4fe0abd8..f21ec460 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt @@ -20,7 +20,6 @@ import android.content.Intent import android.content.SharedPreferences import android.os.Bundle import android.util.Log -import android.view.MenuItem import androidx.fragment.app.Fragment import androidx.preference.PreferenceManager import com.keylesspalace.tusky.BaseActivity @@ -101,16 +100,6 @@ class PreferencesActivity : BaseActivity(), SharedPreferences.OnSharedPreference PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(this) } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> { - onBackPressed() - return true - } - } - return super.onOptionsItemSelected(item) - } - private fun saveInstanceState(outState: Bundle) { outState.putBoolean("restart", restartActivitiesOnExit) } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/ReportActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/ReportActivity.kt index 3ecadd58..2c7f2d46 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/report/ReportActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/report/ReportActivity.kt @@ -18,7 +18,6 @@ package com.keylesspalace.tusky.components.report import android.content.Context import android.content.Intent import android.os.Bundle -import android.view.MenuItem import androidx.activity.viewModels import com.keylesspalace.tusky.BottomSheetActivity import com.keylesspalace.tusky.R @@ -30,7 +29,6 @@ import kotlinx.android.synthetic.main.activity_report.* import kotlinx.android.synthetic.main.toolbar_basic.* import javax.inject.Inject - class ReportActivity : BottomSheetActivity(), HasAndroidInjector { @Inject @@ -120,16 +118,6 @@ class ReportActivity : BottomSheetActivity(), HasAndroidInjector { wizard.currentItem = 0 } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> { - closeScreen() - return true - } - } - return super.onOptionsItemSelected(item) - } - companion object { private const val ACCOUNT_ID = "account_id" private const val ACCOUNT_USERNAME = "account_username" diff --git a/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootActivity.kt index f0944a34..40a67a4e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootActivity.kt @@ -18,7 +18,6 @@ package com.keylesspalace.tusky.components.scheduled import android.content.Context import android.content.Intent import android.os.Bundle -import android.view.MenuItem import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager @@ -104,16 +103,6 @@ class ScheduledTootActivity : BaseActivity(), ScheduledTootActionListener, Injec } } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> { - onBackPressed() - return true - } - } - return super.onOptionsItemSelected(item) - } - private fun refreshStatuses() { viewModel.reload() } 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 8b8d1ef4..be705637 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 @@ -20,7 +20,6 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.view.Menu -import android.view.MenuItem import androidx.activity.viewModels import androidx.appcompat.widget.SearchView import com.google.android.material.tabs.TabLayoutMediator @@ -82,17 +81,7 @@ class SearchActivity : BottomSheetActivity(), HasAndroidInjector { return true } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> { - onBackPressed() - return true - } - } - return super.onOptionsItemSelected(item) - } - - private fun getPageTitle(position: Int): CharSequence? { + private fun getPageTitle(position: Int): CharSequence { return when (position) { 0 -> getString(R.string.title_statuses) 1 -> getString(R.string.title_accounts) From c685192d49b03e6ad7134c63d9fc16403d0ff0e8 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Thu, 4 Feb 2021 21:16:58 +0300 Subject: [PATCH 43/81] Don't stop autocompletion after second @ (#2062) * ComposeTokenizer: do not search stop after second @ * ComposeTokenizer: try to fix test * ComposeTokenizer: try to fix test 2 * ComposeTokenizer: try to fix test 3 * ComposeTokenizer: fix autocomplete for accounts with 1 character nickname * ComposeTokenizer: better document tokenizer * ComposeTokenizer: add tests * ComposeTokenizer: remove unused logging --- .../tusky/util/ComposeTokenizer.kt | 38 +++++++++++++++++-- .../tusky/ComposeTokenizerTest.kt | 23 +++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/util/ComposeTokenizer.kt b/app/src/main/java/com/keylesspalace/tusky/util/ComposeTokenizer.kt index a1294333..c0da4275 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/ComposeTokenizer.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/ComposeTokenizer.kt @@ -21,20 +21,52 @@ import android.text.TextUtils import android.widget.MultiAutoCompleteTextView class ComposeTokenizer : MultiAutoCompleteTextView.Tokenizer { + + private fun isMentionOrHashtagAllowedCharacter(character: Char) : Boolean { + return Character.isLetterOrDigit(character) || character == '_' // simple usernames + || character == '-' // extended usernames + || character == '.' // domain dot + } + override fun findTokenStart(text: CharSequence, cursor: Int): Int { if (cursor == 0) { return cursor } var i = cursor var character = text[i - 1] - while (i > 0 && character != '@' && character != '#' && character != ':') { - // See SpanUtils.MENTION_REGEX - if (!Character.isLetterOrDigit(character) && character != '_') { + + // go up to first illegal character or character we're looking for (@, # or :) + while(i > 0 && !(character == '@' || character == '#' || character == ':')) { + if(!isMentionOrHashtagAllowedCharacter(character)) { return cursor } + i-- character = if (i == 0) ' ' else text[i - 1] } + + // maybe caught domain name? try search username + if(i > 2 && character == '@') { + var j = i - 1 + var character2 = text[i - 2] + + // again go up to first illegal character or tag "@" + while(j > 0 && character2 != '@') { + if(!isMentionOrHashtagAllowedCharacter(character2)) { + break + } + + j-- + character2 = if (j == 0) ' ' else text[j - 1] + } + + // found mention symbol, override cursor + if(character2 == '@') { + i = j + character = character2 + } + } + if (i < 1 || (character != '@' && character != '#' && character != ':') || i > 1 && !Character.isWhitespace(text[i - 2])) { diff --git a/app/src/test/java/com/keylesspalace/tusky/ComposeTokenizerTest.kt b/app/src/test/java/com/keylesspalace/tusky/ComposeTokenizerTest.kt index 73a00670..b603a4a7 100644 --- a/app/src/test/java/com/keylesspalace/tusky/ComposeTokenizerTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/ComposeTokenizerTest.kt @@ -44,6 +44,29 @@ class ComposeTokenizerTest(private val text: CharSequence, arrayOf(" @ment10n_ @ment20n_", 11, 20), arrayOf(" @ment10n_ @ment20n_n", 11, 21), arrayOf(" @ment10n_ @ment20n_9", 11, 21), + arrayOf(" @ment10n-", 1, 10), + arrayOf(" @ment10n- @", 11, 12), + arrayOf(" @ment10n- @ment20n", 11, 19), + arrayOf(" @ment10n- @ment20n-", 11, 20), + arrayOf(" @ment10n- @ment20n-n", 11, 21), + arrayOf(" @ment10n- @ment20n-9", 11, 21), + arrayOf("@ment10n@l0calhost", 0, 18), + arrayOf(" @ment10n@l0calhost", 1, 19), + arrayOf(" @ment10n_@l0calhost", 1, 20), + arrayOf(" @ment10n-@l0calhost", 1, 20), + arrayOf(" @ment10n_@l0calhost @ment20n@husky", 21, 35), + arrayOf(" @ment10n_@l0calhost @ment20n_@husky", 21, 36), + arrayOf(" @ment10n-@l0calhost @ment20n-@husky", 21, 36), + arrayOf(" @m@localhost", 1, 13), + arrayOf(" @m@localhost @a@localhost", 14, 26), + arrayOf("@m@", 0, 3), + arrayOf(" @m@ @a@asdf", 5, 12), + arrayOf(" @m@ @a@", 5, 8), + arrayOf(" @m@ @a@a", 5, 9), + arrayOf(" @m@a @a@m", 6, 10), + arrayOf("@m@m@", 5, 5), + arrayOf("#tusky@husky", 12, 12), + arrayOf(":tusky@husky", 12, 12), arrayOf("mention", 7, 7), arrayOf("ment10n", 7, 7), arrayOf("mentio_", 7, 7), From 958087044591b9322165e6addf7c8f2b167c4acf Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sat, 6 Feb 2021 10:14:51 +0300 Subject: [PATCH 44/81] Animated emoji support (#2064) * Animated emoji support * Try to query preference only once * Revert to using SpannableStringBuilder --- .../keylesspalace/tusky/AccountActivity.kt | 11 ++++-- .../tusky/AccountsInListFragment.kt | 9 +++-- .../com/keylesspalace/tusky/MainActivity.kt | 3 +- .../tusky/adapter/AccountAdapter.java | 6 ++- .../tusky/adapter/AccountFieldAdapter.kt | 6 +-- .../tusky/adapter/AccountSelectionAdapter.kt | 9 +++-- .../tusky/adapter/AccountViewHolder.java | 6 +-- .../tusky/adapter/BlocksAdapter.java | 13 +++---- .../tusky/adapter/FollowAdapter.java | 6 +-- .../tusky/adapter/FollowRequestViewHolder.kt | 17 ++++----- .../tusky/adapter/FollowRequestsAdapter.java | 6 +-- .../tusky/adapter/MutesAdapter.java | 13 +++---- .../tusky/adapter/NotificationsAdapter.java | 31 ++++++++++----- .../tusky/adapter/PollAdapter.kt | 11 ++++-- .../tusky/adapter/StatusBaseViewHolder.java | 34 +++++++++++++---- .../tusky/adapter/StatusViewHolder.java | 10 +++-- .../tusky/adapter/TimelineAdapter.java | 3 +- .../announcements/AnnouncementAdapter.kt | 6 ++- .../announcements/AnnouncementsActivity.kt | 3 +- .../components/compose/ComposeActivity.kt | 12 ++++-- .../compose/ComposeAutoCompleteAdapter.java | 11 +++--- .../conversation/ConversationViewHolder.java | 2 +- .../conversation/ConversationsFragment.kt | 4 +- .../preference/PreferencesFragment.kt | 7 ++++ .../report/adapter/StatusViewHolder.kt | 6 +-- .../fragments/ReportStatusesFragment.kt | 3 +- .../search/adapter/SearchAccountsAdapter.kt | 4 +- .../fragments/SearchAccountsFragment.kt | 13 ++++++- .../fragments/SearchStatusesFragment.kt | 3 +- .../tusky/fragment/AccountListFragment.kt | 14 +++++-- .../tusky/fragment/NotificationsFragment.java | 3 +- .../tusky/fragment/TimelineFragment.java | 3 +- .../tusky/fragment/ViewThreadFragment.java | 3 +- .../tusky/settings/SettingsConstants.kt | 1 + .../tusky/util/CustomEmojiHelper.kt | 38 ++++++++++++++----- .../tusky/util/StatusDisplayOptions.kt | 4 +- .../tusky/util/StatusViewHelper.kt | 10 ++--- app/src/main/res/values/strings.xml | 1 + 38 files changed, 225 insertions(+), 120 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt index bbcfad80..95101708 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt @@ -78,7 +78,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI private val viewModel: AccountViewModel by viewModels { viewModelFactory } - private val accountFieldAdapter = AccountFieldAdapter(this) + private lateinit var accountFieldAdapter : AccountFieldAdapter private var followState: FollowState = FollowState.NOT_FOLLOWING private var blocking: Boolean = false @@ -89,6 +89,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI private var loadedAccount: Account? = null private var animateAvatar: Boolean = false + private var animateEmojis: Boolean = false // fields for scroll animation private var hideFab: Boolean = false @@ -124,6 +125,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this) animateAvatar = sharedPrefs.getBoolean("animateGifAvatars", false) + animateEmojis = sharedPrefs.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) hideFab = sharedPrefs.getBoolean("fabHide", false) setupToolbar() @@ -162,6 +164,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI accountFollowsYouTextView.hide() // setup the RecyclerView for the account fields + accountFieldAdapter = AccountFieldAdapter(this, animateEmojis) accountFieldList.isNestedScrollingEnabled = false accountFieldList.layoutManager = LinearLayoutManager(this) accountFieldList.adapter = accountFieldAdapter @@ -375,9 +378,9 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI val usernameFormatted = getString(R.string.status_username_format, account.username) accountUsernameTextView.text = usernameFormatted - accountDisplayNameTextView.text = account.name.emojify(account.emojis, accountDisplayNameTextView) + accountDisplayNameTextView.text = account.name.emojify(account.emojis, accountDisplayNameTextView, animateEmojis) - val emojifiedNote = account.note.emojify(account.emojis, accountNoteTextView) + val emojifiedNote = account.note.emojify(account.emojis, accountNoteTextView, animateEmojis) LinkHelper.setClickableText(accountNoteTextView, emojifiedNote, null, this) // accountFieldAdapter.fields = account.fields ?: emptyList() @@ -437,7 +440,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI private fun updateToolbar() { loadedAccount?.let { account -> - val emojifiedName = account.name.emojify(account.emojis, accountToolbar) + val emojifiedName = account.name.emojify(account.emojis, accountToolbar, animateEmojis) try { supportActionBar?.title = EmojiCompat.get().process(emojifiedName) diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountsInListFragment.kt b/app/src/main/java/com/keylesspalace/tusky/AccountsInListFragment.kt index 2933d689..f1c3d54d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountsInListFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AccountsInListFragment.kt @@ -31,6 +31,7 @@ import androidx.recyclerview.widget.RecyclerView import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.di.ViewModelFactory import com.keylesspalace.tusky.entity.Account +import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.util.* import com.keylesspalace.tusky.viewmodel.AccountsInListViewModel import com.keylesspalace.tusky.viewmodel.State @@ -71,7 +72,9 @@ class AccountsInListFragment : DialogFragment(), Injectable { private val searchAdapter = SearchAdapter() private val radius by lazy { resources.getDimensionPixelSize(R.dimen.avatar_radius_48dp) } - private val animateAvatar by lazy { PreferenceManager.getDefaultSharedPreferences(requireContext()).getBoolean("animateGifAvatars", false) } + private val pm by lazy { PreferenceManager.getDefaultSharedPreferences(requireContext()) } + private val animateAvatar by lazy { pm.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false) } + private val animateEmojis by lazy { pm.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -209,7 +212,7 @@ class AccountsInListFragment : DialogFragment(), Injectable { } fun bind(account: Account) { - displayNameTextView.text = account.name.emojify(account.emojis, displayNameTextView) + displayNameTextView.text = account.name.emojify(account.emojis, displayNameTextView, animateEmojis) usernameTextView.text = account.username loadAvatar(account.avatar, avatar, radius, animateAvatar) } @@ -252,7 +255,7 @@ class AccountsInListFragment : DialogFragment(), Injectable { override val containerView = itemView fun bind(account: Account, inAList: Boolean) { - displayNameTextView.text = account.name.emojify(account.emojis, displayNameTextView) + displayNameTextView.text = account.name.emojify(account.emojis, displayNameTextView, animateEmojis) usernameTextView.text = account.username loadAvatar(account.avatar, avatar, radius, animateAvatar) diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt index 231c6f43..3f41ca00 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt @@ -723,8 +723,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)) + 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/adapter/AccountAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.java index 5c52e39e..24430dce 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.java @@ -33,10 +33,14 @@ public abstract class AccountAdapter extends RecyclerView.Adapter { List accountList; AccountActionListener accountActionListener; private boolean bottomLoading; + protected final boolean animateEmojis; + protected final boolean animateAvatar; - AccountAdapter(AccountActionListener accountActionListener) { + AccountAdapter(AccountActionListener accountActionListener, boolean animateAvatar, boolean animateEmojis) { this.accountList = new ArrayList<>(); this.accountActionListener = accountActionListener; + this.animateAvatar = animateAvatar; + this.animateEmojis = animateEmojis; bottomLoading = false; } diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountFieldAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountFieldAdapter.kt index e80129c6..e395a7e6 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountFieldAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountFieldAdapter.kt @@ -29,7 +29,7 @@ import com.keylesspalace.tusky.interfaces.LinkListener import com.keylesspalace.tusky.util.* import kotlinx.android.synthetic.main.item_account_field.view.* -class AccountFieldAdapter(private val linkListener: LinkListener) : RecyclerView.Adapter() { +class AccountFieldAdapter(private val linkListener: LinkListener, private val animateEmojis: Boolean) : RecyclerView.Adapter() { var emojis: List = emptyList() var fields: List> = emptyList() @@ -55,10 +55,10 @@ class AccountFieldAdapter(private val linkListener: LinkListener) : RecyclerView viewHolder.valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_check_circle, 0) } else { val field = proofOrField.asRight() - val emojifiedName = field.name.emojify(emojis, viewHolder.nameTextView) + val emojifiedName = field.name.emojify(emojis, viewHolder.nameTextView, animateEmojis) viewHolder.nameTextView.text = emojifiedName - val emojifiedValue = field.value.emojify(emojis, viewHolder.valueTextView) + val emojifiedValue = field.value.emojify(emojis, viewHolder.valueTextView, animateEmojis) LinkHelper.setClickableText(viewHolder.valueTextView, emojifiedValue, null, linkListener) if(field.verifiedAt != null) { diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountSelectionAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountSelectionAdapter.kt index dae0db4b..c8df79f9 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountSelectionAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountSelectionAdapter.kt @@ -23,6 +23,7 @@ import android.widget.ArrayAdapter import androidx.preference.PreferenceManager import com.keylesspalace.tusky.R import com.keylesspalace.tusky.db.AccountEntity +import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.util.* import kotlinx.android.synthetic.main.item_autocomplete_account.view.* @@ -41,12 +42,14 @@ class AccountSelectionAdapter(context: Context) : ArrayAdapter(co val username = view.username val displayName = view.display_name val avatar = view.avatar + val pm = PreferenceManager.getDefaultSharedPreferences(avatar.context) + val animateEmojis = pm.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) + username.text = account.fullName - displayName.text = account.displayName.emojify(account.emojis, displayName) + displayName.text = account.displayName.emojify(account.emojis, displayName, animateEmojis) val avatarRadius = avatar.context.resources.getDimensionPixelSize(R.dimen.avatar_radius_42dp) - val animateAvatar = PreferenceManager.getDefaultSharedPreferences(avatar.context) - .getBoolean("animateGifAvatars", false) + val animateAvatar = pm.getBoolean("animateGifAvatars", false) loadAvatar(account.profilePictureUrl, avatar, avatarRadius, animateAvatar) 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 7b07d5bd..559426e3 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountViewHolder.java @@ -22,7 +22,6 @@ public class AccountViewHolder extends RecyclerView.ViewHolder { private ImageView avatarInset; private String accountId; private boolean showBotOverlay; - private boolean animateAvatar; public AccountViewHolder(View itemView) { super(itemView); @@ -32,15 +31,14 @@ public class AccountViewHolder extends RecyclerView.ViewHolder { avatarInset = itemView.findViewById(R.id.account_avatar_inset); SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(itemView.getContext()); showBotOverlay = sharedPrefs.getBoolean("showBotOverlay", true); - animateAvatar = sharedPrefs.getBoolean("animateGifAvatars", false); } - public void setupWithAccount(Account account) { + public void setupWithAccount(Account account, boolean animateAvatar, boolean animateEmojis) { accountId = account.getId(); String format = username.getContext().getString(R.string.status_username_format); String formattedUsername = String.format(format, account.getUsername()); username.setText(formattedUsername); - CharSequence emojifiedName = CustomEmojiHelper.emojify(account.getName(), account.getEmojis(), displayName); + CharSequence emojifiedName = CustomEmojiHelper.emojify(account.getName(), account.getEmojis(), displayName, animateEmojis); displayName.setText(emojifiedName); int avatarRadius = avatar.getContext().getResources() .getDimensionPixelSize(R.dimen.avatar_radius_48dp); diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java index 073d76da..13144cb8 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java @@ -34,8 +34,8 @@ import com.keylesspalace.tusky.util.ImageLoadingHelper; public class BlocksAdapter extends AccountAdapter { - public BlocksAdapter(AccountActionListener accountActionListener) { - super(accountActionListener); + public BlocksAdapter(AccountActionListener accountActionListener, boolean animateAvatar, boolean animateEmojis) { + super(accountActionListener, animateAvatar, animateEmojis); } @NonNull @@ -60,7 +60,7 @@ public class BlocksAdapter extends AccountAdapter { public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { if (getItemViewType(position) == VIEW_TYPE_ACCOUNT) { BlockedUserViewHolder holder = (BlockedUserViewHolder) viewHolder; - holder.setupWithAccount(accountList.get(position)); + holder.setupWithAccount(accountList.get(position), animateAvatar, animateEmojis); holder.setupActionListener(accountActionListener); } } @@ -71,7 +71,6 @@ public class BlocksAdapter extends AccountAdapter { private TextView displayName; private ImageButton unblock; private String id; - private boolean animateAvatar; BlockedUserViewHolder(View itemView) { super(itemView); @@ -79,14 +78,12 @@ public class BlocksAdapter extends AccountAdapter { username = itemView.findViewById(R.id.blocked_user_username); displayName = itemView.findViewById(R.id.blocked_user_display_name); unblock = itemView.findViewById(R.id.blocked_user_unblock); - animateAvatar = PreferenceManager.getDefaultSharedPreferences(itemView.getContext()) - .getBoolean("animateGifAvatars", false); } - void setupWithAccount(Account account) { + void setupWithAccount(Account account, boolean animateAvatar, boolean animateEmojis) { id = account.getId(); - CharSequence emojifiedName = CustomEmojiHelper.emojify(account.getName(), account.getEmojis(), displayName); + CharSequence emojifiedName = CustomEmojiHelper.emojify(account.getName(), account.getEmojis(), displayName, animateEmojis); displayName.setText(emojifiedName); String format = username.getContext().getString(R.string.status_username_format); String formattedUsername = String.format(format, account.getUsername()); diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.java index 82158746..98cb9e4d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.java @@ -27,8 +27,8 @@ import com.keylesspalace.tusky.interfaces.AccountActionListener; /** Both for follows and following lists. */ public class FollowAdapter extends AccountAdapter { - public FollowAdapter(AccountActionListener accountActionListener) { - super(accountActionListener); + public FollowAdapter(AccountActionListener accountActionListener, boolean animateAvatar, boolean animateEmojis) { + super(accountActionListener, animateAvatar, animateEmojis); } @NonNull @@ -53,7 +53,7 @@ public class FollowAdapter extends AccountAdapter { public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { if (getItemViewType(position) == VIEW_TYPE_ACCOUNT) { AccountViewHolder holder = (AccountViewHolder) viewHolder; - holder.setupWithAccount(accountList.get(position)); + holder.setupWithAccount(accountList.get(position), animateAvatar, animateEmojis); holder.setupActionListener(accountActionListener); } } diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestViewHolder.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestViewHolder.kt index dec4586b..8fa14731 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestViewHolder.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestViewHolder.kt @@ -10,27 +10,24 @@ import androidx.recyclerview.widget.RecyclerView import com.keylesspalace.tusky.R import com.keylesspalace.tusky.entity.Account import com.keylesspalace.tusky.interfaces.AccountActionListener -import com.keylesspalace.tusky.util.emojify -import com.keylesspalace.tusky.util.loadAvatar -import com.keylesspalace.tusky.util.unicodeWrap -import com.keylesspalace.tusky.util.visible +import com.keylesspalace.tusky.util.* import kotlinx.android.synthetic.main.item_follow_request_notification.view.* -internal class FollowRequestViewHolder(itemView: View, private val showHeader: Boolean) : RecyclerView.ViewHolder(itemView) { +internal class FollowRequestViewHolder( + itemView: View, + private val showHeader: Boolean) : RecyclerView.ViewHolder(itemView) { private var id: String? = null - private val animateAvatar: Boolean = PreferenceManager.getDefaultSharedPreferences(itemView.context) - .getBoolean("animateGifAvatars", false) - fun setupWithAccount(account: Account) { + fun setupWithAccount(account: Account, animateAvatar: Boolean, animateEmojis: Boolean) { id = account.id val wrappedName = account.name.unicodeWrap() - val emojifiedName: CharSequence = wrappedName.emojify(account.emojis, itemView) + val emojifiedName: CharSequence = wrappedName.emojify(account.emojis, itemView, animateEmojis) itemView.displayNameTextView.text = emojifiedName if (showHeader) { val wholeMessage: String = itemView.context.getString(R.string.notification_follow_request_format, wrappedName) itemView.notificationTextView?.text = SpannableStringBuilder(wholeMessage).apply { setSpan(StyleSpan(Typeface.BOLD), 0, wrappedName.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - }.emojify(account.emojis, itemView) + }.emojify(account.emojis, itemView, animateEmojis) } itemView.notificationTextView?.visible(showHeader) val format = itemView.context.getString(R.string.status_username_format) diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java index dab3d4fe..9ba59884 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java @@ -27,8 +27,8 @@ import com.keylesspalace.tusky.interfaces.AccountActionListener; public class FollowRequestsAdapter extends AccountAdapter { - public FollowRequestsAdapter(AccountActionListener accountActionListener) { - super(accountActionListener); + public FollowRequestsAdapter(AccountActionListener accountActionListener, boolean animateAvatar, boolean animateEmojis) { + super(accountActionListener, animateAvatar, animateEmojis); } @NonNull @@ -53,7 +53,7 @@ public class FollowRequestsAdapter extends AccountAdapter { public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { if (getItemViewType(position) == VIEW_TYPE_ACCOUNT) { FollowRequestViewHolder holder = (FollowRequestViewHolder) viewHolder; - holder.setupWithAccount(accountList.get(position)); + holder.setupWithAccount(accountList.get(position), animateAvatar, animateEmojis); holder.setupActionListener(accountActionListener); } } diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java index c4224c9c..e1a30759 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java @@ -23,8 +23,8 @@ import java.util.HashMap; public class MutesAdapter extends AccountAdapter { private HashMap mutingNotificationsMap; - public MutesAdapter(AccountActionListener accountActionListener) { - super(accountActionListener); + public MutesAdapter(AccountActionListener accountActionListener, boolean animateAvatar, boolean animateEmojis) { + super(accountActionListener, animateAvatar, animateEmojis); mutingNotificationsMap = new HashMap(); } @@ -51,7 +51,7 @@ public class MutesAdapter extends AccountAdapter { if (getItemViewType(position) == VIEW_TYPE_ACCOUNT) { MutedUserViewHolder holder = (MutedUserViewHolder) viewHolder; Account account = accountList.get(position); - holder.setupWithAccount(account, mutingNotificationsMap.get(account.getId())); + holder.setupWithAccount(account, mutingNotificationsMap.get(account.getId()), animateAvatar, animateEmojis); holder.setupActionListener(accountActionListener); } } @@ -73,7 +73,6 @@ public class MutesAdapter extends AccountAdapter { private ImageButton unmute; private ImageButton muteNotifications; private String id; - private boolean animateAvatar; private boolean notifications; MutedUserViewHolder(View itemView) { @@ -83,13 +82,11 @@ public class MutesAdapter extends AccountAdapter { displayName = itemView.findViewById(R.id.muted_user_display_name); unmute = itemView.findViewById(R.id.muted_user_unmute); muteNotifications = itemView.findViewById(R.id.muted_user_mute_notifications); - animateAvatar = PreferenceManager.getDefaultSharedPreferences(itemView.getContext()) - .getBoolean("animateGifAvatars", false); } - void setupWithAccount(Account account, Boolean mutingNotifications) { + void setupWithAccount(Account account, Boolean mutingNotifications, boolean animateAvatar, boolean animateEmojis) { id = account.getId(); - CharSequence emojifiedName = CustomEmojiHelper.emojify(account.getName(), account.getEmojis(), displayName); + CharSequence emojifiedName = CustomEmojiHelper.emojify(account.getName(), account.getEmojis(), displayName, animateEmojis); displayName.setText(emojifiedName); String format = username.getContext().getString(R.string.status_username_format); String formattedUsername = String.format(format, account.getUsername()); 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 2fe95385..833d18f4 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java @@ -232,7 +232,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter { case VIEW_TYPE_FOLLOW_REQUEST: { if (payloadForHolder == null) { FollowRequestViewHolder holder = (FollowRequestViewHolder) viewHolder; - holder.setupWithAccount(concreteNotificaton.getAccount()); + holder.setupWithAccount(concreteNotificaton.getAccount(), statusDisplayOptions.animateAvatars(), statusDisplayOptions.animateEmojis()); holder.setupActionListener(accountActionListener); } } @@ -255,7 +255,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter { statusDisplayOptions.useBlurhash(), CardViewMode.NONE, statusDisplayOptions.confirmReblogs(), - statusDisplayOptions.hideStats() + statusDisplayOptions.hideStats(), + statusDisplayOptions.animateEmojis() ); } @@ -336,13 +337,17 @@ public class NotificationsAdapter extends RecyclerView.Adapter { String format = context.getString(R.string.notification_follow_format); String wrappedDisplayName = StringUtils.unicodeWrap(account.getName()); String wholeMessage = String.format(format, wrappedDisplayName); - CharSequence emojifiedMessage = CustomEmojiHelper.emojify(wholeMessage, account.getEmojis(), message); + CharSequence emojifiedMessage = CustomEmojiHelper.emojify( + wholeMessage, account.getEmojis(), message, statusDisplayOptions.animateEmojis() + ); message.setText(emojifiedMessage); String username = context.getString(R.string.status_username_format, account.getUsername()); usernameView.setText(username); - CharSequence emojifiedDisplayName = CustomEmojiHelper.emojify(wrappedDisplayName, account.getEmojis(), usernameView); + CharSequence emojifiedDisplayName = CustomEmojiHelper.emojify( + wrappedDisplayName, account.getEmojis(), usernameView, statusDisplayOptions.animateEmojis() + ); displayNameView.setText(emojifiedDisplayName); @@ -425,7 +430,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter { } private void setDisplayName(String name, List emojis) { - CharSequence emojifiedName = CustomEmojiHelper.emojify(name, emojis, displayName); + CharSequence emojifiedName = CustomEmojiHelper.emojify(name, emojis, displayName, statusDisplayOptions.animateEmojis()); displayName.setText(emojifiedName); } @@ -519,7 +524,9 @@ public class NotificationsAdapter extends RecyclerView.Adapter { final SpannableStringBuilder str = new SpannableStringBuilder(wholeMessage); str.setSpan(new StyleSpan(Typeface.BOLD), 0, displayName.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - CharSequence emojifiedText = CustomEmojiHelper.emojify(str, notificationViewData.getAccount().getEmojis(), message); + CharSequence emojifiedText = CustomEmojiHelper.emojify( + str, notificationViewData.getAccount().getEmojis(), message, statusDisplayOptions.animateEmojis() + ); message.setText(emojifiedText); if (statusViewData != null) { @@ -630,11 +637,17 @@ public class NotificationsAdapter extends RecyclerView.Adapter { statusContent.setFilters(NO_INPUT_FILTER); } - CharSequence emojifiedText = CustomEmojiHelper.emojify(content, emojis, statusContent); + CharSequence emojifiedText = CustomEmojiHelper.emojify( + content, emojis, statusContent, statusDisplayOptions.animateEmojis() + ); LinkHelper.setClickableText(statusContent, emojifiedText, statusViewData.getMentions(), listener); - CharSequence emojifiedContentWarning = - CustomEmojiHelper.emojify(statusViewData.getSpoilerText(), statusViewData.getStatusEmojis(), contentWarningDescriptionTextView); + CharSequence emojifiedContentWarning = CustomEmojiHelper.emojify( + statusViewData.getSpoilerText(), + statusViewData.getStatusEmojis(), + contentWarningDescriptionTextView, + statusDisplayOptions.animateEmojis() + ); contentWarningDescriptionTextView.setText(emojifiedContentWarning); } 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 a990d326..0208b953 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt @@ -38,6 +38,7 @@ class PollAdapter: RecyclerView.Adapter() { private var mode = RESULT private var emojis: List = emptyList() private var resultClickListener: View.OnClickListener? = null + private var animateEmojis = false fun setup( options: List, @@ -45,13 +46,15 @@ class PollAdapter: RecyclerView.Adapter() { votersCount: Int?, emojis: List, mode: Int, - resultClickListener: View.OnClickListener?) { + resultClickListener: View.OnClickListener?, + animateEmojis: Boolean) { this.pollOptions = options this.voteCount = voteCount this.votersCount = votersCount this.emojis = emojis this.mode = mode this.resultClickListener = resultClickListener + this.animateEmojis = animateEmojis notifyDataSetChanged() } @@ -81,7 +84,7 @@ class PollAdapter: RecyclerView.Adapter() { RESULT -> { val percent = calculatePercent(option.votesCount, votersCount, voteCount) val emojifiedPollOptionText = buildDescription(option.title, percent, holder.resultTextView.context) - .emojify(emojis, holder.resultTextView) + .emojify(emojis, holder.resultTextView, animateEmojis) holder.resultTextView.text = EmojiCompat.get().process(emojifiedPollOptionText) val level = percent * 100 @@ -90,7 +93,7 @@ class PollAdapter: RecyclerView.Adapter() { holder.resultTextView.setOnClickListener(resultClickListener) } SINGLE -> { - val emojifiedPollOptionText = option.title.emojify(emojis, holder.radioButton) + val emojifiedPollOptionText = option.title.emojify(emojis, holder.radioButton, animateEmojis) holder.radioButton.text = EmojiCompat.get().process(emojifiedPollOptionText) holder.radioButton.isChecked = option.selected holder.radioButton.setOnClickListener { @@ -101,7 +104,7 @@ class PollAdapter: RecyclerView.Adapter() { } } MULTIPLE -> { - val emojifiedPollOptionText = option.title.emojify(emojis, holder.checkBox) + val emojifiedPollOptionText = option.title.emojify(emojis, holder.checkBox, animateEmojis) holder.checkBox.text = EmojiCompat.get().process(emojifiedPollOptionText) holder.checkBox.isChecked = option.selected holder.checkBox.setOnCheckedChangeListener { _, isChecked -> 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 5bce0624..3fc27d7c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java @@ -181,8 +181,10 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { protected abstract int getMediaPreviewHeight(Context context); - protected void setDisplayName(String name, List customEmojis) { - CharSequence emojifiedName = CustomEmojiHelper.emojify(name, customEmojis, displayName); + protected void setDisplayName(String name, List customEmojis, StatusDisplayOptions statusDisplayOptions) { + CharSequence emojifiedName = CustomEmojiHelper.emojify( + name, customEmojis, displayName, statusDisplayOptions.animateEmojis() + ); displayName.setText(emojifiedName); } @@ -206,7 +208,9 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { final StatusActionListener listener) { boolean sensitive = !TextUtils.isEmpty(spoilerText); if (sensitive) { - CharSequence emojiSpoiler = CustomEmojiHelper.emojify(spoilerText, emojis, contentWarningDescription); + CharSequence emojiSpoiler = CustomEmojiHelper.emojify( + spoilerText, emojis, contentWarningDescription, statusDisplayOptions.animateEmojis() + ); contentWarningDescription.setText(emojiSpoiler); contentWarningDescription.setVisibility(View.VISIBLE); contentWarningButton.setVisibility(View.VISIBLE); @@ -245,7 +249,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { StatusDisplayOptions statusDisplayOptions, final StatusActionListener listener) { if (expanded) { - CharSequence emojifiedText = CustomEmojiHelper.emojify(content, emojis, this.content); + CharSequence emojifiedText = CustomEmojiHelper.emojify(content, emojis, this.content, statusDisplayOptions.animateEmojis()); LinkHelper.setClickableText(this.content, emojifiedText, mentions, listener); for (int i = 0; i < mediaLabels.length; ++i) { updateMediaLabel(i, sensitive, expanded); @@ -709,7 +713,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { StatusDisplayOptions statusDisplayOptions, @Nullable Object payloads) { if (payloads == null) { - setDisplayName(status.getUserFullName(), status.getAccountEmojis()); + setDisplayName(status.getUserFullName(), status.getAccountEmojis(), statusDisplayOptions); setUsername(status.getNickname()); setCreatedAt(status.getCreatedAt(), statusDisplayOptions); setIsReply(status.getInReplyToId() != null); @@ -927,12 +931,28 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { listener.onViewThread(position); } }; - pollAdapter.setup(poll.getOptions(), poll.getVotesCount(), poll.getVotersCount(), emojis, PollAdapter.RESULT, viewThreadListener); + pollAdapter.setup( + poll.getOptions(), + poll.getVotesCount(), + poll.getVotersCount(), + emojis, + PollAdapter.RESULT, + viewThreadListener, + statusDisplayOptions.animateEmojis() + ); pollButton.setVisibility(View.GONE); } else { // voting possible - pollAdapter.setup(poll.getOptions(), poll.getVotesCount(), poll.getVotersCount(), emojis, poll.getMultiple() ? PollAdapter.MULTIPLE : PollAdapter.SINGLE, null); + pollAdapter.setup( + poll.getOptions(), + poll.getVotesCount(), + poll.getVotersCount(), + emojis, + poll.getMultiple() ? PollAdapter.MULTIPLE : PollAdapter.SINGLE, + null, + statusDisplayOptions.animateEmojis() + ); pollButton.setVisibility(View.VISIBLE); 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 4a0f6679..043b7b35 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java @@ -66,7 +66,7 @@ public class StatusViewHolder extends StatusBaseViewHolder { if (rebloggedByDisplayName == null) { hideStatusInfo(); } else { - setRebloggedByDisplayName(rebloggedByDisplayName, status); + setRebloggedByDisplayName(rebloggedByDisplayName, status, statusDisplayOptions); statusInfo.setOnClickListener(v -> listener.onOpenReblog(getAdapterPosition())); } @@ -75,11 +75,15 @@ public class StatusViewHolder extends StatusBaseViewHolder { } - private void setRebloggedByDisplayName(final CharSequence name, final StatusViewData.Concrete status) { + private void setRebloggedByDisplayName(final CharSequence name, + final StatusViewData.Concrete status, + final StatusDisplayOptions statusDisplayOptions) { Context context = statusInfo.getContext(); CharSequence wrappedName = StringUtils.unicodeWrap(name); CharSequence boostedText = context.getString(R.string.status_boosted_format, wrappedName); - CharSequence emojifiedText = CustomEmojiHelper.emojify(boostedText, status.getRebloggedByAccountEmojis(), statusInfo); + CharSequence emojifiedText = CustomEmojiHelper.emojify( + boostedText, status.getRebloggedByAccountEmojis(), statusInfo, statusDisplayOptions.animateEmojis() + ); statusInfo.setText(emojifiedText); statusInfo.setVisibility(View.VISIBLE); } diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java index 7963847a..4be922d6 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java @@ -66,7 +66,8 @@ public final class TimelineAdapter extends RecyclerView.Adapter { statusDisplayOptions.useBlurhash(), statusDisplayOptions.cardViewMode(), statusDisplayOptions.confirmReblogs(), - statusDisplayOptions.hideStats() + statusDisplayOptions.hideStats(), + statusDisplayOptions.animateEmojis() ); } 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 c0e6bdd8..b54b1555 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 @@ -42,7 +42,8 @@ interface AnnouncementActionListener: LinkListener { class AnnouncementAdapter( private var items: List = emptyList(), private val listener: AnnouncementActionListener, - private val wellbeingEnabled: Boolean = false + private val wellbeingEnabled: Boolean = false, + private val animateEmojis: Boolean = false ) : RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AnnouncementViewHolder { @@ -99,7 +100,8 @@ class AnnouncementAdapter( reaction.staticUrl ?: "", null )), - this + this, + animateEmojis ) isChecked = reaction.me diff --git a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt index 865574a5..ffd97191 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt @@ -82,8 +82,9 @@ class AnnouncementsActivity : BottomSheetActivity(), AnnouncementActionListener, val preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) val wellbeingEnabled = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false) + val animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) - adapter = AnnouncementAdapter(emptyList(), this, wellbeingEnabled) + adapter = AnnouncementAdapter(emptyList(), this, wellbeingEnabled, animateEmojis) announcementsList.adapter = adapter 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 75b14239..8fc48e9c 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 @@ -69,6 +69,7 @@ import com.keylesspalace.tusky.entity.Attachment import com.keylesspalace.tusky.entity.Emoji import com.keylesspalace.tusky.entity.NewPoll import com.keylesspalace.tusky.entity.Status +import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.util.* import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial @@ -160,7 +161,7 @@ class ComposeActivity : BaseActivity(), composeScheduleView.setDateTime(composeOptions?.scheduledAt) } - setupComposeField(viewModel.startingText) + setupComposeField(preferences, viewModel.startingText) setupContentWarningField(composeOptions?.contentWarning) setupPollView() applyShareIntent(intent, savedInstanceState) @@ -245,13 +246,18 @@ class ComposeActivity : BaseActivity(), composeContentWarningField.onTextChanged { _, _, _, _ -> updateVisibleCharactersLeft() } } - private fun setupComposeField(startingText: String?) { + private fun setupComposeField(preferences: SharedPreferences, startingText: String?) { composeEditField.setOnCommitContentListener(this) composeEditField.setOnKeyListener { _, keyCode, event -> this.onKeyDown(keyCode, event) } composeEditField.setAdapter( - ComposeAutoCompleteAdapter(this)) + ComposeAutoCompleteAdapter( + this, + preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false), + preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) + ) + ) composeEditField.setTokenizer(ComposeTokenizer()) composeEditField.setText(startingText) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeAutoCompleteAdapter.java b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeAutoCompleteAdapter.java index df9ae8a8..b2fa94c3 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeAutoCompleteAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeAutoCompleteAdapter.java @@ -53,11 +53,15 @@ public class ComposeAutoCompleteAdapter extends BaseAdapter private final ArrayList resultList; private final AutocompletionProvider autocompletionProvider; + private final boolean animateAvatar; + private final boolean animateEmojis; - public ComposeAutoCompleteAdapter(AutocompletionProvider autocompletionProvider) { + public ComposeAutoCompleteAdapter(AutocompletionProvider autocompletionProvider, boolean animateAvatar, boolean animateEmojis) { super(); resultList = new ArrayList<>(); this.autocompletionProvider = autocompletionProvider; + this.animateAvatar = animateAvatar; + this.animateEmojis = animateEmojis; } @Override @@ -147,15 +151,12 @@ public class ComposeAutoCompleteAdapter extends BaseAdapter ); accountViewHolder.username.setText(formattedUsername); CharSequence emojifiedName = CustomEmojiHelper.emojify(account.getName(), - account.getEmojis(), accountViewHolder.displayName); + account.getEmojis(), accountViewHolder.displayName, animateEmojis); accountViewHolder.displayName.setText(emojifiedName); int avatarRadius = accountViewHolder.avatar.getContext().getResources() .getDimensionPixelSize(R.dimen.avatar_radius_42dp); - boolean animateAvatar = PreferenceManager.getDefaultSharedPreferences(accountViewHolder.avatar.getContext()) - .getBoolean("animateGifAvatars", false); - ImageLoadingHelper.loadAvatar( account.getAvatar(), accountViewHolder.avatar, 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 19ef749e..e74be628 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 @@ -75,7 +75,7 @@ public class ConversationViewHolder extends StatusBaseViewHolder { setupCollapsedState(status.getCollapsible(), status.getCollapsed(), status.getExpanded(), status.getSpoilerText(), listener); - setDisplayName(account.getDisplayName(), account.getEmojis()); + setDisplayName(account.getDisplayName(), account.getEmojis(), statusDisplayOptions); setUsername(account.getUsername()); setCreatedAt(status.getCreatedAt(), statusDisplayOptions); setIsReply(status.getInReplyToId() != null); 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 d50f9043..abae8702 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 @@ -67,8 +67,8 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res useBlurhash = preferences.getBoolean("useBlurhash", true), cardViewMode = CardViewMode.NONE, confirmReblogs = preferences.getBoolean("confirmReblogs", true), - hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false) - + hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false), + animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) ) adapter = ConversationAdapter(statusDisplayOptions, this, ::onTopLoaded, viewModel::retry) 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 d2598c12..e0a1b683 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 @@ -174,6 +174,13 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable { setTitle(R.string.pref_title_enable_swipe_for_tabs) isSingleLineTitle = false } + + switchPreference { + setDefaultValue(false) + key = PrefKeys.ANIMATE_CUSTOM_EMOJIS + setTitle(R.string.pref_title_animate_custom_emojis) + isSingleLineTitle = false + } } preferenceCategory(R.string.pref_title_browser_settings) { 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 93b3a7d2..8201de2e 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 @@ -75,7 +75,7 @@ class StatusViewHolder( sensitive, previewListener, viewState.isMediaShow(status.id, status.sensitive), mediaViewHeight) - statusViewHelper.setupPollReadonly(status.poll.toViewData(), status.emojis, statusDisplayOptions.useAbsoluteTime) + statusViewHelper.setupPollReadonly(status.poll.toViewData(), status.emojis, statusDisplayOptions) setCreatedAt(status.createdAt) } @@ -89,7 +89,7 @@ class StatusViewHolder( itemView.statusContentWarningButton.hide() itemView.statusContentWarningDescription.hide() } else { - val emojiSpoiler = status.spoilerText.emojify(status.emojis, itemView.statusContentWarningDescription) + val emojiSpoiler = status.spoilerText.emojify(status.emojis, itemView.statusContentWarningDescription, statusDisplayOptions.animateEmojis) itemView.statusContentWarningDescription.text = emojiSpoiler itemView.statusContentWarningDescription.show() itemView.statusContentWarningButton.show() @@ -122,7 +122,7 @@ class StatusViewHolder( emojis: List, listener: LinkListener) { if (expanded) { - val emojifiedText = content.emojify(emojis, itemView.statusContent) + val emojifiedText = content.emojify(emojis, itemView.statusContent, statusDisplayOptions.animateEmojis) LinkHelper.setClickableText(itemView.statusContent, emojifiedText, mentions, listener) } else { LinkHelper.setClickableMentions(itemView.statusContent, mentions, listener) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt index 70bc694d..8ffe243e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt @@ -111,7 +111,8 @@ class ReportStatusesFragment : Fragment(R.layout.fragment_report_statuses), Inje useBlurhash = preferences.getBoolean("useBlurhash", true), cardViewMode = CardViewMode.NONE, confirmReblogs = preferences.getBoolean("confirmReblogs", true), - hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false) + hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false), + animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) ) adapter = StatusesAdapter(statusDisplayOptions, diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchAccountsAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchAccountsAdapter.kt index c135ad7d..b6bc9568 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchAccountsAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchAccountsAdapter.kt @@ -25,7 +25,7 @@ import com.keylesspalace.tusky.adapter.AccountViewHolder import com.keylesspalace.tusky.entity.Account import com.keylesspalace.tusky.interfaces.LinkListener -class SearchAccountsAdapter(private val linkListener: LinkListener) +class SearchAccountsAdapter(private val linkListener: LinkListener, private val animateAvatars: Boolean, private val animateEmojis: Boolean) : PagedListAdapter(ACCOUNT_COMPARATOR) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { @@ -37,7 +37,7 @@ class SearchAccountsAdapter(private val linkListener: LinkListener) override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { getItem(position)?.let { item -> (holder as AccountViewHolder).apply { - setupWithAccount(item) + setupWithAccount(item, animateAvatars, animateEmojis) setupLinkListener(linkListener) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchAccountsFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchAccountsFragment.kt index 714580f7..c453f97c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchAccountsFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchAccountsFragment.kt @@ -18,12 +18,23 @@ package com.keylesspalace.tusky.components.search.fragments import androidx.lifecycle.LiveData import androidx.paging.PagedList import androidx.paging.PagedListAdapter +import androidx.preference.PreferenceManager import com.keylesspalace.tusky.components.search.adapter.SearchAccountsAdapter import com.keylesspalace.tusky.entity.Account +import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.util.NetworkState +import kotlinx.android.synthetic.main.fragment_search.* class SearchAccountsFragment : SearchFragment() { - override fun createAdapter(): PagedListAdapter = SearchAccountsAdapter(this) + override fun createAdapter(): PagedListAdapter { + val preferences = PreferenceManager.getDefaultSharedPreferences(searchRecyclerView.context) + + return SearchAccountsAdapter( + this, + preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false), + preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) + ) + } override val networkStateRefresh: LiveData get() = viewModel.networkStateAccountRefresh 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 bc7ac2ba..6d96bb5a 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 @@ -87,7 +87,8 @@ class SearchStatusesFragment : SearchFragment BlocksAdapter(this) - Type.MUTES -> MutesAdapter(this) - Type.FOLLOW_REQUESTS -> FollowRequestsAdapter(this) - else -> FollowAdapter(this) + Type.BLOCKS -> BlocksAdapter(this, animateAvatar, animateEmojis) + Type.MUTES -> MutesAdapter(this, animateAvatar, animateEmojis) + Type.FOLLOW_REQUESTS -> FollowRequestsAdapter(this, animateAvatar, animateEmojis) + else -> FollowAdapter(this, animateAvatar, animateEmojis) } recyclerView.adapter = adapter 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 216ee228..5db3983f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java @@ -252,7 +252,8 @@ public class NotificationsFragment extends SFragment implements preferences.getBoolean("useBlurhash", true), CardViewMode.NONE, preferences.getBoolean("confirmReblogs", true), - preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false) + preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false), + preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) ); adapter = new NotificationsAdapter(accountManager.getActiveAccount().getAccountId(), diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java index 06ca56f3..ebaa8be8 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java @@ -252,7 +252,8 @@ public class TimelineFragment extends SFragment implements CardViewMode.INDENTED : CardViewMode.NONE, preferences.getBoolean("confirmReblogs", true), - preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false) + preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false), + preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) ); adapter = new TimelineAdapter(dataSource, statusDisplayOptions, this); diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java index 5ce66eee..0c415e1f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java @@ -134,7 +134,8 @@ public final class ViewThreadFragment extends SFragment implements CardViewMode.INDENTED : CardViewMode.NONE, preferences.getBoolean("confirmReblogs", true), - preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false) + preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false), + preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) ); adapter = new ThreadAdapter(statusDisplayOptions, this); } 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 a35885f3..d014ec0c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt +++ b/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt @@ -31,6 +31,7 @@ object PrefKeys { const val SHOW_CARDS_IN_TIMELINES = "showCardsInTimelines" const val CONFIRM_REBLOGS = "confirmReblogs" const val ENABLE_SWIPE_FOR_TABS = "enableSwipeForTabs" + const val ANIMATE_CUSTOM_EMOJIS = "animateCustomEmojis" const val CUSTOM_TABS = "customTabs" const val WELLBEING_LIMITED_NOTIFICATIONS = "wellbeingModeLimitedNotifications" diff --git a/app/src/main/java/com/keylesspalace/tusky/util/CustomEmojiHelper.kt b/app/src/main/java/com/keylesspalace/tusky/util/CustomEmojiHelper.kt index 679f38d3..7521afe4 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/CustomEmojiHelper.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/CustomEmojiHelper.kt @@ -16,11 +16,9 @@ @file:JvmName("CustomEmojiHelper") package com.keylesspalace.tusky.util -import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Paint -import android.graphics.drawable.BitmapDrawable -import android.graphics.drawable.Drawable +import android.graphics.drawable.* import android.text.SpannableStringBuilder import android.text.style.ReplacementSpan import android.view.View @@ -33,6 +31,8 @@ import com.keylesspalace.tusky.entity.Emoji import java.lang.ref.WeakReference import java.util.regex.Pattern +import androidx.preference.PreferenceManager +import com.keylesspalace.tusky.settings.PrefKeys /** * replaces emoji shortcodes in a text with EmojiSpans @@ -41,7 +41,7 @@ import java.util.regex.Pattern * @param view a reference to the a view the emojis will be shown in (should be the TextView, but parents of the TextView are also acceptable) * @return the text with the shortcodes replaced by EmojiSpans */ -fun CharSequence.emojify(emojis: List?, view: View) : CharSequence { +fun CharSequence.emojify(emojis: List?, view: View, animate: Boolean) : CharSequence { if(emojis.isNullOrEmpty()) return this @@ -56,9 +56,9 @@ fun CharSequence.emojify(emojis: List?, view: View) : CharSequence { builder.setSpan(span, matcher.start(), matcher.end(), 0) Glide.with(view) - .asBitmap() + .asDrawable() .load(url) - .into(span.getTarget()) + .into(span.getTarget(animate)) } } return builder @@ -97,11 +97,29 @@ class EmojiSpan(val viewWeakReference: WeakReference) : ReplacementSpan() } } - fun getTarget(): Target { - return object : CustomTarget() { - override fun onResourceReady(resource: Bitmap, transition: Transition?) { + fun getTarget(animate : Boolean): Target { + return object : CustomTarget() { + override fun onResourceReady(resource: Drawable, transition: Transition?) { viewWeakReference.get()?.let { view -> - imageDrawable = BitmapDrawable(view.context.resources, resource) + if(animate && resource is Animatable) { + val callback = resource.callback + + resource.callback = object: Drawable.Callback { + override fun unscheduleDrawable(p0: Drawable, p1: Runnable) { + callback?.unscheduleDrawable(p0, p1) + } + override fun scheduleDrawable(p0: Drawable, p1: Runnable, p2: Long) { + callback?.scheduleDrawable(p0, p1, p2) + } + override fun invalidateDrawable(p0: Drawable) { + callback?.invalidateDrawable(p0) + view.invalidate() + } + } + resource.start() + } + + imageDrawable = resource view.invalidate() } } diff --git a/app/src/main/java/com/keylesspalace/tusky/util/StatusDisplayOptions.kt b/app/src/main/java/com/keylesspalace/tusky/util/StatusDisplayOptions.kt index 93cbb3d9..ce19e00e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/StatusDisplayOptions.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/StatusDisplayOptions.kt @@ -16,5 +16,7 @@ data class StatusDisplayOptions( @get:JvmName("confirmReblogs") val confirmReblogs: Boolean, @get:JvmName("hideStats") - val hideStats: Boolean + val hideStats: Boolean, + @get:JvmName("animateEmojis") + val animateEmojis: Boolean ) \ No newline at end of file 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 db0441bd..82210029 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt @@ -243,7 +243,7 @@ class StatusViewHelper(private val itemView: View) { } } - fun setupPollReadonly(poll: PollViewData?, emojis: List, useAbsoluteTime: Boolean) { + fun setupPollReadonly(poll: PollViewData?, emojis: List, statusDisplayOptions: StatusDisplayOptions) { val pollResults = listOf( itemView.findViewById(R.id.status_poll_option_result_0), itemView.findViewById(R.id.status_poll_option_result_1), @@ -261,10 +261,10 @@ class StatusViewHelper(private val itemView: View) { val timestamp = System.currentTimeMillis() - setupPollResult(poll, emojis, pollResults) + setupPollResult(poll, emojis, pollResults, statusDisplayOptions.animateEmojis) pollDescription.visibility = View.VISIBLE - pollDescription.text = getPollInfoText(timestamp, poll, pollDescription, useAbsoluteTime) + pollDescription.text = getPollInfoText(timestamp, poll, pollDescription, statusDisplayOptions.useAbsoluteTime) } } @@ -292,7 +292,7 @@ class StatusViewHelper(private val itemView: View) { } - private fun setupPollResult(poll: PollViewData, emojis: List, pollResults: List) { + private fun setupPollResult(poll: PollViewData, emojis: List, pollResults: List, animateEmojis: Boolean) { val options = poll.options for (i in 0 until Status.MAX_POLL_OPTIONS) { @@ -300,7 +300,7 @@ class StatusViewHelper(private val itemView: View) { val percent = calculatePercent(options[i].votesCount, poll.votersCount, poll.votesCount) val pollOptionText = buildDescription(options[i].title, percent, pollResults[i].context) - pollResults[i].text = pollOptionText.emojify(emojis, pollResults[i]) + pollResults[i].text = pollOptionText.emojify(emojis, pollResults[i], animateEmojis) pollResults[i].visibility = View.VISIBLE val level = percent * 100 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c1615e04..6aa4a3a8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -243,6 +243,7 @@ Show indicator for bots Animate GIF avatars Show colorful gradients for hidden media + Animate custom emojis Timeline filtering Tabs From 4d856365f9559f86b3986214a64fe126af8f7da6 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Sun, 7 Feb 2021 16:40:09 +0100 Subject: [PATCH 45/81] cleanup drafts when user logs out (#2067) * cleanup drafts when user logs out * delete unused method * remove unneeded sorting from loadDraftsSingle --- .../main/java/com/keylesspalace/tusky/MainActivity.kt | 5 +++++ .../tusky/components/drafts/DraftHelper.kt | 10 ++++++++++ .../main/java/com/keylesspalace/tusky/db/DraftDao.kt | 4 ++++ 3 files changed, 19 insertions(+) diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt index 3f41ca00..3fefe4bf 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt @@ -53,6 +53,7 @@ import com.keylesspalace.tusky.components.announcements.AnnouncementsActivity import com.keylesspalace.tusky.components.compose.ComposeActivity import com.keylesspalace.tusky.components.compose.ComposeActivity.Companion.canHandleMimeType import com.keylesspalace.tusky.components.conversation.ConversationsRepository +import com.keylesspalace.tusky.components.drafts.DraftHelper import com.keylesspalace.tusky.components.drafts.DraftsActivity import com.keylesspalace.tusky.components.notifications.NotificationHelper import com.keylesspalace.tusky.components.preference.PreferencesActivity @@ -104,6 +105,9 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje @Inject lateinit var appDb: AppDatabase + @Inject + lateinit var draftHelper: DraftHelper + private lateinit var header: AccountHeaderView private var notificationTabPosition = 0 @@ -611,6 +615,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje NotificationHelper.deleteNotificationChannelsForAccount(activeAccount, this) cacheUpdater.clearForUser(activeAccount.id) conversationRepository.deleteCacheForAccount(activeAccount.id) + draftHelper.deleteAllDraftsAndAttachmentsForAccount(activeAccount.id) removeShortcut(this, activeAccount) val newAccount = accountManager.logActiveAccountOut() if (!NotificationHelper.areNotificationsEnabled(this, accountManager)) { 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 6f5f9005..5328fef7 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 @@ -29,6 +29,7 @@ import com.keylesspalace.tusky.entity.NewPoll import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.util.IOUtils import io.reactivex.Completable +import io.reactivex.Observable import io.reactivex.Single import io.reactivex.schedulers.Schedulers import java.io.File @@ -126,6 +127,15 @@ class DraftHelper @Inject constructor( .andThen(draftDao.delete(draft.id)) } + fun deleteAllDraftsAndAttachmentsForAccount(accountId: Long) { + draftDao.loadDraftsSingle(accountId) + .flatMapObservable { Observable.fromIterable(it) } + .flatMapCompletable { draft -> + deleteDraftAndAttachments(draft) + }.subscribeOn(Schedulers.io()) + .subscribe() + } + fun deleteAttachments(draft: DraftEntity): Completable { return Completable.fromCallable { draft.attachments.forEach { attachment -> diff --git a/app/src/main/java/com/keylesspalace/tusky/db/DraftDao.kt b/app/src/main/java/com/keylesspalace/tusky/db/DraftDao.kt index 105fd7c5..065af1ae 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/DraftDao.kt +++ b/app/src/main/java/com/keylesspalace/tusky/db/DraftDao.kt @@ -32,9 +32,13 @@ interface DraftDao { @Query("SELECT * FROM DraftEntity WHERE accountId = :accountId ORDER BY id ASC") fun loadDrafts(accountId: Long): DataSource.Factory + @Query("SELECT * FROM DraftEntity WHERE accountId = :accountId") + fun loadDraftsSingle(accountId: Long): Single> + @Query("DELETE FROM DraftEntity WHERE id = :id") fun delete(id: Int): Completable @Query("SELECT * FROM DraftEntity WHERE id = :id") fun find(id: Int): Single + } From b7fc4abd50d60b814b14e967a353acb9455ca7dd Mon Sep 17 00:00:00 2001 From: hiohlan Date: Sun, 7 Feb 2021 14:13:58 +0000 Subject: [PATCH 46/81] Translated using Weblate (Thai) Currently translated at 100.0% (456 of 456 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/th/ --- app/src/main/res/values-th/strings.xml | 33 ++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index dc5487a8..fb5bd52b 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -455,4 +455,37 @@ ซ่อนการแจ้งเตือน ปิดเสียงการแจ้งเตือนจาก %s ซ่อนหัวข้อของแถบเครื่องมือด้านบน + ล้มเหลวในการส่งโพสต์นี้! + ข้อมูลบางอย่างที่อาจส่งผลต่อสุขภาพจิตของคุณจะถูกซ่อนไว้ซึ่งรวมถึง: +\n +\n- การแจ้งเตือน ชื่นชอบ/ดัน/ติดตาม +\n- จำนวนการ ชื่นชอบ/ดัน บนโพสต์ +\n- สถิติ ผู้ติดตาม/โพสต์ ในโปรไฟล์ +\n +\n การแจ้งเตือนแบบพุชจะไม่ได้รับผลกระทบ แต่คุณสามารถตรวจสอบการตั้งค่าการแจ้งเตือนได้ด้วยตนเอง + แจ้งเตือน Limit timeline + แจ้งเตือน Review + ใครบางคนที่ฉันได้ติดตาม ได้เผยแพร่โพสต์ใหม่ + ฟีเจอร์ฉบับร่างใน Tusky ได้รับการออกแบบใหม่ทั้งหมดเพื่อให้เร็วขึ้นเป็นมิตรกับผู้ใช้มากขึ้นและบั๊กน้อยลง +\n คุณยังสามารถเข้าถึงฉบับร่างเก่าผ่านปุ่มในหน้าฉบับร่างใหม่ แต่จะถูกลบออกในการอัปเดตในอนาคต! + ซ่อนสถิติเชิงปริมาณในโปรไฟล์ + ซ่อนสถิติเชิงปริมาณของโพสต์ + สุขภาวะ + บันทึกส่วนตัวของคุณเกี่ยวกับบัญชีนี้ + แจ้งเตือน เมื่อคนที่คุณติดตาม ได้เผยแพร่โพสต์ใหม่ + โพสต์ที่คุณได้ร่างตอบไว้ ถูกลบแลัว + ลบฉบับร่างแล้ว + ล้มเหลวในการโหลดข้อมูลตอบกลับ + ฉบับร่างเก่า + คุณต้องการลบลิสต์ %s ใช่ไหม\? + คุณไม่สามารถอัปโหลดไฟล์แนบมากกว่า %1$d ได้ + บันทึกแล้ว! + ไม่มีประกาศ + ไม่มีกำหนด + ระยะเวลา + ไฟล์แนบ + เสียง + โพสต์ใหม่ + %s พึ่งโพสต์ + ประกาศ \ No newline at end of file From 253ebb80acf80d6ef5fda8208205a06de595bfdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Gera?= Date: Sun, 7 Feb 2021 14:13:58 +0000 Subject: [PATCH 47/81] Translated using Weblate (Hungarian) Currently translated at 100.0% (456 of 456 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/hu/ Translated using Weblate (Hungarian) Currently translated at 100.0% (456 of 456 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/hu/ --- app/src/main/res/values-hu/strings.xml | 35 +++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 0086e299..1a7f29e5 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -237,12 +237,12 @@ Listák Törlés Fiók zárolása - Elmented a vázlatot? + Elmented a piszkozatot\? Tülk elküldése… A tülk elküldése nem sikerült Tülkök elküldése Küldés megszakítva - A tülk másolatát elmentettük a vázlataid közé + A tülk másolatát elmentettük a piszkozataid közé Szerkesztés A %s szervernek nincsenek egyedi emoji-jai Vágólapra másolva @@ -442,7 +442,7 @@ Lista kiválasztása Lista A hangfájloknak kisebbnek kell lenniük, mint 40 MB. - Nincs egy vázlatod sem. + Nincs egy piszkozatod sem. Nincs egy ütemezett tülköd sem. A Mastodonban a legrövidebb ütemezhető időintervallum 5 perc. Követési kérelmek @@ -476,4 +476,33 @@ 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 + Piszkozat törölve + Nem sikerült a Válasz információit betölteni + Régi Piszkozatok + A Tusky piszkozat funkcióját teljesen újraterveztük, hogy gyorsabb, felhasználóbarátabb és hibamentesebb legyen. +\nTovábbra is elérheted a régi piszkozataidat egy gombbal az új piszkozatok képernyőjén, de ezeket egy későbbi frissítésben el fogjuk törölni! + Ez a tülk nem küldődött el! + Tényleg le akarod törölni a %s listát\? + 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 + 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.: +\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 +\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 + Jóllét \ No newline at end of file From d87ac1caee9502d74c301abb02b8220f6a216242 Mon Sep 17 00:00:00 2001 From: Ho Nhat Duy Date: Sun, 7 Feb 2021 14:13:58 +0000 Subject: [PATCH 48/81] Translated using Weblate (Vietnamese) Currently translated at 100.0% (456 of 456 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/ --- app/src/main/res/values-vi/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index d0891bb7..0bf68062 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -134,7 +134,7 @@ Tạo bình chọn Thêm tệp Mở trong trình duyệt - Bộ sưu tập + Thư viện Yêu cầu theo dõi Máy chủ đã ẩn Người dùng đã chặn @@ -289,7 +289,7 @@ Cộng đồng xem thêm Trả lời @%s - Bộ sưu tập + Thư viện Luôn hiện nội dung bị ẩn Luôn hiện nội dung nhạy cảm Đang theo dõi bạn From 1ca9154d1bae7a1aed202023c03b6b380ad87585 Mon Sep 17 00:00:00 2001 From: Chaitanya Date: Sun, 7 Feb 2021 14:13:58 +0000 Subject: [PATCH 49/81] Translated using Weblate (Hindi) Currently translated at 83.5% (381 of 456 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/hi/ --- app/src/main/res/values-hi/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 20b6e5d0..3d4fe2c3 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -4,7 +4,7 @@ पसंदीदा प्रारूप लॉग आउट - पसंद + प्राथमिकताएं खाता प्राथमिकताएं प्रोफाइल एडिट करें खोज From 62a2027cb6a225f8c79f399a4f3c02842af46729 Mon Sep 17 00:00:00 2001 From: Vegard Skjefstad Date: Sun, 7 Feb 2021 14:13:58 +0000 Subject: [PATCH 50/81] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (457 of 457 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/nb_NO/ --- 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 36c7dc31..f9083c8a 100644 --- a/app/src/main/res/values-no-rNB/strings.xml +++ b/app/src/main/res/values-no-rNB/strings.xml @@ -496,4 +496,5 @@ KladdfunksjonaLiteten i Tusky er skrevet om og er nå kjappere, mer brukervennlig, og med færre feil. \nGamle kladder er fortsatt tilgjengelige via en knapp på den nye kladdskjermen, men de vil bli fjernet i en fremtidig oppdatering! Sending av toot feilet! + Animer egendefinerte emojis \ No newline at end of file From ba986379d4250bbc817e96ac14e1a6714fcb3998 Mon Sep 17 00:00:00 2001 From: Lafudoci Date: Sun, 7 Feb 2021 14:13:59 +0000 Subject: [PATCH 51/81] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (457 of 457 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/zh_Hant/ Translated using Weblate (Chinese (Hong Kong)) Currently translated at 97.5% (446 of 457 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/zh_Hant_HK/ Translated using Weblate (Chinese (Hong Kong)) Currently translated at 84.2% (385 of 457 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/zh_Hant_HK/ --- app/src/main/res/values-zh-rHK/strings.xml | 164 +++++++++++++++++---- app/src/main/res/values-zh-rTW/strings.xml | 152 +++++++++++++++---- 2 files changed, 256 insertions(+), 60 deletions(-) diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 5c50933d..58c63891 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -1,24 +1,24 @@ - 應用程式出現異常 - 網絡請求出錯,請檢查互聯網連接並重試 - 內容不能為空 + 應用程式出現異常。 + 網絡請求出錯,請檢查互聯網連接並重試! + 內容不能為空。 該域名無效 - 無法連接此伺服器 - 沒有可用的瀏覽器 - 認證過程出現未知錯誤 - 授權被拒絕 - 無法獲取登入資訊 + 無法連接此伺服器。 + 沒有可用的瀏覽器。 + 認證過程出現未知錯誤。 + 授權被拒絕。 + 無法獲取登入資訊。 嘟文太長了! - 檔案大小限制 8MB - 影片大小限制 40MB - 無法上傳此類型的檔案 - 此檔案無法開啟 - 需要授予 Tusky 讀取媒體檔案的權限 - 需要授予 Tusky 寫入儲存空間的權限 - 無法在嘟文中同時插入影片和圖片 - 媒體檔案上傳失敗 - 嘟文發送時出錯 + 檔案大小限制 8MB。 + 影片大小限制 40MB。 + 無法上傳此類型的檔案。 + 此檔案無法開啟。 + 需要授予 Tusky 讀取媒體檔案的權限。 + 需要授予 Tusky 寫入儲存空間的權限。 + 無法在嘟文中同時插入影片和圖片。 + 媒體檔案上傳失敗。 + 嘟文發送時出錯。 主頁 通知設定 本站時間軸 @@ -47,10 +47,10 @@ 摺疊內容 展開 摺疊 - 沒有內容 - 還沒有內容,向下拉動即可重新整理 + 沒有內容。 + 還沒有內容,向下拉動即可重新整理! %s 轉嘟了你的嘟文 - %s 收藏了你的嘟文 + %s 把你的嘟文加入了最愛 %s 關注了你 檢舉使用者 @%s 的濫用行為 更多評論? @@ -112,12 +112,12 @@ 話題 打開轉嘟用戶主頁 顯示轉嘟 - 顯示收藏 + 顯示最愛 話題 提及 連結 打開媒體 #%d - 正在下載 %1$s… + 正在下載 %1$s 複製連結 打開為 %s 分享為 … @@ -130,8 +130,8 @@ 已解除封鎖 已解除靜音 已檢舉! - 成功送出回覆 - 域名 + 成功送出回覆。 + 哪一個域名? 有什麼新鮮事? 敏感內容警告 暱稱 @@ -143,8 +143,14 @@ 標題 什麼是站點? 正在連線… - 請輸入你帳號所在的 Mastodon 站點的域名或地址 - 正在完成上傳… + 輸入你帳號所在的 Mastodon 站點的域名或地址,譬如 mastodon.social、icosahedron.website、social.tchncs.de 和 更多 +\n +\n如果你還沒有帳號,你可以輸入你想要加入的域名並在此建立新帳號。 +\n +\n一個站點是一個託管你的帳號的地方,但是你可以很容易的跟不同站台的人們交流,就像是在同一個站台一樣。 +\n +\n更多資訊可以在 joinmastodon.org 查看。 + 正在完成上傳 正在上傳… 下載 移除關注請求? @@ -165,7 +171,7 @@ 被提及 有新的關注者 嘟文被轉嘟 - 嘟文被收藏 + 嘟文被加入收藏 投票已結束 外觀 佈景主題 @@ -210,7 +216,7 @@ 轉嘟 當有使用者轉嘟了我的嘟文時 收藏 - 當有使用者收藏了我的嘟文時 + 當有使用者把我的嘟文加入收藏時 投票 當我參與的投票結束時 %s 提及了你 @@ -323,7 +329,7 @@ 標籤 內容 嘟文顯示精確時間 - 以下資訊可能並不完整,要檢視完整資料請使用瀏覽器開啟 + 以下資訊可能並不完整,要檢視完整資料請使用瀏覽器開啟。 取消置頂 置頂 @@ -332,8 +338,8 @@ <b>%s</b> 次轉嘟 - 轉嘟 - 收藏 + 轉嘟由 + 收藏由 %1$s %1$s 和 %2$s %1$s, %2$s 和 %3$d 等人 @@ -402,4 +408,100 @@ 話題 關注請求 編輯 + 動態自訂表情符號 + 在隱藏的媒體上使用漸變色彩 + 動態 GIF 頭像 + 我關注的人有新嘟文 + 已送出關注請求 + 隱藏通知 + 靜音 @%s? + 封鎖 @%s? + 隱藏整個網域 + 確定要封鎖 %s 所有內容?你將不會在任何公開時間軸或是通知中看到來自這個網域的內容。你的關注者若來自這個網域則將會被移除。 + %s 已解除隱藏 + 重設 + 排程嘟文 + 排程的嘟文 + 取消靜音對話 + 靜音對話 + 取消靜音 %s + 靜音 %s + 靜音來自 %s 的通知 + 取消靜音來自 %s 的通知 + 取消靜音 %s + 新增投票 + 被隱藏的網域 + 被加入書籤 + 我的書籤 + 書籤 + 我的書籤 + %s 剛剛發了新嘟文 + %s 希望可以關注你 + 公告 + 已排程的嘟文 + 被隱藏的網域 + 聲音檔大小限制 40MB。 + 完整字詞 + 你的草稿欲回覆的原嘟文已被刪除 + 草稿已刪除 + 載入回覆資訊失敗 + 舊的草稿 + 這條嘟文發送失敗! + 你確定要刪除列表 %s? + 你無法上傳超過 %1$d 媒體附件。 + 已儲存! + 你對此帳號的個人註記 + 隱藏頂端工具列的標題 + 在轉嘟時提示確認 + 在時間軸中顯示連結預覽 + Mastodon 的最短發文間隔限制為 5 分鐘。 + 沒有公告。 + 你沒有任何已排程的嘟文。 + 你沒有任何草稿。 + 尋找嘟文時發生錯誤 %s + 選項 %d + 多個選項 + 新增選項 + 7 天 + 3 天 + 1 天 + 6 小時 + 1 小時 + 30 分鐘 + 5 分鐘 + 無限期 + 期間 + 投票 + 啟用在分頁間切換的滑動手勢 + 顯示通知過濾器 + 搜尋失敗 + 帳號 + 擷取狀態失敗 + 回報失敗 + 轉送至 %s + 額外的評論 + 成功回報 @%s + 完成 + 返回 + 繼續 + + %s 人 + + + 列表 + 選擇列表 + 加上話題標籤 + 投票選項: %1$s, %2$s, %3$s, %4$s; %5$s + Google 目前的表情符號包 + 總是顯示被標注為內容警告的嘟文 + 附件 + 錄音 + 由 Tusky 提供 + Tusky %s + 當你關注的人發布新嘟文時通知 + 新嘟文 + 關注請求的通知 + 底端 + 頂端 + 主要導覽列的位置 \ No newline at end of file diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 6d690ace..46411116 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -1,24 +1,24 @@ - 應用程式出現異常 - 網絡請求出錯,請檢查互聯網連接並重試 - 內容不能為空 + 應用程式出現異常。 + 網絡請求出錯,請檢查互聯網連接並重試! + 內容不能為空。 該域名無效 - 無法連接此伺服器 - 沒有可用的瀏覽器 - 認證過程出現未知錯誤 - 授權被拒絕 - 無法獲取登入資訊 + 無法連接此伺服器。 + 沒有可用的瀏覽器。 + 認證過程出現未知錯誤。 + 授權被拒絕。 + 無法獲取登入資訊。 嘟文太長了! - 檔案大小限制 8MB - 影片大小限制 40MB - 無法上傳此類型的檔案 - 此檔案無法開啟 - 需要授予 Tusky 讀取媒體檔案的權限 - 需要授予 Tusky 寫入儲存空間的權限 - 無法在嘟文中同時插入影片和圖片 - 媒體檔案上傳失敗 - 嘟文發送時出錯 + 檔案大小限制 8MB。 + 影片大小限制 40MB。 + 無法上傳此類型的檔案。 + 此檔案無法開啟。 + 需要授予 Tusky 讀取媒體檔案的權限。 + 需要授予 Tusky 寫入儲存空間的權限。 + 無法在嘟文中同時插入影片和圖片。 + 媒體檔案上傳失敗。 + 嘟文發送時出錯。 主頁 通知 本站時間軸 @@ -47,8 +47,8 @@ 摺疊內容 展開 摺疊 - 沒有內容 - 還沒有內容,向下拉動即可重新整理 + 沒有內容。 + 還沒有內容,向下拉動即可重新整理! %s 轉嘟了你的嘟文 %s 收藏了你的嘟文 %s 關注了你 @@ -112,12 +112,12 @@ 話題 打開轉嘟用戶主頁 顯示轉嘟 - 顯示收藏 + 顯示最愛 話題 提及 連結 打開媒體 #%d - 正在下載 %1$s… + 正在下載 %1$s 複製連結 打開為 %s 分享為 … @@ -130,8 +130,8 @@ 已解除封鎖 已解除靜音 已發送! - 成功送出回覆 - 域名 + 成功送出回覆。 + 哪一個域名? 有什麼新鮮事? 敏感內容警告 暱稱 @@ -143,8 +143,14 @@ 標題 什麼是站點? 正在連線… - 請輸入你帳號所在的 Mastodon 站點的域名或地址 - 正在完成上傳… + 輸入你帳號所在的 Mastodon 站點的域名或地址,譬如 mastodon.social、icosahedron.website、social.tchncs.de 和 更多 +\n +\n如果你還沒有帳號,你可以輸入你想要加入的域名並在此建立新帳號。 +\n +\n一個站點是一個託管你的帳號的地方,但是你可以很容易的跟不同站台的人們交流,就像是在同一個站台一樣。 +\n +\n更多資訊可以在 joinmastodon.org 查看。 + 正在完成上傳 正在上傳… 下載 移除關注請求? @@ -165,7 +171,7 @@ 被提及 有新的關注者 嘟文被轉嘟 - 嘟文被收藏 + 嘟文被加入收藏 投票已結束 外觀 佈景主題 @@ -210,7 +216,7 @@ 轉嘟 當有使用者轉嘟了我的嘟文時 收藏 - 當有使用者收藏了我的嘟文時 + 當有使用者把我的嘟文加入收藏時 投票 當我參與的投票結束時 %s 提及了你 @@ -323,7 +329,7 @@ 標籤 內容 嘟文顯示精確時間 - 以下資訊可能並不完整,要檢視完整資料請使用瀏覽器開啟 + 以下資訊可能並不完整,要檢視完整資料請使用瀏覽器開啟。 取消置頂 置頂 @@ -333,7 +339,7 @@ <b>%s</b> 次轉嘟 轉嘟 - 收藏 + 收藏由 %1$s %1$s 和 %2$s %1$s, %2$s 和 %3$d 等人 @@ -428,4 +434,92 @@ 編輯 書籤 音檔必需小於40MB。 + Tusky 的草稿功能已重新設計,更快、更好用、更少問題。 +\n 你還是可以在草稿頁面中查看你的先前的舊草稿,但它們在未來的某次更新中將會被移除! + 隱藏個人頁面中的狀態數量資訊 + 隱藏貼文上的狀態數量資訊 + 限制時間軸通知 + 檢查通知設定 + 有些資訊可能會影響你的心理健康將會被隱藏。包括: +\n +\n- 收藏/轉嘟/關注 通知 +\n- 收藏/轉嘟 數量 +\n- 關注/貼文 在個人頁面的狀態 +\n +\n推播通知不會受到影響,但你可以手動檢查你的通知設定。 + 數位健康 + + %s 人 + + + %s 剛剛發了新嘟文 + %s 請求關注你 + 動態自訂表情符號 + 你的草稿欲回覆的原嘟文已被刪除 + 草稿已刪除 + 載入回覆資訊失敗 + 舊的草稿 + 這條嘟文發送失敗! + 附件 + 錄音 + 你確定要刪除列表 %s? + 7 天 + 3 天 + 1 天 + 6 小時 + 1 小時 + 30 分鐘 + 5 分鐘 + 無限期 + 期間 + 你無法上傳超過 %1$d 媒體附件。 + 當你關注的人發布新嘟文時通知 + 新嘟文 + 我關注的人有新嘟文 + 沒有公告。 + 公告 + 已儲存! + 你對此帳號的個人註記 + 隱藏頂端工具列的標題 + 隱藏通知 + 靜音來自 %s 的通知 + 取消靜音來自 %s 的通知 + 取消靜音 %s + 取消靜音 %s + 底端 + 頂端 + 主要導覽列的位置 + 在隱藏的媒體上使用漸變色彩 + 加上話題標籤 + 在轉嘟時提示確認 + 在時間軸中顯示連結預覽 + 啟用在分頁間切換的滑動手勢 + 關注請求的通知 + 已送出關注請求 + 靜音 @%s? + 封鎖 @%s? + 取消靜音對話 + 靜音對話 + Mastodon 的最短發文間隔限制為 5 分鐘。 + 你沒有任何草稿。 + 你沒有任何已排程的嘟文。 + 列表 + 選擇列表 + 被加入書籤 + 我的書籤 + 書籤 + 由 Tusky 提供 + 尋找嘟文時發生錯誤 %s + 重設 + 排程嘟文 + 排程的嘟文 + 已排程的嘟文 + 選項 %d + 多個選項 + 新增選項 + 投票 + 新增投票 + 總是顯示被標注為內容警告的嘟文 + 搜尋失敗 + 帳號 \ No newline at end of file From c178804efe96cd9ef36f70b4181678c841620006 Mon Sep 17 00:00:00 2001 From: Ivan Kupalov Date: Tue, 9 Feb 2021 18:41:40 +0100 Subject: [PATCH 52/81] Fix TalkBack not initializing old views in recycler (#2036) --- .../tusky/fragment/TimelineFragment.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java index ebaa8be8..6f42dd15 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java @@ -24,11 +24,13 @@ import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityManager; import android.widget.ProgressBar; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.arch.core.util.Function; +import androidx.core.content.ContextCompat; import androidx.core.util.Pair; import androidx.core.widget.ContentLoadingProgressBar; import androidx.lifecycle.Lifecycle; @@ -95,6 +97,7 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.ListIterator; +import java.util.Objects; import java.util.concurrent.TimeUnit; import javax.inject.Inject; @@ -1471,9 +1474,21 @@ public class TimelineFragment extends SFragment implements } }; + AccessibilityManager a11yManager; + boolean talkBackWasEnabled; + @Override public void onResume() { super.onResume(); + a11yManager = Objects.requireNonNull( + ContextCompat.getSystemService(requireContext(), AccessibilityManager.class) + ); + boolean wasEnabled = this.talkBackWasEnabled; + talkBackWasEnabled = a11yManager.isEnabled(); + Log.d(TAG, "talkback was enabled: " + wasEnabled + ", now " + talkBackWasEnabled); + if (talkBackWasEnabled && !wasEnabled) { + this.adapter.notifyDataSetChanged(); + } startUpdateTimestamp(); } From b380653d71a288ed27801b53261e38c53cdf77e3 Mon Sep 17 00:00:00 2001 From: Connyduck Date: Mon, 8 Feb 2021 17:38:25 +0000 Subject: [PATCH 53/81] Translated using Weblate (German) Currently translated at 97.3% (445 of 457 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/de/ --- app/src/main/res/values-de/strings.xml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index decfff34..6f2c7999 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -470,4 +470,22 @@ Titel der Hauptnavigation verstecken Im Moment gibt es keine Ankündigungen. Ankündigungen + Der Beitrag auf den du antworten willst wurde gelöscht + Entwurf gelöscht + Alte Entwürfe + Das \"Entwürfe\"-Feature in Tusky wurde komplett neu gestaltet um schneller und benutzerfreundlicher zu sein. +\nDu kannst deine alten Entwürfe noch hinter einem Button bei den neuen Entwürfen finden, aber sie werden mit einem zukünftigen Update gelöscht! + Dieser Beitrag konnte nicht gesendet werden! + Willst du die Liste %s wirklich löschen\? + Du kannst nicht mehr als %1$d Anhänge hochladen. + Wohlbefinden + Dauer + Für immer + Anhänge + Audio + Benachrichtigungen, wenn jemand, den ich abonniert habe, etwas Neues veröffentlicht + Neue Beiträge + GIF-Emojis animieren + Jemand, den ich abonniert habe, etwas Neues veröffentlicht + % hat gerade etwas gepostet \ No newline at end of file From a4a6cce16c8b9fff867a18f8850cb8f7fa9de042 Mon Sep 17 00:00:00 2001 From: Ho Nhat Duy Date: Mon, 8 Feb 2021 17:38:25 +0000 Subject: [PATCH 54/81] Translated using Weblate (Vietnamese) Currently translated at 100.0% (457 of 457 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/ --- app/src/main/res/values-vi/strings.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 0bf68062..1182609a 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -352,7 +352,7 @@ Cuộc bình chọn bạn tạo đã kết thúc Cuộc bình chọn của bạn đã kết thúc Bình chọn - Kết thúc + xong kết thúc lúc %s %s người @@ -488,4 +488,5 @@ Tính năng lên lịch đăng tút của Tusky sẽ được thiết kế lại hoàn toàn để nhanh hơn, thân thiện và ít lỗi hơn. \nBạn vẫn có thể xem lại bản nháp cũ nhưng chúng sẽ bị xóa bỏ trong bản cập nhật tương lai! Đăng tút không thành công! + Emoji động \ No newline at end of file From be77d91e9110697c6874ecf9a06804ed98e815d1 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Tue, 9 Feb 2021 19:45:43 +0100 Subject: [PATCH 55/81] change draft attachment directory (#2070) --- .../tusky/components/drafts/DraftHelper.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) 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 5328fef7..5038ac00 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 @@ -59,13 +59,19 @@ class DraftHelper @Inject constructor( ): Completable { return Single.fromCallable { - val draftDirectory = context.getExternalFilesDir("Tusky") + val externalFilesDir = context.getExternalFilesDir("Tusky") - if (draftDirectory == null || !(draftDirectory.exists())) { + if (externalFilesDir == null || !(externalFilesDir.exists())) { Log.e("DraftHelper", "Error obtaining directory to save media.") throw Exception() } + val draftDirectory = File(externalFilesDir, "Drafts") + + if (!draftDirectory.exists()) { + draftDirectory.mkdir() + } + val uris = mediaUris.map { uriString -> uriString.toUri() }.map { uri -> From 8c13673662efc2e4ff9b274ebc2ec873f4582bc1 Mon Sep 17 00:00:00 2001 From: Conny Duck Date: Tue, 9 Feb 2021 19:46:35 +0100 Subject: [PATCH 56/81] Release 79 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index fa6fc401..e9a382e1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -20,8 +20,8 @@ android { applicationId APP_ID minSdkVersion 21 targetSdkVersion 29 - versionCode 78 - versionName "13.1" + versionCode 79 + versionName "14.0 beta 1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true From 7a14296d04a8566171ce03df3f0e82ce9291ac81 Mon Sep 17 00:00:00 2001 From: Conny Duck Date: Tue, 9 Feb 2021 20:07:34 +0100 Subject: [PATCH 57/81] prepare changelog for Tusky 14.0 --- fastlane/metadata/android/en-US/changelogs/80.txt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 fastlane/metadata/android/en-US/changelogs/80.txt diff --git a/fastlane/metadata/android/en-US/changelogs/80.txt b/fastlane/metadata/android/en-US/changelogs/80.txt new file mode 100644 index 00000000..cfbdb490 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/80.txt @@ -0,0 +1,9 @@ +Tusky v14.0 + +- Add option to be notified when a followed user posts - click the bell icon on their profile! (Mastodon 3.3.0 feature) +- New Drafts implementation: The draft feature in Tusky has been completely redesigned to be faster, more user friendly and less buggy. +- Wellbeing mode: A new wellbeing mode that allows you to limit certain Tusky features has been added. You can enable the feature in the Preferences. +- Animated emoji support: Tusky can now animate custom emojis. The feature is off by default, you can enable it in the Preferences. +- Support for timed mutes: It is now possible to mute users for only a certain time. +- a lot of bug fixes and small improvements, especially for Pleroma compatibility +- improved translations From 62732bd04554a851abe59565319ada4abd429566 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Fri, 12 Feb 2021 16:40:42 +0100 Subject: [PATCH 58/81] Shorten changelog for TUsky v14.0 Closes #1834 --- fastlane/metadata/android/en-US/changelogs/80.txt | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/fastlane/metadata/android/en-US/changelogs/80.txt b/fastlane/metadata/android/en-US/changelogs/80.txt index cfbdb490..14d28f0a 100644 --- a/fastlane/metadata/android/en-US/changelogs/80.txt +++ b/fastlane/metadata/android/en-US/changelogs/80.txt @@ -1,9 +1,7 @@ Tusky v14.0 -- Add option to be notified when a followed user posts - click the bell icon on their profile! (Mastodon 3.3.0 feature) -- New Drafts implementation: The draft feature in Tusky has been completely redesigned to be faster, more user friendly and less buggy. -- Wellbeing mode: A new wellbeing mode that allows you to limit certain Tusky features has been added. You can enable the feature in the Preferences. -- Animated emoji support: Tusky can now animate custom emojis. The feature is off by default, you can enable it in the Preferences. -- Support for timed mutes: It is now possible to mute users for only a certain time. -- a lot of bug fixes and small improvements, especially for Pleroma compatibility -- improved translations +- Get notified when a followed user posts - click the bell icon on their profile! (Mastodon 3.3.0 feature) +- The draft feature in Tusky has been completely redesigned to be faster, more user friendly and less buggy. +- A new wellbeing mode that allows you to limit certain Tusky features has been added. +- Tusky can now animate custom emojis. +Full changelog: https://github.com/tuskyapp/Tusky/releases From bdb468d02dcd4e42b10b047b76f1d09dd081a612 Mon Sep 17 00:00:00 2001 From: Vegard Skjefstad Date: Sun, 14 Feb 2021 03:55:16 +0000 Subject: [PATCH 59/81] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (12 of 12 strings) Translation: Tusky/Tusky description Translate-URL: https://weblate.tusky.app/projects/tusky/tusky-app/nb_NO/ --- fastlane/metadata/android/nb_NO/changelogs/80.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 fastlane/metadata/android/nb_NO/changelogs/80.txt diff --git a/fastlane/metadata/android/nb_NO/changelogs/80.txt b/fastlane/metadata/android/nb_NO/changelogs/80.txt new file mode 100644 index 00000000..11b5b37d --- /dev/null +++ b/fastlane/metadata/android/nb_NO/changelogs/80.txt @@ -0,0 +1,7 @@ +Tusky v14.0 + +- Mulighet for å bli varslet dersom en bruker du følger publiserer en ny toot - trykk på bjelle-ikonet på profilen deres (krever Mastodon 3.3.0) +- Ny og forbedret kladd-funksjonalitet. +- Velværemodus: Kan brukes til å begrense utvalgt funksjonalitet i Tusky. Du kan aktivere velværemodus i innstillinger. +- Støtte for animerte emojis. Dette er skrudd av som standard, men du kan skru det på i innstillinger. +- Tidsbestemt demping. Det er mulig å dempe brukere i en angitt periode. From b4a397f036a167eb66d0cf550fa2c117e64f1026 Mon Sep 17 00:00:00 2001 From: Ho Nhat Duy Date: Sun, 14 Feb 2021 03:55:16 +0000 Subject: [PATCH 60/81] Translated using Weblate (Vietnamese) Currently translated at 100.0% (12 of 12 strings) Translation: Tusky/Tusky description Translate-URL: https://weblate.tusky.app/projects/tusky/tusky-app/vi/ --- fastlane/metadata/android/vi/changelogs/80.txt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 fastlane/metadata/android/vi/changelogs/80.txt diff --git a/fastlane/metadata/android/vi/changelogs/80.txt b/fastlane/metadata/android/vi/changelogs/80.txt new file mode 100644 index 00000000..3d54a1a9 --- /dev/null +++ b/fastlane/metadata/android/vi/changelogs/80.txt @@ -0,0 +1,9 @@ +Tusky v14.0 + +- Thông báo khi người bạn theo dõi đăng tút - click vào biểu tượng cái chuông trên trang cá nhân của họ! (Mastodon 3.3.0) +- Tút Nháp: được thiết kế lại toàn bộ, giúp nhanh hơn, dễ dùng hơn và ít lỗi hơn. +- Chế độ Cai Nghiện: cho phép bạn giới hạn một số tính năng của Tusky. +- Hỗ trợ Emoji động: cho phép xem emoji động trong Tusky. +- Ẩn Có Thời Hạn: có thể chặn người nào đó trong khoảng thời gian cho trước. +- Sửa các lỗi vặt, đặc biệt là sự tương thích Pleroma. +- Cải thiện bản dịch From 5ccee0bcd90b58fd112a6f36d432210771922252 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BC=D0=B0=D1=87=D0=BA=D0=BE?= <2w167kmuk@relay.firefox.com> Date: Sun, 14 Feb 2021 03:55:17 +0000 Subject: [PATCH 61/81] Translated using Weblate (Bulgarian) Currently translated at 75.0% (9 of 12 strings) Translation: Tusky/Tusky description Translate-URL: https://weblate.tusky.app/projects/tusky/tusky-app/bg/ --- fastlane/metadata/android/bg/changelogs/61.txt | 7 +++++++ fastlane/metadata/android/bg/changelogs/67.txt | 9 +++++++++ fastlane/metadata/android/bg/changelogs/68.txt | 3 +++ fastlane/metadata/android/bg/changelogs/70.txt | 8 ++++++++ fastlane/metadata/android/bg/changelogs/74.txt | 8 ++++++++ fastlane/metadata/android/bg/changelogs/77.txt | 10 ++++++++++ fastlane/metadata/android/bg/full_description.txt | 12 ++++++++++++ fastlane/metadata/android/bg/short_description.txt | 1 + fastlane/metadata/android/bg/title.txt | 1 + 9 files changed, 59 insertions(+) create mode 100644 fastlane/metadata/android/bg/changelogs/61.txt create mode 100644 fastlane/metadata/android/bg/changelogs/67.txt create mode 100644 fastlane/metadata/android/bg/changelogs/68.txt create mode 100644 fastlane/metadata/android/bg/changelogs/70.txt create mode 100644 fastlane/metadata/android/bg/changelogs/74.txt create mode 100644 fastlane/metadata/android/bg/changelogs/77.txt create mode 100644 fastlane/metadata/android/bg/full_description.txt create mode 100644 fastlane/metadata/android/bg/short_description.txt create mode 100644 fastlane/metadata/android/bg/title.txt diff --git a/fastlane/metadata/android/bg/changelogs/61.txt b/fastlane/metadata/android/bg/changelogs/61.txt new file mode 100644 index 00000000..c6fed811 --- /dev/null +++ b/fastlane/metadata/android/bg/changelogs/61.txt @@ -0,0 +1,7 @@ +Tusky v7.0 + +- Поддръжка за показване на анкети, гласуване и известия за анкети +- Нови бутони за филтриране на раздела за известия и за изтриване на всички известия +- изтриване и преработване на вашите собствени публикации +- нов индикатор, който показва дали даден акаунт е бот на изображението на профила (може да бъде изключен в предпочитанията) +- Нови преводи: норвежки, букмал и словенски. diff --git a/fastlane/metadata/android/bg/changelogs/67.txt b/fastlane/metadata/android/bg/changelogs/67.txt new file mode 100644 index 00000000..9cf27a89 --- /dev/null +++ b/fastlane/metadata/android/bg/changelogs/67.txt @@ -0,0 +1,9 @@ +Tusky v9.0 + +- Вече можете да създавате анкети от Tusky +- Подобрено търсене +- Нова опция в Предпочитания на акаунта за винаги разширяване на предупрежденията за съдържание +- Аватарите в навигационното чекмедже вече имат закръглена квадратна форма +- Вече е възможно да докладвате за потребители, дори когато те никога не са публикували статус +- Tusky сега ще откаже да се свързва чрез връзки с чист текст на Android 6+ +- Много други малки подобрения и корекции на грешки diff --git a/fastlane/metadata/android/bg/changelogs/68.txt b/fastlane/metadata/android/bg/changelogs/68.txt new file mode 100644 index 00000000..ae09bdf1 --- /dev/null +++ b/fastlane/metadata/android/bg/changelogs/68.txt @@ -0,0 +1,3 @@ +Tusky v9.1 + +Тази версия осигурява съвместимост с Mastodon 3 и подобрява производителността и стабилността. diff --git a/fastlane/metadata/android/bg/changelogs/70.txt b/fastlane/metadata/android/bg/changelogs/70.txt new file mode 100644 index 00000000..d1cca33a --- /dev/null +++ b/fastlane/metadata/android/bg/changelogs/70.txt @@ -0,0 +1,8 @@ +Tusky v10.0 + +- Вече можете да маркирате състояния и да показвате отметките си в Tusky. +- Вече можете да планирате публикации с Tusky. Имайте предвид, че избраното време трябва да бъде поне 5 минути в бъдеще. +- Вече можете да добавяте списъци към главния екран. +- Вече можете да публикувате аудио прикачени файлове с Tusky. + +И много други малки подобрения и корекции на грешки! diff --git a/fastlane/metadata/android/bg/changelogs/74.txt b/fastlane/metadata/android/bg/changelogs/74.txt new file mode 100644 index 00000000..4bcdadaf --- /dev/null +++ b/fastlane/metadata/android/bg/changelogs/74.txt @@ -0,0 +1,8 @@ +Tusky v12.0 + +- Подобрен основен интерфейс - вече можете да премествате разделите отдолу +- Когато заглушавате потребител, вече можете също да решите дали да заглушите известията му +- Вече можете да следвате колкото искате хештегове в един единствен раздел хештегове +- Подобрен е начинът, по който се показват описанията на мултимедиите, така че да работи дори за супер дълги описания + +Пълен дневник на промените: https://github.com/tuskyapp/Tusky/releases diff --git a/fastlane/metadata/android/bg/changelogs/77.txt b/fastlane/metadata/android/bg/changelogs/77.txt new file mode 100644 index 00000000..e24a0601 --- /dev/null +++ b/fastlane/metadata/android/bg/changelogs/77.txt @@ -0,0 +1,10 @@ +Tusky v13.0 + +- поддръжка за бележки в профила (функция на Mastodon 3.2.0) +- поддръжка за администраторски съобщения (функция на Mastodon 3.1.0) + +- аватарът на избрания от вас акаунт вече ще се показва в главната лента с инструменти +- щракването върху показваното име в емисия ще отвори страницата с профила на този потребител + +- много корекции на грешки и малки подобрения +- подобрени преводи diff --git a/fastlane/metadata/android/bg/full_description.txt b/fastlane/metadata/android/bg/full_description.txt new file mode 100644 index 00000000..73ce4354 --- /dev/null +++ b/fastlane/metadata/android/bg/full_description.txt @@ -0,0 +1,12 @@ +Tusky е лек клиент за Mastodon, свободен сървър за социални мрежи с отворен код. + +• Материален дизайн +• Повечето приложени API на Mastodon +• Поддръжка на няколко акаунта +• Тъмна и светла тема с възможност за автоматично превключване в зависимост от часа +• Чернови - съставете публикации и ги запазете за по-късно +• Изберете между различни стилове емоджита +• Оптимизиран за всички размери на екрана +• Напълно отворен код - няма несвободни зависимости като услугите на Google + +За да научите повече за Mastodon, посетете https://joinmastodon.org/ diff --git a/fastlane/metadata/android/bg/short_description.txt b/fastlane/metadata/android/bg/short_description.txt new file mode 100644 index 00000000..d0331150 --- /dev/null +++ b/fastlane/metadata/android/bg/short_description.txt @@ -0,0 +1 @@ +Клиент с няколко акаунта за социалната мрежа Mastodon diff --git a/fastlane/metadata/android/bg/title.txt b/fastlane/metadata/android/bg/title.txt new file mode 100644 index 00000000..0238ffc0 --- /dev/null +++ b/fastlane/metadata/android/bg/title.txt @@ -0,0 +1 @@ +Tusky From af3766dcc447974c733ee55ce09b5039261120e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Gera?= Date: Sun, 14 Feb 2021 03:55:17 +0000 Subject: [PATCH 62/81] Translated using Weblate (Hungarian) Currently translated at 100.0% (12 of 12 strings) Translation: Tusky/Tusky description Translate-URL: https://weblate.tusky.app/projects/tusky/tusky-app/hu/ --- fastlane/metadata/android/hu/changelogs/80.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 fastlane/metadata/android/hu/changelogs/80.txt diff --git a/fastlane/metadata/android/hu/changelogs/80.txt b/fastlane/metadata/android/hu/changelogs/80.txt new file mode 100644 index 00000000..dd5d5804 --- /dev/null +++ b/fastlane/metadata/android/hu/changelogs/80.txt @@ -0,0 +1,7 @@ +Tusky v14.0 + +- Értesítést kaphatsz, amikor egy követett felhasználó tülköl - csak kattints a csengő ikonra a profilján! (Mastodon 3.3.0 funkció) +- A Tusky piszkozat funkcióját teljesen újraterveztük, hogy gyorsabb, felhasználóbarátabb, hibamentesebb legyen. +- Az új jóllét üzemmód lehetővé teszi, hogy bizonyos Tusky funkciókat korlátozz. +- A Tusky mostantól képes animálni az egyedi emojikat is. +Összes változás: https://github.com/tuskyapp/Tusky/releases From b3106ada64a104ff6138e97dbd107729d96a9d44 Mon Sep 17 00:00:00 2001 From: Connyduck Date: Sun, 14 Feb 2021 20:45:34 +0000 Subject: [PATCH 63/81] Added translation using Weblate (Bulgarian) --- app/src/main/res/values-bg/strings.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 app/src/main/res/values-bg/strings.xml diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml new file mode 100644 index 00000000..a6b3daec --- /dev/null +++ b/app/src/main/res/values-bg/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From 998bc677138df112703e24ed3f9c9ad13e0ef987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BC=D0=B0=D1=87=D0=BA=D0=BE?= <2w167kmuk@relay.firefox.com> Date: Sun, 14 Feb 2021 20:45:34 +0000 Subject: [PATCH 64/81] Translated using Weblate (Bulgarian) Currently translated at 99.7% (456 of 457 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/bg/ --- app/src/main/res/values-bg/strings.xml | 506 ++++++++++++++++++++++++- 1 file changed, 505 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index a6b3daec..61c97505 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -1,2 +1,506 @@ - \ No newline at end of file + + Публикация + Публикацията, на която сте изготвили отговор, е премахната + 1 час + 30 минути + 5 минути + Неопределено + Продължителност + Анкета + Активиране на плъзгащия жест за превключване между раздели + Показване на филтър за известия + Търсенето бе неуспешно + Акаунти + Акаунтът е от друг сървър. Да изпратите ли и там анонимно копие на доклада\? + Докладът ще бъде изпратен на модератора на вашия сървър. Можете да предоставите обяснение защо докладвате този акаунт по-долу: + Извличането на състояния бе неуспешно + Докладването бе неуспешно + Препращане към %s + Допълнителни коментари + Успешно докладване на @%s + Готово + Назад + Продължаване + + Остава %d секунда + Остават %d секунди + + + Остава %d минута + Остават %d минути + + + Остава %d час + Остават %d часа + + + Остава %d ден + Остават %d дни + + Анкета, която създадохте, приключи + Анкета, в която сте гласували, приключи + Гласуване + затворено + завършва в %s + + %s човек + %s човека + + + %s глас + %s гласа + + " <!-- 15 votes • 1 hour left --> %1$s • %2$s" + %1$s • %2$s + Действия за изображение %s + Сигурни ли сте, че искате да изчистите окончателно всичките си известия\? + Композиране + Композиране на публикация + Прилагане + Филтриране + Изчистване + Списък + Избиране на списък + Хаштагове + Хаштаг без # + Добавяне на хаштаг + Име на списък + Анкета с избори: %1$s, %2$s, %3$s, %4$s; %5$s + Директно + Последователи + Публично + Отметнато + Поставено в любими + Реблог + Без описание + Предупреждение за съдържание: %s + Мултимедия: %s + достигнати са максималните %1$d раздела + %1$s, %2$s и %3$d други + %1$s + %1$s и %2$s + Поставено в любими от + Споделено от + + <b>%s</b> Споделяне + <b>%s</b> Споделяния + + + <b>%1$s</b> Любимо + <b>%1$s</b> Любими + + Закачане + Разкачане + Информацията по-долу може да отразява непълно потребителския профил. Натиснете, за да отворите пълен профил в браузъра. + Използване на абсолютно време + Съдържание + Етикет + добавяне на данни + Профилни метаданни + CC-BY-SA 4.0 + CC-BY 4.0 + Лицензиран под лиценза Apache (копие по-долу) + Tusky съдържа код и активи от следните проекти с отворен код: + Отсподеляне + Споделяне с оригиналната аудитория + %1$s се премести в: + Бот + Изтеглянето се провали + Текущият набор от емоджита на Google + Първо ще трябва да изтеглите тези емоджи комплекти + Стандартният емоджи комплект на Mastodon + Blob емоджитата, известни от Android 4.4–7.1 + Емоджи комплектът по подразбиране в устройство ви + Рестартиране + По-късно + Ще трябва да рестартирате Tusky, за да приложите тези промени + Изисква се рестартиране на приложението + Отваряне на публикация + Разгъване/свиване на всички състояния + Извършва се търсене… + По подразбиране от системата + Стил на емоджи + Копирано в клипборда + Инстанцията ви %s няма персонализирани емоджита + Композиране + Копие от публикацията е запазено във вашите чернови + Изпращането е отменено + Изпращане на публикации + Грешка при изпращане на публикация + Изпращане на публикация… + Запазване на чернова\? + Изисква ръчно одобряване на последователи + Заключване на акаунт + Премахване + Задаване на надпис + Опишете за хора със зрителни увреждания +\n(%d ограничение на знаците) + Неуспешно задаване на надпис + Публикуване с акаунт %1$s + Премахване на акаунт от списъка + Добавяне на акаунт към списъка + Търсене на хора, които следвате + Редакция на списъка + Изтриване на списъка + Преименуване на списъка + Създаване на списък + Списъкът не можа да се изтрие + Списъкът не можа да се създаде + Списъкът не можа да се преименува + Списъчна емисия + Списъци + Списъци + Добавяне на нов Mastodon акаунт + Добавяне на акаунт + Фраза за филтриране + Когато ключовата дума или фраза е само буквено-цифрова, тя ще бъде приложена само ако съответства на цялата дума + Цяла дума + Актуализиране + Премахване + Редакция на филтър + Добавяне на филтър + Разговори + Публични емисии + зареждане на още + Отговаряне на @%s + Мултимедия + Винаги разгъване на публикации, маркирани с предупреждения за съдържание + Винаги показване на деликатно съдържание + Следва ви + %dс + %dм + %dч + %dд + %dг + след %dс + след %dм + след %dч + след %dг + след %dд + Заявено последване + Прикачени файлове + Аудио + Видео + Изображения + Споделяне на връзка към публикация + Споделяне на съдържание на публикация + Профилът на Tusky + Доклади за грешки и заявки за функции: +\n https://github.com/tuskyapp/Tusky/issues + Уебсайт на проекта: +\n https://tusky.app + Tusky е свободен софтуер с отворен код. Той е лицензиран под Общият публичен лиценз на GNU Версия 3. Можете да видите лиценза тук: https://www.gnu.org/licenses/gpl-3.0.en.html + Осъществено от Tusky + Tusky %s + Относно + Заключен акаунт + %d нови взаимодействия + %1$s и %2$s + %1$s, %2$s, и %3$s + %1$s, %2$s, %3$s и %4$d други + %s ви спомена + Известия, когато някой, за когото сте абонирани, публикува + Нови публикации + Известия за приключили анкети + Анкети + Известия, когато публикациите ви бъдат означени като любими + Любими + Известия, когато публикациите ви се споделят + Най-малък + Скрито + Раздели + Филтриране на емисия + Анимиране на персонализирани емоджита + Показване на цветни градиенти за скрита мултимедия + Анимиране на GIF аватари + Показване на индикатор за ботове + Език + Скриване на бутона за композиране, при превъртане + Използване на персонализирани раздели чрез Chrome + Браузър + Използване на системния дизайн + Автоматично при залез + Черно + Светло + Тъмно + Филтри + Емисии + Тема на приложение + Външен вид + някой, за когото съм абониран, публикува + приключили анкети + публикациите ми са сложени в любими + публикациите ми са споделени + заявка за последване + последвани + споменати + Уведомете ме когато + Уведомяване със светлина + Уведомяване с вибрация + Уведомяване със звук + Сигнали + Известия + Известия + Директно: Публикуване само за споменатите потребители + Само за последователи: Публикуване само за последователи + Публично: Публикуване в публични емисии + Скриване на известия + Заглушаване на @%s\? + Блокиране на @%s\? + Скриване на целия домейн + Сигурни ли сте, че искате да блокирате всички от %s\? Няма да виждате съдържание от този домейн в нито една публична емисия или във вашите известия. Последователите ви от този домейн ще бъдат премахнати. + Изтриване и преработване на тази публикация\? + Изтриване на тази публикация\? + Отследване на този акаунт\? + Отмяна на заявката за последване\? + Изтегляне + Качване… + Завършване на мултимедийно качване + "Тук може да се въведе адресът или домейнът на която и да е инстанция, като mastodon.social, icosahedron.website, social.tchncs.de и <a href=\"https://instances.social\">други!</a> +\n +\nАко все още нямате акаунт, можете да въведете името на инстанцията, към който искате да се присъедините, и да създадете акаунт там. +\n +\nИнстанцията е единично място, където се хоства акаунтът ви, но можете лесно да комуникирате и да следвате хора в други инстанции, сякаш сте на същия сайт. +\n +\nПовече информация можете да намерите на <a href=\"https://joinmastodon.org\">joinmastodon.org</a>. "more! + \n\nIf you don\'t yet have an account, you can enter the name of the instance you\'d like to + join and create an account there.\n\nAn instance is a single place where your account is + hosted, but you can easily communicate with and follow folks on other instances as though + you were on the same site. + \n\nMore info can be found at joinmastodon.org. + + Свързване… + Какво е инстанция\? + Заглавна част + Аватар + Отговор… + Няма резултати + Търсене… + Био + Показвано име + Предупреждение за съдържание + Какво се случва\? + Коя инстанция\? + Отговорът е изпратен успешно. + Изпратено! + %s е разкрит + Потребителят е раззаглушен + Потребителят е деблокиран + Изпратено! + Споделяне на мултимедия в… + Споделяне на публикация в… + Споделяне на URL адреса на публикацията в… + Теглене на мултимедия + Изтегляне на мултимедия + Споделяне като … + Отваряне като %s + Копиране на връзката + Изтегляне на %1$s + Отваряне на мултимедия #%d + Връзки + Споменавания + Хаштагове + Показване на любими + Показване на споделяния + Отваряне на споделилия автор + Хаштагове + Споменавания + Връзки + Добавяне на раздел + Нулиране + Планиране на публикация + Емоджи клавиатура + Предупреждение за съдържание + Видимост на публикация + Планирани публикации + Чернови + Търсене + Отхвърляне + Приемане + Отмяна + Редакция + Редакция на профил + Запазване + Отваряне на чекмедже + Скриване на мултимедия + Споменаване + Раззаглушаване на разговор + Заглушаване на разговор + Раззаглушаване на %s + Заглушаване на %s + Заглушаване на известия от %s + Раззаглушаване на известия от %s + Раззаглушаване на %s + Раззаглушаване + Заглушаване + Споделяне + Снимане + Добавяне на анкета + Добавяне на мултимедия + Отваряне в браузър + Мултимедия + Заявки за последване + Скрити домейни + Блокирани потребители + Заглушени потребители + Отметки + Любими + Предпочитания за акаунт + Предпочитания + Профил + Затваряне + Повторен опит + ПУБЛИКУВАНЕ! + ИЗПРАЩАНЕ + Изтриване и преработване + Изтриване + Редакция + Докладване + Показване на споделяния + Скриване на споделяния + Деблокиране + Блокиране + Отследване + Последване + Сигурни ли сте, че искате да излезете от акаунта %1$s\? + Излизане + Влизане с Mastodon + Композиране + Още + Премахване от любими + Отмятане + Поставяне в любими + Премахване на споделяне + Споделяне + Отговор + Бърз отговор + Допълнителни коментари\? + Докладване на @%s + %s току-що публикува + %s поиска да ви последва + %s ви последва + %s постави вашата публикация в любими + %s сподели вашата публикация + Нищо тук. Дръпнете надолу, за да опресните! + Нищо тук. + Свиване + Разгъване + Покажи по-малко + Покажи повече + Щракнете за преглед + Мултимедията е скрита + Деликатно съдържание + %s сподели + \@%s + Лицензи + Оповестявания + Планирани публикации + Чернови + Редакция на профила ви + Заявки за последване + Скрити домейни + Блокирани потребители + Заглушени потребители + Отметки + Любими + Последователи + Последвани + Закачени + С отговори + Публикации + Раздели + Директни съобщения + Локално + Известия + Начало + Грешка при изпращане на публикация. + Качването бе неуспешно. + Изображения и видеоклипове не могат да бъдат прикачени към едно и също състояние. + Изисква се разрешение за съхранение на мултимедия. + Изисква се разрешение за четене на носител. + Този файл не можа да бъде отворен. + Този тип файл не може да бъде качен. + Аудио файловете трябва да са по-малки от 40MB. + Видео файловете трябва да са по-малки от 40MB. + Файлът трябва да е по-малък от 8MB. + Състоянието е твърде дълго! + Получаването на токен за вход бе неуспешно. + Упълномощаването е отказано. + Възникна неидентифицирана грешка при упълномощаване. + Неуспешно намиране на уеб браузър, който да се използва. + Неуспешно удостоверяване с тази инстанция. + Въведен е невалиден домейн + Това не може да бъде празно. + Възникна грешка в мрежата! Моля, проверете връзката си и опитайте отново! + Възникна грешка. + Черновата е изтрита + Неуспешно зареждане на информация за отговор + Стари чернови + Функцията за чернови в Tusky е напълно преработена, за да бъде по-бърза, по-лесна за ползване и по-малко бъгава. +\n Все още можете да осъществите достъп до старите си чернови чрез бутон на екрана за нови чернови, но те ще бъдат премахнати при бъдеща актуализация! + Тази публикация не успя да се изпрати! + Наистина ли искате да изтриете списъка %s\? + Не можете да качите повече от %1$d мултимедийни прикачени файлове. + Скриване на количествена статистика на профили + Скриване на количествена статистика на публикации + Ограничаване на известия от емисия + Преглед на известията + Част от информацията, която може да повлияе на вашето психично състояние, ще бъде скрита. Това включва: +\n +\n - Известия за Любими/Споделяния/Последвани +\n - Брой Любими/Споделяния на публикации +\n - Статистика за Последователи/Публикации на профили +\n +\n Изскачащите известия няма да бъдат засегнати, но можете да прегледате предпочитанията си за известяване ръчно. + Запазено! + Вашата лична бележка за този акаунт + Благосъстояние + Скриване на заглавието на горната лента с инструменти + Показване на диалоговия прозорец за потвърждение преди споделяне + Показване на визуализации на връзки в емисии + Mastodon има минимален интервал за планиране от 5 минути. + Няма оповестявания. + Нямате планирани състояния. + Нямате чернови. + Грешка при търсенето на публикация %s + Редакция + Избор %d + Множество избора + Добавяне на избор + 7 дни + 3 дни + 1 ден + 6 часа + Споделяния + Известия за заявки за последване + Заявки за последване + Известия за нови последователи + Нови последователи + Известия за нови споменавания + Нови споменавания + Най-голям + Голям + Среден + Малък + Скрито: Не се показва в публични емисии + Размер на текста на състоянието + Само за последователи + Скрито + Публично + Долу + Горе + Основна навигационна позиция + Синхронизирането на настройките бе неуспешно + Публикуване (синхронизирано със сървър) + Винаги маркиране на мултимедия като чувствителна + Поверителност на публикация по подразбиране + HTTP прокси порт + HTTP прокси сървър + Активиране на HTTP прокси + HTTP прокси + Прокси + Изтегляне на визуализации за мултимедии + Показване на отговори + Показване на споделяния + \ No newline at end of file From ee24a27ce3776af355ccdfc657e82a0172a7f265 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Gera?= Date: Sun, 14 Feb 2021 20:45:34 +0000 Subject: [PATCH 65/81] Translated using Weblate (Hungarian) Currently translated at 100.0% (457 of 457 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/hu/ --- app/src/main/res/values-hu/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 1a7f29e5..0cc2f276 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -505,4 +505,5 @@ valaki, akit követek újat tülkölt %s épp tülkölt Jóllét + Egyedi emojik animálása \ No newline at end of file From 027e6d0b49c2298251a1a213ceeba86f3f60a592 Mon Sep 17 00:00:00 2001 From: Ho Nhat Duy Date: Sun, 14 Feb 2021 20:45:34 +0000 Subject: [PATCH 66/81] Translated using Weblate (Vietnamese) Currently translated at 100.0% (457 of 457 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/ Translated using Weblate (Vietnamese) Currently translated at 100.0% (457 of 457 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/ --- app/src/main/res/values-vi/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 1182609a..349ab4d4 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -476,8 +476,8 @@ người tôi đăng ký theo dõi đăng tút mới %s vừa đăng tút Bạn không thể đính kèm quá %1$d tệp. - Không giới hạn - Độ dài + Vĩnh viễn + Thời hạn Bạn thật sự muốn xóa danh sách %s\? Đính kèm Âm thanh @@ -485,7 +485,7 @@ Tút lên lịch cũ Tút lên lịch đã xóa Chưa tải được bình luận - Tính năng lên lịch đăng tút của Tusky sẽ được thiết kế lại hoàn toàn để nhanh hơn, thân thiện và ít lỗi hơn. + Tính năng lên lịch đăng tút của Tusky được thiết kế lại hoàn toàn để nhanh hơn, thân thiện hơn và ít lỗi hơn. \nBạn vẫn có thể xem lại bản nháp cũ nhưng chúng sẽ bị xóa bỏ trong bản cập nhật tương lai! Đăng tút không thành công! Emoji động From b0871ea9a236f83bb57748a5d953da6b564ad4bd Mon Sep 17 00:00:00 2001 From: papapep Date: Sun, 14 Feb 2021 20:45:35 +0000 Subject: [PATCH 67/81] Translated using Weblate (Catalan) Currently translated at 99.5% (455 of 457 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/ca/ Translated using Weblate (Catalan) Currently translated at 97.1% (444 of 457 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/ca/ --- app/src/main/res/values-ca/strings.xml | 84 +++++++++++++++++++------- 1 file changed, 63 insertions(+), 21 deletions(-) diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 9748a481..daecc343 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -2,20 +2,20 @@ S\'ha produït un error. Això no pot estar buit. - El domini introduït no és vàlid - L\'autenticació en aquesta instància ha fallat. + El domini que s\'ha introduït no és vàlid + Ha fallat l\'autenticació en aquesta instància. No s\'ha trobat cap navegador web per a utilitzar. S\'ha produït un error d\'autorització no identificat. S\'ha denegat l\'autorització. - L\'obtenció del token d\'inici de sessió ha fallat. + Ha fallat l\'obtenció del token d\'inici de sessió. L\'estat és massa llarg! - El fitxer ha de ser inferior a 8MB. - Aquest tipus de fitxer no es pot pujar. - Aquest tipus de fitxer no es pot obrir. - Cal permís d\'accés al emmagatzematge. - Cal permís d\'escriptura en el dispositiu. + El fitxer ha de ser d\'una mida menor de 8MB. + No es pot pujar aquest tipus de fitxer. + No es pot obrir aquest tipus de fitxer. + Cal permís d\'accés a l\'emmagatzematge. + Cal permís d\'escriptura a l\'emmagatzematge. No es poden adjuntar imatges i vídeos en el mateix estat. - La pujada ha fallat. + Ha fallat la pujada. Inici Notificacions Local @@ -107,7 +107,7 @@ , però pots comunicar-te fàcilment i seguir amics d\'altres instàncies com si fossiu en el mateix lloc. \n\nTens més informació a joinmastodon.org. - S\'està finalitzant la pujada de materila multimèdia + S\'està finalitzant la pujada de material multimèdia S\'està pujant… Baixa Vols deixar de seguir aquest compte? @@ -191,7 +191,7 @@ En resposta a @%s carrega\'n més Vota - S\'ha produït un error en enviar el toot. + S\'ha produït un error en enviar el tut. Pestanyes Llicències Amplia @@ -202,15 +202,15 @@ Missatges directes No hi ha res aquí. Elimina l\'impuls - S\'ha produït un error de connexió! Comprova la connexió i torna-ho a provar! - Els fitxers de vídeo han de pesar menys de 40 MB. + S\'ha produït un error de connexió! Comproveu la connexió i torneu-ho a provar! + Els fitxers de vídeo han de ser de mida menor de 40 MB. Multimèdia amagada Amaga Estàs segur de tancar la sessió de %1$s\? Amaga els retoots Mostra els impulsos Elimina i reecririu - Open drawer + Obre el menú Visibilitat del toot Contingut sensible Afegir una pestanya @@ -225,7 +225,7 @@ Baixa el fitxer Compartir la imatge a … Enviat! - Follow requested + S\'ha enviat la petició de seguiment Amb respostes Teclat d\'emojis Obrir el media #%d @@ -334,8 +334,8 @@ %1$s Favorits - - + %s impuls + %s impulsos Impulsat per Marcat favorit per @@ -361,7 +361,7 @@ Vols netejar totes les notificacions permanentment\? %1$s • %2$s - %s vots + %s vot %s vots Acaba a %s @@ -421,13 +421,13 @@ Programar el toot Reiniciar Desenvolupat per Tusky - Afegit a les adreces d\'interès. + S\'ha afegit a les adreces d\'interès Seleccionar la llista Llista S\'ha produït un error en cercar la publicació %s No tens cap estat planificat. - Els fitxers d\'àudio han de ser més petits que 40MB. - No tens cap esborrany + Els fitxers d\'àudio han de ser de mida menor de 40MB. + No teniu cap esborrany. L\'interval mínim de planificació a Mastodon és de 5 minuts. Peticions de seguiment Mostra el diàleg de confirmació abans de promoure @@ -455,4 +455,46 @@ Desactivar les notificacions per %s Activar les notificacions per %s Deixar de silenciar %s + Revisió d\'avisos + S\'ha desat! + Les vostres notes quant a aquest compte + Benestar + Amaga el títol de la barra d\'eines superior + No hi ha cap avís. + Indefinit + Durada + + falta %d segon + falten %d segons + + + falta %d minut + falten %d minuts + + + falta %d hora + falten %d hores + + + falta %d dia + falten %d dies + + Adjuncions + Àudio + Notificacions quan algú a qui esteu subscrit publica un tut nou + Tuts nous + 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 l\'esborrany + No s\'ha pogut carregar la informació de la resposta + Esborranys antics + No s\'ha pogut enviar aquest tut! + Segur que voleu esborrar la llista %s\? + No podeu pujar més de %1$d adjunts multimèdia. + Amaga les estadístiques quantitatives dels perfils + Amaga les estadístiques quantitatives de les publicacions + Limita les notificacions de la cronologia \ No newline at end of file From 2fea6011fde90ab18cbba5b7cad5e9bba4058fbb Mon Sep 17 00:00:00 2001 From: David Date: Tue, 16 Feb 2021 18:45:45 +0100 Subject: [PATCH 68/81] NavIcon Avatar fix (#2080) * NavIcon Avatar fix * Added in all three methods --- app/src/main/java/com/keylesspalace/tusky/MainActivity.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt index 3fefe4bf..3b3af8ae 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt @@ -694,16 +694,18 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje .into(object : CustomTarget(navIconSize, navIconSize) { override fun onLoadStarted(placeholder: Drawable?) { - if(placeholder != null) { + if (placeholder != null) { mainToolbar.navigationIcon = FixedSizeDrawable(placeholder, navIconSize, navIconSize) } } override fun onResourceReady(resource: Drawable, transition: Transition?) { - mainToolbar.navigationIcon = resource + mainToolbar.navigationIcon = FixedSizeDrawable(resource, navIconSize, navIconSize) } override fun onLoadCleared(placeholder: Drawable?) { - mainToolbar.navigationIcon = placeholder + if (placeholder != null) { + mainToolbar.navigationIcon = FixedSizeDrawable(placeholder, navIconSize, navIconSize) + } } }) } From ff7d900b67c471a10b066b91a64f82f8dedcbceb Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Thu, 18 Feb 2021 19:26:03 +0100 Subject: [PATCH 69/81] fix profile note "Saved" message appearing on other account changes (#2079) --- app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt index 95101708..99d135ed 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt @@ -568,11 +568,12 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI subscribing = relation.subscribing } + // remove the listener so it doesn't fire on non-user changes + accountNoteTextInputLayout.editText?.removeTextChangedListener(noteWatcher) + accountNoteTextInputLayout.visible(relation.note != null) accountNoteTextInputLayout.editText?.setText(relation.note) - // add the listener late to avoid it firing on the first change - accountNoteTextInputLayout.editText?.removeTextChangedListener(noteWatcher) accountNoteTextInputLayout.editText?.addTextChangedListener(noteWatcher) updateButtons() From e3b79097d4afc2d2dce59af5b7b4a829d9ac80bb Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Thu, 18 Feb 2021 19:26:14 +0100 Subject: [PATCH 70/81] remove redundant options from account toolbar (#2075) --- .../keylesspalace/tusky/AccountActivity.kt | 19 +------------------ app/src/main/res/menu/account_toolbar.xml | 8 -------- 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt index 99d135ed..ec3334e1 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt @@ -661,14 +661,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI menuInflater.inflate(R.menu.account_toolbar, menu) if (!viewModel.isSelf) { - val follow = menu.findItem(R.id.action_follow) - follow.title = if (followState == FollowState.NOT_FOLLOWING) { - getString(R.string.action_follow) - } else { - getString(R.string.action_unfollow) - } - - follow.isVisible = followState != FollowState.REQUESTED val block = menu.findItem(R.id.action_block) block.title = if (blocking) { @@ -712,8 +704,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI } } else { - // It shouldn't be possible to block, follow, mute or report yourself. - menu.removeItem(R.id.action_follow) + // It shouldn't be possible to block, mute or report yourself. menu.removeItem(R.id.action_block) menu.removeItem(R.id.action_mute) menu.removeItem(R.id.action_mute_domain) @@ -805,10 +796,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { - R.id.action_mention -> { - mention() - return true - } R.id.action_open_in_web -> { // If the account isn't loaded yet, eat the input. if (loadedAccount != null) { @@ -816,10 +803,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI } return true } - R.id.action_follow -> { - viewModel.changeFollowState() - return true - } R.id.action_block -> { toggleBlock() return true diff --git a/app/src/main/res/menu/account_toolbar.xml b/app/src/main/res/menu/account_toolbar.xml index ee881122..d25bcdc1 100644 --- a/app/src/main/res/menu/account_toolbar.xml +++ b/app/src/main/res/menu/account_toolbar.xml @@ -2,18 +2,10 @@ - - - - From d2a8254d4fc29409f32ad867d796af0a4d385836 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Thu, 18 Feb 2021 19:26:25 +0100 Subject: [PATCH 71/81] add content description to account subscription button (#2076) --- app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt | 2 ++ app/src/main/res/values/strings.xml | 3 +++ 2 files changed, 5 insertions(+) diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt index ec3334e1..397b7f64 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt @@ -623,8 +623,10 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI if(subscribing) { accountSubscribeButton.setIconResource(R.drawable.ic_notifications_active_24dp) + accountSubscribeButton.contentDescription = getString(R.string.action_unsubscribe_account) } else { accountSubscribeButton.setIconResource(R.drawable.ic_notifications_24dp) + accountSubscribeButton.contentDescription = getString(R.string.action_subscribe_account) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6aa4a3a8..c6f05b4f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -611,4 +611,7 @@ Failed loading Reply information Draft deleted The Toot you drafted a reply to has been removed + + Subscribe + Unsubscribe From f3285b03e0b716c1d9de06e078a0e682edb3a42f Mon Sep 17 00:00:00 2001 From: Vegard Skjefstad Date: Tue, 16 Feb 2021 21:56:22 +0000 Subject: [PATCH 72/81] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (12 of 12 strings) Translation: Tusky/Tusky description Translate-URL: https://weblate.tusky.app/projects/tusky/tusky-app/nb_NO/ --- fastlane/metadata/android/nb_NO/changelogs/80.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/metadata/android/nb_NO/changelogs/80.txt b/fastlane/metadata/android/nb_NO/changelogs/80.txt index 11b5b37d..8e4b8595 100644 --- a/fastlane/metadata/android/nb_NO/changelogs/80.txt +++ b/fastlane/metadata/android/nb_NO/changelogs/80.txt @@ -4,4 +4,4 @@ Tusky v14.0 - Ny og forbedret kladd-funksjonalitet. - Velværemodus: Kan brukes til å begrense utvalgt funksjonalitet i Tusky. Du kan aktivere velværemodus i innstillinger. - Støtte for animerte emojis. Dette er skrudd av som standard, men du kan skru det på i innstillinger. -- Tidsbestemt demping. Det er mulig å dempe brukere i en angitt periode. +- Komplett endringelogg: https://github.com/tuskyapp/Tusky/releases From 3ad254fdf0e3d611c98562b6393bf0d2663e73a4 Mon Sep 17 00:00:00 2001 From: Vegard Skjefstad Date: Sun, 21 Feb 2021 17:22:13 +0000 Subject: [PATCH 73/81] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (459 of 459 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/nb_NO/ --- app/src/main/res/values-no-rNB/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-no-rNB/strings.xml b/app/src/main/res/values-no-rNB/strings.xml index f9083c8a..455f5e5d 100644 --- a/app/src/main/res/values-no-rNB/strings.xml +++ b/app/src/main/res/values-no-rNB/strings.xml @@ -497,4 +497,6 @@ \nGamle kladder er fortsatt tilgjengelige via en knapp på den nye kladdskjermen, men de vil bli fjernet i en fremtidig oppdatering! Sending av toot feilet! Animer egendefinerte emojis + Avslutt abonnementet + Abonner \ No newline at end of file From 71fd3be5da5d90025c8b05b940d49e8817c0af04 Mon Sep 17 00:00:00 2001 From: Ho Nhat Duy Date: Sun, 21 Feb 2021 17:22:13 +0000 Subject: [PATCH 74/81] Translated using Weblate (Vietnamese) Currently translated at 100.0% (459 of 459 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/ --- app/src/main/res/values-vi/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 349ab4d4..41f6a2a7 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -489,4 +489,6 @@ \nBạn vẫn có thể xem lại bản nháp cũ nhưng chúng sẽ bị xóa bỏ trong bản cập nhật tương lai! Đăng tút không thành công! Emoji động + Ngưng nhận thông báo + Nhận thông báo \ No newline at end of file From 1206cd3312aad4d1f3a44029a7bbac416517cff6 Mon Sep 17 00:00:00 2001 From: vachan-maker Date: Sun, 21 Feb 2021 17:22:13 +0000 Subject: [PATCH 75/81] Translated using Weblate (Malayalam) Currently translated at 23.7% (109 of 459 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/ml/ --- app/src/main/res/values-ml/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index de97414b..4f4a7868 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -111,4 +111,5 @@ അറിയിപ്പുകൾ ടാബുകൾ അറിയിപ്പുകൾ + പ്രഖ്യാപനങ്ങൾ \ No newline at end of file From e8200f85fa183a38a15acd7bbfc7e0f7e8002391 Mon Sep 17 00:00:00 2001 From: Nikita Epifanov Date: Tue, 23 Feb 2021 09:16:03 +0000 Subject: [PATCH 76/81] Translated using Weblate (Russian) Currently translated at 95.8% (440 of 459 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/ru/ --- app/src/main/res/values-ru/strings.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 9e2cf800..c7f2550d 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -505,4 +505,16 @@ Скрыть заголовок в верхней панели Объявлений нет. Объявления + "Некоторая информация, которая может повлиять на ваше психическое благополучие, будет скрыта. Это включает в себя: +\n +\n - Избранное/Продвижение/Уведомления подписок +\n - Избранное/Продвижение счета на тутах +\n - Статистика подписчиков/публикаций в профилях +\n +\n На push-уведомления это не повлияет, но вы можете просмотреть настройки уведомлений вручную." + Благосостояние + Неопределённая + Продолжительность + Вложения + Аудио \ No newline at end of file From aa8a7b89b9d9172647accba22b4ffb9a98474b3e Mon Sep 17 00:00:00 2001 From: Ho Nhat Duy Date: Tue, 23 Feb 2021 09:16:03 +0000 Subject: [PATCH 77/81] Translated using Weblate (Vietnamese) Currently translated at 100.0% (459 of 459 strings) Translation: Tusky/Tusky Translate-URL: https://weblate.tusky.app/projects/tusky/tusky/vi/ --- 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 41f6a2a7..f3aa8bfa 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -134,7 +134,7 @@ Tạo bình chọn Thêm tệp Mở trong trình duyệt - Thư viện + Album Yêu cầu theo dõi Máy chủ đã ẩn Người dùng đã chặn From 6ebbe5f6573ea0583d33425d7653c0afdce403cf Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Tue, 23 Feb 2021 18:53:43 +0100 Subject: [PATCH 78/81] fix crash when rotating screen in a preference dialog twice (#2083) --- .../preference/PreferencesActivity.kt | 56 ++++++++++--------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt index f21ec460..7bb8766a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt @@ -21,6 +21,7 @@ import android.content.SharedPreferences import android.os.Bundle import android.util.Log import androidx.fragment.app.Fragment +import androidx.fragment.app.commit import androidx.preference.PreferenceManager import com.keylesspalace.tusky.BaseActivity import com.keylesspalace.tusky.MainActivity @@ -58,33 +59,36 @@ class PreferencesActivity : BaseActivity(), SharedPreferences.OnSharedPreference setDisplayShowHomeEnabled(true) } - val fragment: Fragment = when (intent.getIntExtra(EXTRA_PREFERENCE_TYPE, 0)) { - GENERAL_PREFERENCES -> { - setTitle(R.string.action_view_preferences) - PreferencesFragment.newInstance() - } - ACCOUNT_PREFERENCES -> { - setTitle(R.string.action_view_account_preferences) - AccountPreferencesFragment.newInstance() - } - NOTIFICATION_PREFERENCES -> { - setTitle(R.string.pref_title_edit_notification_settings) - NotificationPreferencesFragment.newInstance() - } - TAB_FILTER_PREFERENCES -> { - setTitle(R.string.pref_title_status_tabs) - TabFilterPreferencesFragment.newInstance() - } - PROXY_PREFERENCES -> { - setTitle(R.string.pref_title_http_proxy_settings) - ProxyPreferencesFragment.newInstance() - } - else -> throw IllegalArgumentException("preferenceType not known") - } + val fragmentTag = "preference_fragment_$EXTRA_PREFERENCE_TYPE" - supportFragmentManager.beginTransaction() - .replace(R.id.fragment_container, fragment) - .commit() + val fragment: Fragment = supportFragmentManager.findFragmentByTag(fragmentTag) + ?: when (intent.getIntExtra(EXTRA_PREFERENCE_TYPE, 0)) { + GENERAL_PREFERENCES -> { + setTitle(R.string.action_view_preferences) + PreferencesFragment.newInstance() + } + ACCOUNT_PREFERENCES -> { + setTitle(R.string.action_view_account_preferences) + AccountPreferencesFragment.newInstance() + } + NOTIFICATION_PREFERENCES -> { + setTitle(R.string.pref_title_edit_notification_settings) + NotificationPreferencesFragment.newInstance() + } + TAB_FILTER_PREFERENCES -> { + setTitle(R.string.pref_title_status_tabs) + TabFilterPreferencesFragment.newInstance() + } + PROXY_PREFERENCES -> { + setTitle(R.string.pref_title_http_proxy_settings) + ProxyPreferencesFragment.newInstance() + } + else -> throw IllegalArgumentException("preferenceType not known") + } + + supportFragmentManager.commit { + replace(R.id.fragment_container, fragment, fragmentTag) + } restartActivitiesOnExit = intent.getBooleanExtra("restart", false) From 4d0083b6b1ccee281060b41b4f1bf74e0be012dc Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Tue, 23 Feb 2021 18:54:01 +0100 Subject: [PATCH 79/81] add Bulgarian, Sorani and Sanskrit to the in-app language picker (#2085) --- app/src/main/res/values/donottranslate.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 794682c6..20c78368 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -60,12 +60,15 @@ Taqbaylit Tiếng Việt Türkçe + български Русский العربية + کوردیی ناوەندی বাংলা (বাংলাদেশ) বাংলা (ভারত) فارسی हिंदी + संस्कृतम् தமிழ் ภาษาไทย 한국어 @@ -103,12 +106,15 @@ kab vi tr + bg ru ar + ckb bn-bd bn-in fa hi + sa ta th ko From 019051bd7dbc31e1bf184aa1be6099c533207a7b Mon Sep 17 00:00:00 2001 From: Conny Duck Date: Tue, 23 Feb 2021 19:19:44 +0100 Subject: [PATCH 80/81] fix strings with wrong placeholders --- app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-pt-rBR/strings.xml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 6f2c7999..4367e349 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -487,5 +487,5 @@ Neue Beiträge GIF-Emojis animieren Jemand, den ich abonniert habe, etwas Neues veröffentlicht - % hat gerade etwas gepostet + %s hat gerade etwas gepostet \ No newline at end of file diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index cd0c219a..939349b6 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -481,7 +481,6 @@ A função de rascunhos no Tusky foi totalmente redesenhada para ser mais rápida, mais fácil e com menos erros. \nÉ possível acessar rascunhos antigos através de um botão na tela de novos rascunhos, mas serão removidos numa futura atualização! Rascunhos antigos - Deseja excluir esta lista\? Não é possível anexar mais de %1$d arquivos de mídia. Ocultar status dos perfis Ocultar status dos toots From 09317d92356c9c6efd71908d74117a55208b4100 Mon Sep 17 00:00:00 2001 From: Conny Duck Date: Tue, 23 Feb 2021 19:19:52 +0100 Subject: [PATCH 81/81] Release 80 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index e9a382e1..00ca2a56 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -20,8 +20,8 @@ android { applicationId APP_ID minSdkVersion 21 targetSdkVersion 29 - versionCode 79 - versionName "14.0 beta 1" + versionCode 80 + versionName "14.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true