From 597862c82998111aea2c737a5235ebbed13b4151 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Mesk=C3=B3?= Date: Fri, 18 Dec 2020 22:06:51 +0000 Subject: [PATCH 01/10] 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 | 34 +++++++++++++------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 560814439..53f74c138 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -3,26 +3,26 @@ Hiba történt. Hálózati hiba történt! Kérjük, ellenőrizd a kapcsolatot, és próbálja meg újra! Ez nem lehet üres. - Helytelen domén - Sikertelen bejelentkezés ezen a szerveren. + Helytelen domain + Sikertelen hitelesítés ezen a példányon. Nem található használható böngésző. Azonosítatlan engedélyezési hiba történt. Engedély megtagadva. Bejelentkezési token megszerzése sikertelen. Túl hosszú a tülkölés! - A fájlnak kisebbnek kell lennie, mint 8MB. - A video fájlnak kisebbnek kell lennie, mint 40MB. + A fájlnak kisebbnek kell lennie, mint 8 MB. + A videofájloknak kisebbnek kell lenniük, mint 40 MB. Ilyen típusú fájlt nem lehet feltölteni. Fájl megnyitása sikertelen. Média olvasási engedély szükséges. Média tárolási engedély szükséges. Képek és videók egyszerre nem csatolhatók ugyanazon tülköléshez. Feltöltés sikertelen. - Nem sikerült elküldeni a tülköt.. + Nem sikerült elküldeni a tülköt. Kezdőlap Értesítések Helyi - Föderáció + Föderációs Közvetlen üzenetek Fülek Tülk @@ -65,8 +65,8 @@ Biztosan ki szeretnél jelentkezni a következőből: %1$s? Követés Követés vége - Letiltás - Letiltás feloldása + Blokkolás + Blokkolá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 - Letiltott felhasználók + Blokkolt felhasználók Követési kérelmek Média Megnyitás böngészőben @@ -118,11 +118,11 @@ Tülk URL megosztása… Tülk megosztása… Elküldve! - Felhasználó letiltása feloldva + Felhasználó blokkolása feloldva Felhasználó némítása feloldva Elküldve! Válasz sikeresen elküldve. - Melyik szerver\? + Melyik példány\? Mi jár a fejedben\? Tartalom figyelmeztetés Megjelenítési név @@ -134,11 +134,11 @@ Fejléc Mi az a szerver\? Csatlakozás… - Bármely szerver címét beírhatod ide, mint mastodon.social, icosahedron.website, social.tchncs.de, és mások! + Bármely példány címét vagy domain nevét beírhatod ide, mint a mastodon.social, az icosahedron.website, a social.tchncs.de és mások! \n -\nHa még nincs fiókod, beírhatod a címét ide annak a szervernek amelyhez csatlakoznál, majd azon létrehozhatsz egy fiókot. +\nHa még nincs fiókod, beírhatod annak a példánynak a címét, amelyhez csatlakoznál, majd azon létrehozhatsz egy fiókot. \n -\nA szerver az a hely ahol a fiókadataidat tárolják, de ettől még ugyanúgy kommunikálhatsz más szervereken lévő emberekkel, mintha ugyanazon az oldalon lennétek. +\nA példány az a hely, ahol a fiókadataidat tárolják, de ettől még ugyanúgy kommunikálhatsz más példányokon lévő emberekkel, mintha ugyanazon az oldalon lennétek. \n \nTöbb információt találhatsz itt: joinmastodon.org. Média feltöltés befejezése @@ -236,7 +236,7 @@ Listák Listák Törlés - Fiók lezárása + Fiók zárolása Elmented a vázlatot? Tülk elküldése… A tülk elküldése nem sikerült @@ -441,7 +441,7 @@ Könyvjelzőzve Lista kiválasztása Lista - A hangfájlok mérete 40MB-nál kisebb kell legyen. + A hangfájloknak kisebbnek kell lenniük, mint 40 MB. Nincs egy vázlatod sem. Nincs egy ütemezett tülköd sem. A Mastodonban a legrövidebb ütemezhető időintervallum 5 perc. @@ -462,7 +462,7 @@ Színes homály mutatása rejtett médiánál követni szeretnének Értesítések elrejtése - Letiltsuk @%s -t\? + Blokkolod: @%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 f6172cfbbfa262dce0c5eefc24beec62dbca322e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Gera?= Date: Fri, 18 Dec 2020 22:06:51 +0000 Subject: [PATCH 02/10] 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 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 53f74c138..df5cd610a 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -26,7 +26,7 @@ Közvetlen üzenetek Fülek Tülk - Posztok + Tülkök Válaszokkal Rögzített Követett @@ -198,7 +198,7 @@ Új követők Értesítések új követőkről Megtolások - Értesítések posztjaid megtolása esetén + Értesítések tülkjeid megtolása esetén Kedvencek Értesítések mikor tülkjeidet kedvencnek jelölik %s megemlített téged @@ -433,7 +433,7 @@ Időzített tülkök Tülk Időzítése Visszaállítás - Nem találjuk ezt a posztot %s + Nem találjuk ezt a tülköt %s Könyvjelzők Könyvjelzőzés Könyvjelzők From a917e0dad86a1c067d9bee4c45449bde49bb1fdd Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Sun, 20 Dec 2020 19:00:43 +0100 Subject: [PATCH 03/10] Release 78 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 1416975ad..a7f566ac6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -20,8 +20,8 @@ android { applicationId APP_ID minSdkVersion 21 targetSdkVersion 29 - versionCode 77 - versionName "13.0" + versionCode 78 + versionName "13.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true From b91a0aceebcf0359be61000effe7242b5ec1cd67 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Wed, 23 Dec 2020 14:52:39 +0300 Subject: [PATCH 04/10] 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 000000000..c47a305b7 --- /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 478d8aff4..1a725d346 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 dcaf41690..d879bac0c 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 14c9e480b..87a5d261c 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 f917d2bb7..1e90abc85 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 77ce16ccb..ab6dbb7eb 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 608333d93..da3aba592 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 aedcc3ae0..7e86bbac5 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 fe342e7ae..5a169b11b 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 e6a75c519..e25a3d10d 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 6014ac9bc..9d88d2614 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 8f3dab3fd..08a5483db 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 df9627d80..c3c26782f 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 593f43d3b..b2568f34e 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 000000000..9a60daacf --- /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 fe9f9a736..a6cb7d954 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 bc340a3d0..f99bbcf37 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 05/10] 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 000000000..f178690a1 --- /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 06/10] 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 b0e770463..c1f14f389 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 07/10] 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 df5cd610a..4c1f5cd7c 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 08/10] 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 1a725d346..c16584eb5 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 d879bac0c..2fe95385b 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 6350dde0b..8755e8e80 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 91ec25e2f..7963847af 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 29b57a2eb..c0e6bdd8e 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 1f86a0ab4..0b96b430f 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 b1ab32d7e..2add9c449 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 5e61e2fa6..d2598c129 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 703acee26..116a0c0fe 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 ce5c26122..ffd698995 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 da3aba592..5c5b7cb62 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 5a169b11b..0dbefd61b 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 da9893a22..d5785ae36 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 9d88d2614..fe0c75f55 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 ddcb0eb0f..d6910a4c4 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 77fb32836..bf28301ff 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 c3c26782f..a35885f39 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 eaaa5e19f..93cbb3d97 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 d01775dd6..8c04a7d20 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 f99bbcf37..85cd7cce5 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 09/10] 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 2add9c449..d50f90438 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 6151903fd..03bd8ef91 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 4e7b00abf..b933b2fa7 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 116a0c0fe..70bc694dc 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 c5929c43a..83eb30910 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 10/10] 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 e9615d33f..5306e57ae 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 85cd7cce5..87d63df5b 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.