add option to always expand content warnings (#1422)

This commit is contained in:
Konrad Pozniak 2019-07-28 19:59:52 +02:00 committed by GitHub
parent 588775ff9b
commit 8834c22120
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 742 additions and 210 deletions

View file

@ -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')"
]
}
}

View file

@ -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_8_9, AppDatabase.MIGRATION_9_10, AppDatabase.MIGRATION_10_11,
AppDatabase.MIGRATION_11_12, AppDatabase.MIGRATION_12_13, AppDatabase.MIGRATION_10_13, 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_13_14, AppDatabase.MIGRATION_14_15, AppDatabase.MIGRATION_15_16,
AppDatabase.MIGRATION_16_17) AppDatabase.MIGRATION_16_17, AppDatabase.MIGRATION_17_18)
.build(); .build();
accountManager = new AccountManager(appDatabase); accountManager = new AccountManager(appDatabase);
serviceLocator = new ServiceLocator() { serviceLocator = new ServiceLocator() {

View file

@ -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 <http://www.gnu.org/licenses>. */
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<Account> accountList;
private List<Status> statusList;
private List<StatusViewData.Concrete> concreteStatusList;
private List<String> 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));
}
}
}

View file

@ -45,7 +45,9 @@ class SearchViewModel @Inject constructor(
private val statusesRepository = SearchRepository<Pair<Status, StatusViewData.Concrete>>(mastodonApi) private val statusesRepository = SearchRepository<Pair<Status, StatusViewData.Concrete>>(mastodonApi)
private val accountsRepository = SearchRepository<Account>(mastodonApi) private val accountsRepository = SearchRepository<Account>(mastodonApi)
private val hashtagsRepository = SearchRepository<HashTag>(mastodonApi) private val hashtagsRepository = SearchRepository<HashTag>(mastodonApi)
var alwaysShowSensitiveMedia: Boolean = activeAccount?.alwaysShowSensitiveMedia val alwaysShowSensitiveMedia: Boolean = activeAccount?.alwaysShowSensitiveMedia
?: false
val alwaysOpenSpoiler: Boolean = activeAccount?.alwaysOpenSpoiler
?: false ?: false
private val repoResultStatus = MutableLiveData<Listing<Pair<Status, StatusViewData.Concrete>>>() private val repoResultStatus = MutableLiveData<Listing<Pair<Status, StatusViewData.Concrete>>>()
@ -67,7 +69,7 @@ class SearchViewModel @Inject constructor(
fun search(query: String?) { fun search(query: String?) {
loadedStatuses.clear() loadedStatuses.clear()
repoResultStatus.value = statusesRepository.getSearchData(SearchType.Status, query, disposables, initialItems = loadedStatuses) { 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()) ?: emptyList())
.apply { .apply {
loadedStatuses.addAll(this) loadedStatuses.addAll(this)

View file

@ -48,6 +48,7 @@ data class AccountEntity(@field:PrimaryKey(autoGenerate = true) var id: Long,
var defaultPostPrivacy: Status.Visibility = Status.Visibility.PUBLIC, var defaultPostPrivacy: Status.Visibility = Status.Visibility.PUBLIC,
var defaultMediaSensitivity: Boolean = false, var defaultMediaSensitivity: Boolean = false,
var alwaysShowSensitiveMedia: Boolean = false, var alwaysShowSensitiveMedia: Boolean = false,
var alwaysOpenSpoiler: Boolean = false,
var mediaPreviewEnabled: Boolean = true, var mediaPreviewEnabled: Boolean = true,
var lastNotificationId: String = "0", var lastNotificationId: String = "0",
var activeNotifications: String = "[]", var activeNotifications: String = "[]",

View file

@ -30,7 +30,7 @@ import androidx.annotation.NonNull;
@Database(entities = {TootEntity.class, AccountEntity.class, InstanceEntity.class, TimelineStatusEntity.class, @Database(entities = {TootEntity.class, AccountEntity.class, InstanceEntity.class, TimelineStatusEntity.class,
TimelineAccountEntity.class, ConversationEntity.class TimelineAccountEntity.class, ConversationEntity.class
}, version = 17) }, version = 18)
public abstract class AppDatabase extends RoomDatabase { public abstract class AppDatabase extends RoomDatabase {
public abstract TootDao tootDao(); 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");
}
};
} }

View file

@ -162,6 +162,7 @@ public class NotificationsFragment extends SFragment implements
private boolean bottomLoading; private boolean bottomLoading;
private String bottomId; private String bottomId;
private boolean alwaysShowSensitiveMedia; private boolean alwaysShowSensitiveMedia;
private boolean alwaysOpenSpoiler;
private boolean showNotificationsFilter; private boolean showNotificationsFilter;
// Each element is either a Notification for loading data or a Placeholder // 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(); Notification notification = input.asRight();
return ViewDataUtils.notificationToViewData( return ViewDataUtils.notificationToViewData(
notification, notification,
alwaysShowSensitiveMedia alwaysShowSensitiveMedia,
alwaysOpenSpoiler
); );
} else { } else {
return new NotificationViewData.Placeholder(input.asLeft().id, false); return new NotificationViewData.Placeholder(input.asLeft().id, false);
@ -236,6 +238,7 @@ public class NotificationsFragment extends SFragment implements
adapter = new NotificationsAdapter(accountManager.getActiveAccount().getAccountId(), adapter = new NotificationsAdapter(accountManager.getActiveAccount().getAccountId(),
dataSource, this, this); dataSource, this, this);
alwaysShowSensitiveMedia = accountManager.getActiveAccount().getAlwaysShowSensitiveMedia(); alwaysShowSensitiveMedia = accountManager.getActiveAccount().getAlwaysShowSensitiveMedia();
alwaysOpenSpoiler = accountManager.getActiveAccount().getAlwaysOpenSpoiler();
boolean mediaPreviewEnabled = accountManager.getActiveAccount().getMediaPreviewEnabled(); boolean mediaPreviewEnabled = accountManager.getActiveAccount().getMediaPreviewEnabled();
adapter.setMediaPreviewEnabled(mediaPreviewEnabled); adapter.setMediaPreviewEnabled(mediaPreviewEnabled);
boolean useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false); boolean useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false);

View file

@ -166,6 +166,7 @@ public class TimelineFragment extends SFragment implements
private boolean didLoadEverythingBottom; private boolean didLoadEverythingBottom;
private boolean alwaysShowSensitiveMedia; private boolean alwaysShowSensitiveMedia;
private boolean alwaysOpenSpoiler;
private boolean initialUpdateFailed = false; private boolean initialUpdateFailed = false;
private PairedList<Either<Placeholder, Status>, StatusViewData> statuses = private PairedList<Either<Placeholder, Status>, StatusViewData> statuses =
@ -176,7 +177,8 @@ public class TimelineFragment extends SFragment implements
if (status != null) { if (status != null) {
return ViewDataUtils.statusToViewData( return ViewDataUtils.statusToViewData(
status, status,
alwaysShowSensitiveMedia alwaysShowSensitiveMedia,
alwaysOpenSpoiler
); );
} else { } else {
Placeholder placeholder = input.asLeft(); Placeholder placeholder = input.asLeft();
@ -340,6 +342,7 @@ public class TimelineFragment extends SFragment implements
private void setupTimelinePreferences() { private void setupTimelinePreferences() {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity()); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
alwaysShowSensitiveMedia = accountManager.getActiveAccount().getAlwaysShowSensitiveMedia(); alwaysShowSensitiveMedia = accountManager.getActiveAccount().getAlwaysShowSensitiveMedia();
alwaysOpenSpoiler = accountManager.getActiveAccount().getAlwaysOpenSpoiler();
boolean mediaPreviewEnabled = accountManager.getActiveAccount().getMediaPreviewEnabled(); boolean mediaPreviewEnabled = accountManager.getActiveAccount().getMediaPreviewEnabled();
adapter.setMediaPreviewEnabled(mediaPreviewEnabled); adapter.setMediaPreviewEnabled(mediaPreviewEnabled);
boolean useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false); boolean useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false);

View file

@ -92,6 +92,7 @@ public final class ViewThreadFragment extends SFragment implements
private ThreadAdapter adapter; private ThreadAdapter adapter;
private String thisThreadsStatusId; private String thisThreadsStatusId;
private boolean alwaysShowSensitiveMedia; private boolean alwaysShowSensitiveMedia;
private boolean alwaysOpenSpoiler;
private int statusIndex = 0; private int statusIndex = 0;
@ -101,7 +102,8 @@ public final class ViewThreadFragment extends SFragment implements
public StatusViewData.Concrete apply(Status input) { public StatusViewData.Concrete apply(Status input) {
return ViewDataUtils.statusToViewData( return ViewDataUtils.statusToViewData(
input, input,
alwaysShowSensitiveMedia alwaysShowSensitiveMedia,
alwaysOpenSpoiler
); );
} }
}); });
@ -149,6 +151,7 @@ public final class ViewThreadFragment extends SFragment implements
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences( SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(
getActivity()); getActivity());
alwaysShowSensitiveMedia = accountManager.getActiveAccount().getAlwaysShowSensitiveMedia(); alwaysShowSensitiveMedia = accountManager.getActiveAccount().getAlwaysShowSensitiveMedia();
alwaysOpenSpoiler = accountManager.getActiveAccount().getAlwaysOpenSpoiler();
boolean mediaPreviewEnabled = accountManager.getActiveAccount().getMediaPreviewEnabled(); boolean mediaPreviewEnabled = accountManager.getActiveAccount().getMediaPreviewEnabled();
adapter.setMediaPreviewEnabled(mediaPreviewEnabled); adapter.setMediaPreviewEnabled(mediaPreviewEnabled);
boolean useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false); boolean useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false);

View file

@ -65,6 +65,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(),
private lateinit var defaultPostPrivacyPreference: ListPreference private lateinit var defaultPostPrivacyPreference: ListPreference
private lateinit var defaultMediaSensitivityPreference: SwitchPreferenceCompat private lateinit var defaultMediaSensitivityPreference: SwitchPreferenceCompat
private lateinit var alwaysShowSensitiveMediaPreference: SwitchPreferenceCompat private lateinit var alwaysShowSensitiveMediaPreference: SwitchPreferenceCompat
private lateinit var alwaysOpenSpoilerPreference: SwitchPreferenceCompat
private lateinit var mediaPreviewEnabledPreference: SwitchPreferenceCompat private lateinit var mediaPreviewEnabledPreference: SwitchPreferenceCompat
private lateinit var homeFiltersPreference: Preference private lateinit var homeFiltersPreference: Preference
private lateinit var notificationFiltersPreference: Preference private lateinit var notificationFiltersPreference: Preference
@ -85,6 +86,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(),
defaultMediaSensitivityPreference = requirePreference("defaultMediaSensitivity") as SwitchPreferenceCompat defaultMediaSensitivityPreference = requirePreference("defaultMediaSensitivity") as SwitchPreferenceCompat
mediaPreviewEnabledPreference = requirePreference("mediaPreviewEnabled") as SwitchPreferenceCompat mediaPreviewEnabledPreference = requirePreference("mediaPreviewEnabled") as SwitchPreferenceCompat
alwaysShowSensitiveMediaPreference = requirePreference("alwaysShowSensitiveMedia") as SwitchPreferenceCompat alwaysShowSensitiveMediaPreference = requirePreference("alwaysShowSensitiveMedia") as SwitchPreferenceCompat
alwaysOpenSpoilerPreference = requirePreference("alwaysOpenSpoiler") as SwitchPreferenceCompat
homeFiltersPreference = requirePreference("homeFilters") homeFiltersPreference = requirePreference("homeFilters")
notificationFiltersPreference = requirePreference("notificationFilters") notificationFiltersPreference = requirePreference("notificationFilters")
publicFiltersPreference = requirePreference("publicFilters") publicFiltersPreference = requirePreference("publicFilters")
@ -109,6 +111,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(),
defaultMediaSensitivityPreference.onPreferenceChangeListener = this defaultMediaSensitivityPreference.onPreferenceChangeListener = this
mediaPreviewEnabledPreference.onPreferenceChangeListener = this mediaPreviewEnabledPreference.onPreferenceChangeListener = this
alwaysShowSensitiveMediaPreference.onPreferenceChangeListener = this alwaysShowSensitiveMediaPreference.onPreferenceChangeListener = this
alwaysOpenSpoilerPreference.onPreferenceChangeListener = this
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -124,6 +127,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(),
mediaPreviewEnabledPreference.isChecked = it.mediaPreviewEnabled mediaPreviewEnabledPreference.isChecked = it.mediaPreviewEnabled
alwaysShowSensitiveMediaPreference.isChecked = it.alwaysShowSensitiveMedia alwaysShowSensitiveMediaPreference.isChecked = it.alwaysShowSensitiveMedia
alwaysOpenSpoilerPreference.isChecked = it.alwaysOpenSpoiler
} }
} }
@ -150,6 +154,12 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(),
accountManager.saveAccount(it) accountManager.saveAccount(it)
} }
} }
alwaysOpenSpoilerPreference -> {
accountManager.activeAccount?.let {
it.alwaysOpenSpoiler = newValue as Boolean
accountManager.saveAccount(it)
}
}
} }
eventHub.dispatch(PreferenceChangedEvent(preference.key)) eventHub.dispatch(PreferenceChangedEvent(preference.key))

View file

@ -29,7 +29,8 @@ import com.keylesspalace.tusky.viewdata.StatusViewData;
public final class ViewDataUtils { public final class ViewDataUtils {
@Nullable @Nullable
public static StatusViewData.Concrete statusToViewData(@Nullable Status status, public static StatusViewData.Concrete statusToViewData(@Nullable Status status,
boolean alwaysShowSensitiveMedia) { boolean alwaysShowSensitiveMedia,
boolean alwaysOpenSpoiler) {
if (status == null) return null; if (status == null) return null;
Status visibleStatus = status.getReblog() == null ? status : status.getReblog(); Status visibleStatus = status.getReblog() == null ? status : status.getReblog();
return new StatusViewData.Builder().setId(status.getId()) return new StatusViewData.Builder().setId(status.getId())
@ -42,7 +43,7 @@ public final class ViewDataUtils {
.setInReplyToId(visibleStatus.getInReplyToId()) .setInReplyToId(visibleStatus.getInReplyToId())
.setFavourited(visibleStatus.getFavourited()) .setFavourited(visibleStatus.getFavourited())
.setReblogged(visibleStatus.getReblogged()) .setReblogged(visibleStatus.getReblogged())
.setIsExpanded(false) .setIsExpanded(alwaysOpenSpoiler)
.setIsShowingSensitiveContent(false) .setIsShowingSensitiveContent(false)
.setMentions(visibleStatus.getMentions()) .setMentions(visibleStatus.getMentions())
.setNickname(visibleStatus.getAccount().getUsername()) .setNickname(visibleStatus.getAccount().getUsername())
@ -67,14 +68,16 @@ public final class ViewDataUtils {
} }
public static NotificationViewData.Concrete notificationToViewData(Notification notification, public static NotificationViewData.Concrete notificationToViewData(Notification notification,
boolean alwaysShowSensitiveData) { boolean alwaysShowSensitiveData,
boolean alwaysOpenSpoiler) {
return new NotificationViewData.Concrete( return new NotificationViewData.Concrete(
notification.getType(), notification.getType(),
notification.getId(), notification.getId(),
notification.getAccount(), notification.getAccount(),
statusToViewData( statusToViewData(
notification.getStatus(), notification.getStatus(),
alwaysShowSensitiveData alwaysShowSensitiveData,
alwaysOpenSpoiler
), ),
false false
); );

View file

@ -313,6 +313,7 @@
<string name="follows_you">Follows you</string> <string name="follows_you">Follows you</string>
<string name="pref_title_alway_show_sensitive_media">Always show sensitive content</string> <string name="pref_title_alway_show_sensitive_media">Always show sensitive content</string>
<string name="pref_title_alway_open_spoiler">Always expand toots marked with content warnings</string>
<string name="title_media">Media</string> <string name="title_media">Media</string>
<string name="replying_to">Replying to @%s</string> <string name="replying_to">Replying to @%s</string>
<string name="load_more_placeholder_text">load more</string> <string name="load_more_placeholder_text">load more</string>

View file

@ -50,6 +50,11 @@
android:key="alwaysShowSensitiveMedia" android:key="alwaysShowSensitiveMedia"
android:title="@string/pref_title_alway_show_sensitive_media" android:title="@string/pref_title_alway_show_sensitive_media"
app:singleLineTitle="false" /> app:singleLineTitle="false" />
<SwitchPreferenceCompat
android:key="alwaysOpenSpoiler"
android:title="@string/pref_title_alway_open_spoiler"
app:singleLineTitle="false" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="@string/pref_title_timeline_filters"> <PreferenceCategory android:title="@string/pref_title_timeline_filters">