From 9885065f021784ef7844c1abc03a5f4cfae71342 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Sun, 28 Jul 2019 19:59:52 +0200 Subject: [PATCH] add option to always expand content warnings (#1422) --- .../18.json | 693 ++++++++++++++++++ .../keylesspalace/tusky/TuskyApplication.java | 2 +- .../tusky/adapter/SearchResultsAdapter.java | 199 ----- .../components/search/SearchViewModel.kt | 6 +- .../keylesspalace/tusky/db/AccountEntity.kt | 1 + .../keylesspalace/tusky/db/AppDatabase.java | 9 +- .../tusky/fragment/NotificationsFragment.java | 5 +- .../tusky/fragment/TimelineFragment.java | 5 +- .../tusky/fragment/ViewThreadFragment.java | 5 +- .../preference/AccountPreferencesFragment.kt | 10 + .../tusky/util/ViewDataUtils.java | 11 +- app/src/main/res/values/strings.xml | 1 + app/src/main/res/xml/account_preferences.xml | 5 + 13 files changed, 742 insertions(+), 210 deletions(-) create mode 100644 app/schemas/com.keylesspalace.tusky.db.AppDatabase/18.json delete mode 100644 app/src/main/java/com/keylesspalace/tusky/adapter/SearchResultsAdapter.java diff --git a/app/schemas/com.keylesspalace.tusky.db.AppDatabase/18.json b/app/schemas/com.keylesspalace.tusky.db.AppDatabase/18.json new file mode 100644 index 00000000..0319e673 --- /dev/null +++ b/app/schemas/com.keylesspalace.tusky.db.AppDatabase/18.json @@ -0,0 +1,693 @@ +{ + "formatVersion": 1, + "database": { + "version": 18, + "identityHash": "33d7d9b8ba14c87b96ce795c337bfc57", + "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)", + "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 + } + ], + "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, `notificationsReblogged` INTEGER NOT NULL, `notificationsFavorited` INTEGER NOT NULL, `notificationsPolls` 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": "notificationsReblogged", + "columnName": "notificationsReblogged", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsFavorited", + "columnName": "notificationsFavorited", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationsPolls", + "columnName": "notificationsPolls", + "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 `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, 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 + } + ], + "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, `favourited` INTEGER NOT NULL, `sensitive` INTEGER NOT NULL, `spoilerText` TEXT, `visibility` INTEGER, `attachments` TEXT, `mentions` TEXT, `application` TEXT, `reblogServerId` TEXT, `reblogAccountId` TEXT, `poll` TEXT, PRIMARY KEY(`serverId`, `timelineUserId`), FOREIGN KEY(`authorServerId`, `timelineUserId`) REFERENCES `TimelineAccountEntity`(`serverId`, `timelineUserId`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "serverId", + "columnName": "serverId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "timelineUserId", + "columnName": "timelineUserId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "authorServerId", + "columnName": "authorServerId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inReplyToId", + "columnName": "inReplyToId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inReplyToAccountId", + "columnName": "inReplyToAccountId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "emojis", + "columnName": "emojis", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "reblogsCount", + "columnName": "reblogsCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "favouritesCount", + "columnName": "favouritesCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "reblogged", + "columnName": "reblogged", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "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 + } + ], + "primaryKey": { + "columnNames": [ + "serverId", + "timelineUserId" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_TimelineStatusEntity_authorServerId_timelineUserId", + "unique": false, + "columnNames": [ + "authorServerId", + "timelineUserId" + ], + "createSql": "CREATE INDEX `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_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.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, '33d7d9b8ba14c87b96ce795c337bfc57')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.java b/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.java index 99ab905a..63178026 100644 --- a/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.java +++ b/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.java @@ -68,7 +68,7 @@ public class TuskyApplication extends Application implements HasAndroidInjector AppDatabase.MIGRATION_8_9, AppDatabase.MIGRATION_9_10, AppDatabase.MIGRATION_10_11, AppDatabase.MIGRATION_11_12, AppDatabase.MIGRATION_12_13, AppDatabase.MIGRATION_10_13, AppDatabase.MIGRATION_13_14, AppDatabase.MIGRATION_14_15, AppDatabase.MIGRATION_15_16, - AppDatabase.MIGRATION_16_17) + AppDatabase.MIGRATION_16_17, AppDatabase.MIGRATION_17_18) .build(); accountManager = new AccountManager(appDatabase); serviceLocator = new ServiceLocator() { diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/SearchResultsAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/SearchResultsAdapter.java deleted file mode 100644 index 87978ed8..00000000 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/SearchResultsAdapter.java +++ /dev/null @@ -1,199 +0,0 @@ -/* Copyright 2017 Andrew Dawson - * - * This file is a part of Tusky. - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 3 of the - * License, or (at your option) any later version. - * - * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even - * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along with Tusky; if not, - * see . */ - -package com.keylesspalace.tusky.adapter; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.recyclerview.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import com.keylesspalace.tusky.R; -import com.keylesspalace.tusky.entity.Account; -import com.keylesspalace.tusky.entity.SearchResults; -import com.keylesspalace.tusky.entity.Status; -import com.keylesspalace.tusky.interfaces.LinkListener; -import com.keylesspalace.tusky.interfaces.StatusActionListener; -import com.keylesspalace.tusky.util.ViewDataUtils; -import com.keylesspalace.tusky.viewdata.StatusViewData; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class SearchResultsAdapter extends RecyclerView.Adapter { - private static final int VIEW_TYPE_ACCOUNT = 0; - private static final int VIEW_TYPE_STATUS = 1; - private static final int VIEW_TYPE_HASHTAG = 2; - - private List accountList; - private List statusList; - private List concreteStatusList; - private List hashtagList; - - private boolean mediaPreviewsEnabled; - private boolean alwaysShowSensitiveMedia; - private boolean useAbsoluteTime; - private boolean showBotOverlay; - private boolean animateAvatar; - - private LinkListener linkListener; - private StatusActionListener statusListener; - - public SearchResultsAdapter(LinkListener linkListener, - StatusActionListener statusListener, - boolean mediaPreviewsEnabled, - boolean alwaysShowSensitiveMedia, - boolean useAbsoluteTime, - boolean showBotOverlay, - boolean animateAvatar) { - - this.accountList = Collections.emptyList(); - this.statusList = Collections.emptyList(); - this.concreteStatusList = new ArrayList<>(); - this.hashtagList = Collections.emptyList(); - - this.mediaPreviewsEnabled = mediaPreviewsEnabled; - this.alwaysShowSensitiveMedia = alwaysShowSensitiveMedia; - this.useAbsoluteTime = useAbsoluteTime; - this.showBotOverlay = showBotOverlay; - this.animateAvatar = animateAvatar; - - this.linkListener = linkListener; - this.statusListener = statusListener; - - } - - @NonNull - @Override - public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - switch (viewType) { - default: - case VIEW_TYPE_ACCOUNT: { - View view = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.item_account, parent, false); - return new AccountViewHolder(view); - } - case VIEW_TYPE_HASHTAG: { - View view = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.item_hashtag, parent, false); - return new HashtagViewHolder(view); - } - case VIEW_TYPE_STATUS: { - View view = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.item_status, parent, false); - return new StatusViewHolder(view, useAbsoluteTime); - } - } - } - - @Override - public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { - if (position >= accountList.size()) { - if(position >= accountList.size() + concreteStatusList.size()) { - HashtagViewHolder holder = (HashtagViewHolder) viewHolder; - int index = position - accountList.size() - concreteStatusList.size(); - holder.setup(hashtagList.get(index), linkListener); - } else { - StatusViewHolder holder = (StatusViewHolder) viewHolder; - int index = position - accountList.size(); - holder.setupWithStatus(concreteStatusList.get(index), statusListener, - mediaPreviewsEnabled, showBotOverlay, animateAvatar); - } - } else { - AccountViewHolder holder = (AccountViewHolder) viewHolder; - holder.setupWithAccount(accountList.get(position)); - holder.setupLinkListener(linkListener); - } - } - - @Override - public int getItemCount() { - return accountList.size() + hashtagList.size() + concreteStatusList.size(); - } - - @Override - public int getItemViewType(int position) { - if (position >= accountList.size()) { - if(position >= accountList.size() + concreteStatusList.size()) { - return VIEW_TYPE_HASHTAG; - } else { - return VIEW_TYPE_STATUS; - } - } else { - return VIEW_TYPE_ACCOUNT; - } - } - - @Nullable public Status getStatusAtPosition(int position) { - return statusList.get(position - accountList.size()); - } - - @Nullable public StatusViewData.Concrete getConcreteStatusAtPosition(int position) { - return concreteStatusList.get(position - accountList.size()); - } - - public void updateStatusAtPosition(StatusViewData.Concrete status, int position, boolean doNotify) { - concreteStatusList.set(position - accountList.size(), status); - if(doNotify) { - notifyItemChanged(position); - } - } - - public void removeStatusAtPosition(int position) { - concreteStatusList.remove(position - accountList.size()); - notifyItemRemoved(position); - } - - public void updateSearchResults(SearchResults results) { - if (results != null) { - accountList = results.getAccounts(); - statusList = results.getStatuses(); - concreteStatusList.clear(); - for(Status status: results.getStatuses()) { - concreteStatusList.add(ViewDataUtils.statusToViewData( - status, - alwaysShowSensitiveMedia - )); - } - hashtagList = results.getHashtags(); - - } else { - accountList = Collections.emptyList(); - statusList = Collections.emptyList(); - concreteStatusList.clear(); - hashtagList = Collections.emptyList(); - - } - notifyDataSetChanged(); - } - - private static class HashtagViewHolder extends RecyclerView.ViewHolder { - private TextView hashtag; - - HashtagViewHolder(View itemView) { - super(itemView); - hashtag = itemView.findViewById(R.id.hashtag); - } - - void setup(final String tag, final LinkListener listener) { - hashtag.setText(String.format("#%s", tag)); - hashtag.setOnClickListener(v -> listener.onViewTag(tag)); - } - } -} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/SearchViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/SearchViewModel.kt index 0974b03c..c3c6b976 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/SearchViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/SearchViewModel.kt @@ -45,7 +45,9 @@ class SearchViewModel @Inject constructor( private val statusesRepository = SearchRepository>(mastodonApi) private val accountsRepository = SearchRepository(mastodonApi) private val hashtagsRepository = SearchRepository(mastodonApi) - var alwaysShowSensitiveMedia: Boolean = activeAccount?.alwaysShowSensitiveMedia + val alwaysShowSensitiveMedia: Boolean = activeAccount?.alwaysShowSensitiveMedia + ?: false + val alwaysOpenSpoiler: Boolean = activeAccount?.alwaysOpenSpoiler ?: false private val repoResultStatus = MutableLiveData>>() @@ -67,7 +69,7 @@ class SearchViewModel @Inject constructor( fun search(query: String?) { loadedStatuses.clear() repoResultStatus.value = statusesRepository.getSearchData(SearchType.Status, query, disposables, initialItems = loadedStatuses) { - (it?.statuses?.map { status -> Pair(status, ViewDataUtils.statusToViewData(status, alwaysShowSensitiveMedia)!!) } + (it?.statuses?.map { status -> Pair(status, ViewDataUtils.statusToViewData(status, alwaysShowSensitiveMedia, alwaysOpenSpoiler)!!) } ?: emptyList()) .apply { loadedStatuses.addAll(this) 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 83b88e8f..e4948c3d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt @@ -48,6 +48,7 @@ data class AccountEntity(@field:PrimaryKey(autoGenerate = true) var id: Long, var defaultPostPrivacy: Status.Visibility = Status.Visibility.PUBLIC, var defaultMediaSensitivity: Boolean = false, var alwaysShowSensitiveMedia: Boolean = false, + var alwaysOpenSpoiler: Boolean = false, var mediaPreviewEnabled: Boolean = true, var lastNotificationId: String = "0", var activeNotifications: String = "[]", 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 c6412783..54fd411c 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 = 17) + }, version = 18) public abstract class AppDatabase extends RoomDatabase { public abstract TootDao tootDao(); @@ -293,4 +293,11 @@ public abstract class AppDatabase extends RoomDatabase { } }; + public static final Migration MIGRATION_17_18 = new Migration(17, 18) { + @Override + public void migrate(@NonNull SupportSQLiteDatabase database) { + database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `alwaysOpenSpoiler` INTEGER NOT NULL DEFAULT 0"); + } + }; + } \ No newline at end of file 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 cffd1d63..59f1b236 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java @@ -162,6 +162,7 @@ public class NotificationsFragment extends SFragment implements private boolean bottomLoading; private String bottomId; private boolean alwaysShowSensitiveMedia; + private boolean alwaysOpenSpoiler; private boolean showNotificationsFilter; // Each element is either a Notification for loading data or a Placeholder @@ -173,7 +174,8 @@ public class NotificationsFragment extends SFragment implements Notification notification = input.asRight(); return ViewDataUtils.notificationToViewData( notification, - alwaysShowSensitiveMedia + alwaysShowSensitiveMedia, + alwaysOpenSpoiler ); } else { return new NotificationViewData.Placeholder(input.asLeft().id, false); @@ -236,6 +238,7 @@ public class NotificationsFragment extends SFragment implements adapter = new NotificationsAdapter(accountManager.getActiveAccount().getAccountId(), dataSource, this, this); alwaysShowSensitiveMedia = accountManager.getActiveAccount().getAlwaysShowSensitiveMedia(); + alwaysOpenSpoiler = accountManager.getActiveAccount().getAlwaysOpenSpoiler(); boolean mediaPreviewEnabled = accountManager.getActiveAccount().getMediaPreviewEnabled(); adapter.setMediaPreviewEnabled(mediaPreviewEnabled); boolean useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false); 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 2860f50d..6dd0ea46 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java @@ -166,6 +166,7 @@ public class TimelineFragment extends SFragment implements private boolean didLoadEverythingBottom; private boolean alwaysShowSensitiveMedia; + private boolean alwaysOpenSpoiler; private boolean initialUpdateFailed = false; private PairedList, StatusViewData> statuses = @@ -176,7 +177,8 @@ public class TimelineFragment extends SFragment implements if (status != null) { return ViewDataUtils.statusToViewData( status, - alwaysShowSensitiveMedia + alwaysShowSensitiveMedia, + alwaysOpenSpoiler ); } else { Placeholder placeholder = input.asLeft(); @@ -340,6 +342,7 @@ public class TimelineFragment extends SFragment implements private void setupTimelinePreferences() { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity()); alwaysShowSensitiveMedia = accountManager.getActiveAccount().getAlwaysShowSensitiveMedia(); + alwaysOpenSpoiler = accountManager.getActiveAccount().getAlwaysOpenSpoiler(); boolean mediaPreviewEnabled = accountManager.getActiveAccount().getMediaPreviewEnabled(); adapter.setMediaPreviewEnabled(mediaPreviewEnabled); boolean useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false); 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 b5e8878f..7239f793 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java @@ -92,6 +92,7 @@ public final class ViewThreadFragment extends SFragment implements private ThreadAdapter adapter; private String thisThreadsStatusId; private boolean alwaysShowSensitiveMedia; + private boolean alwaysOpenSpoiler; private int statusIndex = 0; @@ -101,7 +102,8 @@ public final class ViewThreadFragment extends SFragment implements public StatusViewData.Concrete apply(Status input) { return ViewDataUtils.statusToViewData( input, - alwaysShowSensitiveMedia + alwaysShowSensitiveMedia, + alwaysOpenSpoiler ); } }); @@ -149,6 +151,7 @@ public final class ViewThreadFragment extends SFragment implements SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences( getActivity()); alwaysShowSensitiveMedia = accountManager.getActiveAccount().getAlwaysShowSensitiveMedia(); + alwaysOpenSpoiler = accountManager.getActiveAccount().getAlwaysOpenSpoiler(); boolean mediaPreviewEnabled = accountManager.getActiveAccount().getMediaPreviewEnabled(); adapter.setMediaPreviewEnabled(mediaPreviewEnabled); boolean useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false); diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/preference/AccountPreferencesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/preference/AccountPreferencesFragment.kt index 409f5947..0ab9e136 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/preference/AccountPreferencesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/preference/AccountPreferencesFragment.kt @@ -65,6 +65,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), private lateinit var defaultPostPrivacyPreference: ListPreference private lateinit var defaultMediaSensitivityPreference: SwitchPreferenceCompat private lateinit var alwaysShowSensitiveMediaPreference: SwitchPreferenceCompat + private lateinit var alwaysOpenSpoilerPreference: SwitchPreferenceCompat private lateinit var mediaPreviewEnabledPreference: SwitchPreferenceCompat private lateinit var homeFiltersPreference: Preference private lateinit var notificationFiltersPreference: Preference @@ -85,6 +86,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), defaultMediaSensitivityPreference = requirePreference("defaultMediaSensitivity") as SwitchPreferenceCompat mediaPreviewEnabledPreference = requirePreference("mediaPreviewEnabled") as SwitchPreferenceCompat alwaysShowSensitiveMediaPreference = requirePreference("alwaysShowSensitiveMedia") as SwitchPreferenceCompat + alwaysOpenSpoilerPreference = requirePreference("alwaysOpenSpoiler") as SwitchPreferenceCompat homeFiltersPreference = requirePreference("homeFilters") notificationFiltersPreference = requirePreference("notificationFilters") publicFiltersPreference = requirePreference("publicFilters") @@ -109,6 +111,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), defaultMediaSensitivityPreference.onPreferenceChangeListener = this mediaPreviewEnabledPreference.onPreferenceChangeListener = this alwaysShowSensitiveMediaPreference.onPreferenceChangeListener = this + alwaysOpenSpoilerPreference.onPreferenceChangeListener = this } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -124,6 +127,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), mediaPreviewEnabledPreference.isChecked = it.mediaPreviewEnabled alwaysShowSensitiveMediaPreference.isChecked = it.alwaysShowSensitiveMedia + alwaysOpenSpoilerPreference.isChecked = it.alwaysOpenSpoiler } } @@ -150,6 +154,12 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), accountManager.saveAccount(it) } } + alwaysOpenSpoilerPreference -> { + accountManager.activeAccount?.let { + it.alwaysOpenSpoiler = newValue as Boolean + accountManager.saveAccount(it) + } + } } eventHub.dispatch(PreferenceChangedEvent(preference.key)) diff --git a/app/src/main/java/com/keylesspalace/tusky/util/ViewDataUtils.java b/app/src/main/java/com/keylesspalace/tusky/util/ViewDataUtils.java index 5e213068..7997ce86 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/ViewDataUtils.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/ViewDataUtils.java @@ -29,7 +29,8 @@ import com.keylesspalace.tusky.viewdata.StatusViewData; public final class ViewDataUtils { @Nullable public static StatusViewData.Concrete statusToViewData(@Nullable Status status, - boolean alwaysShowSensitiveMedia) { + boolean alwaysShowSensitiveMedia, + boolean alwaysOpenSpoiler) { if (status == null) return null; Status visibleStatus = status.getReblog() == null ? status : status.getReblog(); return new StatusViewData.Builder().setId(status.getId()) @@ -42,7 +43,7 @@ public final class ViewDataUtils { .setInReplyToId(visibleStatus.getInReplyToId()) .setFavourited(visibleStatus.getFavourited()) .setReblogged(visibleStatus.getReblogged()) - .setIsExpanded(false) + .setIsExpanded(alwaysOpenSpoiler) .setIsShowingSensitiveContent(false) .setMentions(visibleStatus.getMentions()) .setNickname(visibleStatus.getAccount().getUsername()) @@ -67,14 +68,16 @@ public final class ViewDataUtils { } public static NotificationViewData.Concrete notificationToViewData(Notification notification, - boolean alwaysShowSensitiveData) { + boolean alwaysShowSensitiveData, + boolean alwaysOpenSpoiler) { return new NotificationViewData.Concrete( notification.getType(), notification.getId(), notification.getAccount(), statusToViewData( notification.getStatus(), - alwaysShowSensitiveData + alwaysShowSensitiveData, + alwaysOpenSpoiler ), false ); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 62264c01..2dc0ad4d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -313,6 +313,7 @@ Follows you Always show sensitive content + Always expand toots marked with content warnings Media Replying to @%s load more diff --git a/app/src/main/res/xml/account_preferences.xml b/app/src/main/res/xml/account_preferences.xml index dff44b9f..5b316c72 100644 --- a/app/src/main/res/xml/account_preferences.xml +++ b/app/src/main/res/xml/account_preferences.xml @@ -50,6 +50,11 @@ android:key="alwaysShowSensitiveMedia" android:title="@string/pref_title_alway_show_sensitive_media" app:singleLineTitle="false" /> + +