Refactor notifications to Kotlin & paging (#4026)
This refactors the NotificationsFragment and related classes to Kotlin & paging. While trying to preserve as much of the original behavior as possible, this adds the following improvements as well: - The "show notifications filter" preference was added again - The "load more" button now has a background ripple effect when clicked - The "legal" report category of Mastodon 4.2 is now supported in report notifications - Unknown notifications now display "unknown notification type" instead of an empty line Other code quality improvements: - All views from xml layouts are now referenced via ViewBindings - the classes responsible for showing system notifications were moved to a new package `systemnotifications` while the classes from this refactoring are in `notifications` - the id of the local Tusky account is now called `tuskyAccountId` in all places I could find closes https://github.com/tuskyapp/Tusky/issues/3429 --------- Co-authored-by: Zongle Wang <wangzongler@gmail.com>
This commit is contained in:
parent
3bbf96b057
commit
b2c0b18c8e
121 changed files with 6992 additions and 4654 deletions
|
|
@ -18,6 +18,8 @@ package com.keylesspalace.tusky.db
|
|||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.keylesspalace.tusky.db.dao.AccountDao
|
||||
import com.keylesspalace.tusky.db.entity.AccountEntity
|
||||
import com.keylesspalace.tusky.entity.Account
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.settings.PrefKeys
|
||||
|
|
|
|||
|
|
@ -27,6 +27,21 @@ import androidx.sqlite.db.SupportSQLiteDatabase;
|
|||
|
||||
import com.keylesspalace.tusky.TabDataKt;
|
||||
import com.keylesspalace.tusky.components.conversation.ConversationEntity;
|
||||
import com.keylesspalace.tusky.db.dao.AccountDao;
|
||||
import com.keylesspalace.tusky.db.dao.DraftDao;
|
||||
import com.keylesspalace.tusky.db.dao.InstanceDao;
|
||||
import com.keylesspalace.tusky.db.dao.NotificationsDao;
|
||||
import com.keylesspalace.tusky.db.dao.TimelineAccountDao;
|
||||
import com.keylesspalace.tusky.db.dao.TimelineDao;
|
||||
import com.keylesspalace.tusky.db.dao.TimelineStatusDao;
|
||||
import com.keylesspalace.tusky.db.entity.AccountEntity;
|
||||
import com.keylesspalace.tusky.db.entity.DraftEntity;
|
||||
import com.keylesspalace.tusky.db.entity.HomeTimelineEntity;
|
||||
import com.keylesspalace.tusky.db.entity.InstanceEntity;
|
||||
import com.keylesspalace.tusky.db.entity.NotificationEntity;
|
||||
import com.keylesspalace.tusky.db.entity.NotificationReportEntity;
|
||||
import com.keylesspalace.tusky.db.entity.TimelineAccountEntity;
|
||||
import com.keylesspalace.tusky.db.entity.TimelineStatusEntity;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
|
|
@ -40,11 +55,14 @@ import java.io.File;
|
|||
InstanceEntity.class,
|
||||
TimelineStatusEntity.class,
|
||||
TimelineAccountEntity.class,
|
||||
ConversationEntity.class
|
||||
ConversationEntity.class,
|
||||
NotificationEntity.class,
|
||||
NotificationReportEntity.class,
|
||||
HomeTimelineEntity.class
|
||||
},
|
||||
// Note: Starting with version 54, database versions in Tusky are always even.
|
||||
// This is to reserve odd version numbers for use by forks.
|
||||
version = 58,
|
||||
version = 60,
|
||||
autoMigrations = {
|
||||
@AutoMigration(from = 48, to = 49),
|
||||
@AutoMigration(from = 49, to = 50, spec = AppDatabase.MIGRATION_49_50.class),
|
||||
|
|
@ -61,6 +79,9 @@ public abstract class AppDatabase extends RoomDatabase {
|
|||
@NonNull public abstract ConversationsDao conversationDao();
|
||||
@NonNull public abstract TimelineDao timelineDao();
|
||||
@NonNull public abstract DraftDao draftDao();
|
||||
@NonNull public abstract NotificationsDao notificationsDao();
|
||||
@NonNull public abstract TimelineStatusDao timelineStatusDao();
|
||||
@NonNull public abstract TimelineAccountDao timelineAccountDao();
|
||||
|
||||
public static final Migration MIGRATION_2_3 = new Migration(2, 3) {
|
||||
@Override
|
||||
|
|
@ -698,4 +719,126 @@ public abstract class AppDatabase extends RoomDatabase {
|
|||
database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `isShowHomeSelfBoosts` INTEGER NOT NULL DEFAULT 1");
|
||||
}
|
||||
};
|
||||
|
||||
public static final Migration MIGRATION_58_60 = new Migration(58, 60) {
|
||||
@Override
|
||||
public void migrate(@NonNull SupportSQLiteDatabase database) {
|
||||
// drop the old tables - they are only caches anyway
|
||||
database.execSQL("DROP TABLE `TimelineStatusEntity`");
|
||||
database.execSQL("DROP TABLE `TimelineAccountEntity`");
|
||||
|
||||
// create the new tables
|
||||
database.execSQL("""
|
||||
CREATE TABLE IF NOT EXISTS `TimelineAccountEntity` (
|
||||
`serverId` TEXT NOT NULL,
|
||||
`tuskyAccountId` 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`, `tuskyAccountId`)
|
||||
)"""
|
||||
);
|
||||
database.execSQL("""
|
||||
CREATE TABLE IF NOT EXISTS `TimelineStatusEntity` (
|
||||
`serverId` TEXT NOT NULL,
|
||||
`url` TEXT,
|
||||
`tuskyAccountId` INTEGER NOT NULL,
|
||||
`authorServerId` TEXT NOT NULL,
|
||||
`inReplyToId` TEXT,
|
||||
`inReplyToAccountId` TEXT,
|
||||
`content` TEXT NOT NULL,
|
||||
`createdAt` INTEGER NOT NULL,
|
||||
`editedAt` INTEGER,
|
||||
`emojis` TEXT NOT NULL,
|
||||
`reblogsCount` INTEGER NOT NULL,
|
||||
`favouritesCount` INTEGER NOT NULL,
|
||||
`repliesCount` INTEGER NOT NULL,
|
||||
`reblogged` INTEGER NOT NULL,
|
||||
`bookmarked` INTEGER NOT NULL,
|
||||
`favourited` INTEGER NOT NULL,
|
||||
`sensitive` INTEGER NOT NULL,
|
||||
`spoilerText` TEXT NOT NULL,
|
||||
`visibility` INTEGER NOT NULL,
|
||||
`attachments` TEXT NOT NULL,
|
||||
`mentions` TEXT NOT NULL,
|
||||
`tags` TEXT NOT NULL,
|
||||
`application` TEXT,
|
||||
`poll` TEXT,
|
||||
`muted` INTEGER NOT NULL,
|
||||
`expanded` INTEGER NOT NULL,
|
||||
`contentCollapsed` INTEGER NOT NULL,
|
||||
`contentShowing` INTEGER NOT NULL,
|
||||
`pinned` INTEGER NOT NULL,
|
||||
`card` TEXT, `language` TEXT,
|
||||
`filtered` TEXT NOT NULL,
|
||||
PRIMARY KEY(`serverId`, `tuskyAccountId`),
|
||||
FOREIGN KEY(`authorServerId`, `tuskyAccountId`) REFERENCES `TimelineAccountEntity`(`serverId`, `tuskyAccountId`) ON UPDATE NO ACTION ON DELETE NO ACTION
|
||||
)"""
|
||||
);
|
||||
database.execSQL(
|
||||
"CREATE INDEX IF NOT EXISTS `index_TimelineStatusEntity_authorServerId_tuskyAccountId` ON `TimelineStatusEntity` (`authorServerId`, `tuskyAccountId`)"
|
||||
);
|
||||
database.execSQL("""
|
||||
CREATE TABLE IF NOT EXISTS `HomeTimelineEntity` (
|
||||
`tuskyAccountId` INTEGER NOT NULL,
|
||||
`id` TEXT NOT NULL,
|
||||
`statusId` TEXT,
|
||||
`reblogAccountId` TEXT,
|
||||
`loading` INTEGER NOT NULL,
|
||||
PRIMARY KEY(`id`, `tuskyAccountId`),
|
||||
FOREIGN KEY(`statusId`, `tuskyAccountId`) REFERENCES `TimelineStatusEntity`(`serverId`, `tuskyAccountId`) ON UPDATE NO ACTION ON DELETE NO ACTION,
|
||||
FOREIGN KEY(`reblogAccountId`, `tuskyAccountId`) REFERENCES `TimelineAccountEntity`(`serverId`, `tuskyAccountId`) ON UPDATE NO ACTION ON DELETE NO ACTION
|
||||
)"""
|
||||
);
|
||||
database.execSQL(
|
||||
"CREATE INDEX IF NOT EXISTS `index_HomeTimelineEntity_statusId_tuskyAccountId` ON `HomeTimelineEntity` (`statusId`, `tuskyAccountId`)"
|
||||
);
|
||||
database.execSQL(
|
||||
"CREATE INDEX IF NOT EXISTS `index_HomeTimelineEntity_reblogAccountId_tuskyAccountId` ON `HomeTimelineEntity` (`reblogAccountId`, `tuskyAccountId`)"
|
||||
);
|
||||
database.execSQL("""
|
||||
CREATE TABLE IF NOT EXISTS `NotificationReportEntity`(
|
||||
`tuskyAccountId` INTEGER NOT NULL,
|
||||
`serverId` TEXT NOT NULL,
|
||||
`category` TEXT NOT NULL,
|
||||
`statusIds` TEXT,
|
||||
`createdAt` INTEGER NOT NULL,
|
||||
`targetAccountId` TEXT,
|
||||
PRIMARY KEY(`serverId`, `tuskyAccountId`),
|
||||
FOREIGN KEY(`targetAccountId`, `tuskyAccountId`) REFERENCES `TimelineAccountEntity`(`serverId`, `tuskyAccountId`) ON UPDATE NO ACTION ON DELETE NO ACTION
|
||||
)"""
|
||||
);
|
||||
database.execSQL(
|
||||
"CREATE INDEX IF NOT EXISTS `index_NotificationReportEntity_targetAccountId_tuskyAccountId` ON `NotificationReportEntity` (`targetAccountId`, `tuskyAccountId`)"
|
||||
);
|
||||
database.execSQL("""
|
||||
CREATE TABLE IF NOT EXISTS `NotificationEntity` (
|
||||
`tuskyAccountId` INTEGER NOT NULL,
|
||||
`type` TEXT,
|
||||
`id` TEXT NOT NULL,
|
||||
`accountId` TEXT,
|
||||
`statusId` TEXT,
|
||||
`reportId` TEXT,
|
||||
`loading` INTEGER NOT NULL,
|
||||
PRIMARY KEY(`id`, `tuskyAccountId`),
|
||||
FOREIGN KEY(`accountId`, `tuskyAccountId`) REFERENCES `TimelineAccountEntity`(`serverId`, `tuskyAccountId`) ON UPDATE NO ACTION ON DELETE NO ACTION,
|
||||
FOREIGN KEY(`statusId`, `tuskyAccountId`) REFERENCES `TimelineStatusEntity`(`serverId`, `tuskyAccountId`) ON UPDATE NO ACTION ON DELETE NO ACTION,
|
||||
FOREIGN KEY(`reportId`, `tuskyAccountId`) REFERENCES `NotificationReportEntity`(`serverId`, `tuskyAccountId`) ON UPDATE NO ACTION ON DELETE NO ACTION
|
||||
)"""
|
||||
);
|
||||
database.execSQL(
|
||||
"CREATE INDEX IF NOT EXISTS `index_NotificationEntity_accountId_tuskyAccountId` ON `NotificationEntity` (`accountId`, `tuskyAccountId`)"
|
||||
);
|
||||
database.execSQL(
|
||||
"CREATE INDEX IF NOT EXISTS `index_NotificationEntity_statusId_tuskyAccountId` ON `NotificationEntity` (`statusId`, `tuskyAccountId`)"
|
||||
);
|
||||
database.execSQL(
|
||||
"CREATE INDEX IF NOT EXISTS `index_NotificationEntity_reportId_tuskyAccountId` ON `NotificationEntity` (`reportId`, `tuskyAccountId`)"
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import androidx.room.TypeConverter
|
|||
import com.keylesspalace.tusky.TabData
|
||||
import com.keylesspalace.tusky.components.conversation.ConversationAccountEntity
|
||||
import com.keylesspalace.tusky.createTabDataFromId
|
||||
import com.keylesspalace.tusky.db.entity.DraftAttachment
|
||||
import com.keylesspalace.tusky.entity.Attachment
|
||||
import com.keylesspalace.tusky.entity.Card
|
||||
import com.keylesspalace.tusky.entity.Emoji
|
||||
|
|
@ -187,4 +188,29 @@ class Converters @Inject constructor(
|
|||
fun cardToJson(card: Card?): String {
|
||||
return moshi.adapter<Card?>().toJson(card)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun jsonToCard(cardJson: String?): Card? {
|
||||
return cardJson?.let { moshi.adapter<Card?>().fromJson(cardJson) }
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun stringListToJson(list: List<String>?): String? {
|
||||
return moshi.adapter<List<String>?>().toJson(list)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun jsonToStringList(listJson: String?): List<String>? {
|
||||
return listJson?.let { moshi.adapter<List<String>?>().fromJson(it) }
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun applicationToJson(application: Status.Application?): String {
|
||||
return moshi.adapter<Status.Application?>().toJson(application)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun jsonToApplication(applicationJson: String?): Status.Application? {
|
||||
return applicationJson?.let { moshi.adapter<Status.Application?>().fromJson(it) }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
/* Copyright 2024 Tusky Contributors
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.db
|
||||
|
||||
import androidx.room.withTransaction
|
||||
import com.keylesspalace.tusky.db.entity.HomeTimelineEntity
|
||||
import com.keylesspalace.tusky.db.entity.NotificationEntity
|
||||
import com.keylesspalace.tusky.db.entity.NotificationReportEntity
|
||||
import com.keylesspalace.tusky.db.entity.TimelineAccountEntity
|
||||
import com.keylesspalace.tusky.db.entity.TimelineStatusEntity
|
||||
import javax.inject.Inject
|
||||
|
||||
class DatabaseCleaner @Inject constructor(
|
||||
private val db: AppDatabase
|
||||
) {
|
||||
/**
|
||||
* Cleans the [HomeTimelineEntity], [TimelineStatusEntity], [TimelineAccountEntity], [NotificationEntity] and [NotificationReportEntity] tables from old entries.
|
||||
* Should be regularly run to prevent the database from growing too big.
|
||||
* @param tuskyAccountId id of the account for which to clean tables
|
||||
* @param timelineLimit how many timeline items to keep
|
||||
* @param notificationLimit how many notifications to keep
|
||||
*/
|
||||
suspend fun cleanupOldData(
|
||||
tuskyAccountId: Long,
|
||||
timelineLimit: Int,
|
||||
notificationLimit: Int
|
||||
) {
|
||||
db.withTransaction {
|
||||
// the order here is important - foreign key constraints must not be violated
|
||||
db.notificationsDao().cleanupNotifications(tuskyAccountId, notificationLimit)
|
||||
db.notificationsDao().cleanupReports(tuskyAccountId)
|
||||
db.timelineDao().cleanupHomeTimeline(tuskyAccountId, timelineLimit)
|
||||
db.timelineStatusDao().cleanupStatuses(tuskyAccountId)
|
||||
db.timelineAccountDao().cleanupAccounts(tuskyAccountId)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes everything from the [HomeTimelineEntity], [TimelineStatusEntity], [TimelineAccountEntity], [NotificationEntity] and [NotificationReportEntity] tables for one user.
|
||||
* Intended to be used when a user logs out.
|
||||
* @param tuskyAccountId id of the account for which to clean tables
|
||||
*/
|
||||
suspend fun cleanupEverything(tuskyAccountId: Long) {
|
||||
db.withTransaction {
|
||||
// the order here is important - foreign key constraints must not be violated
|
||||
db.notificationsDao().removeAllNotifications(tuskyAccountId)
|
||||
db.notificationsDao().removeAllReports(tuskyAccountId)
|
||||
db.timelineDao().removeAllHomeTimelineItems(tuskyAccountId)
|
||||
db.timelineStatusDao().removeAllStatuses(tuskyAccountId)
|
||||
db.timelineAccountDao().removeAllAccounts(tuskyAccountId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ import androidx.lifecycle.LifecycleOwner
|
|||
import androidx.lifecycle.lifecycleScope
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.components.drafts.DraftsActivity
|
||||
import com.keylesspalace.tusky.db.dao.DraftDao
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
|
|||
|
|
@ -1,346 +0,0 @@
|
|||
/* Copyright 2021 Tusky Contributors
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.db
|
||||
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy.Companion.REPLACE
|
||||
import androidx.room.Query
|
||||
import androidx.room.TypeConverters
|
||||
import com.keylesspalace.tusky.entity.Attachment
|
||||
import com.keylesspalace.tusky.entity.Card
|
||||
import com.keylesspalace.tusky.entity.Emoji
|
||||
import com.keylesspalace.tusky.entity.HashTag
|
||||
import com.keylesspalace.tusky.entity.Poll
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
|
||||
@Dao
|
||||
abstract class TimelineDao {
|
||||
|
||||
@Insert(onConflict = REPLACE)
|
||||
abstract suspend fun insertAccount(timelineAccountEntity: TimelineAccountEntity): Long
|
||||
|
||||
@Insert(onConflict = REPLACE)
|
||||
abstract suspend fun insertStatus(timelineStatusEntity: TimelineStatusEntity): Long
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT s.serverId, s.url, s.timelineUserId,
|
||||
s.authorServerId, s.inReplyToId, s.inReplyToAccountId, s.createdAt, s.editedAt,
|
||||
s.emojis, s.reblogsCount, s.favouritesCount, s.repliesCount, s.reblogged, s.favourited, s.bookmarked, s.sensitive,
|
||||
s.spoilerText, s.visibility, s.mentions, s.tags, s.application, s.reblogServerId,s.reblogAccountId,
|
||||
s.content, s.attachments, s.poll, s.card, s.muted, s.expanded, s.contentShowing, s.contentCollapsed, s.pinned, s.language, s.filtered,
|
||||
a.serverId as 'a_serverId', a.timelineUserId as 'a_timelineUserId',
|
||||
a.localUsername as 'a_localUsername', a.username as 'a_username',
|
||||
a.displayName as 'a_displayName', a.url as 'a_url', a.avatar as 'a_avatar',
|
||||
a.emojis as 'a_emojis', a.bot as 'a_bot',
|
||||
rb.serverId as 'rb_serverId', rb.timelineUserId 'rb_timelineUserId',
|
||||
rb.localUsername as 'rb_localUsername', rb.username as 'rb_username',
|
||||
rb.displayName as 'rb_displayName', rb.url as 'rb_url', rb.avatar as 'rb_avatar',
|
||||
rb.emojis as 'rb_emojis', rb.bot as 'rb_bot'
|
||||
FROM TimelineStatusEntity s
|
||||
LEFT JOIN TimelineAccountEntity a ON (s.timelineUserId = a.timelineUserId AND s.authorServerId = a.serverId)
|
||||
LEFT JOIN TimelineAccountEntity rb ON (s.timelineUserId = rb.timelineUserId AND s.reblogAccountId = rb.serverId)
|
||||
WHERE s.timelineUserId = :account
|
||||
ORDER BY LENGTH(s.serverId) DESC, s.serverId DESC"""
|
||||
)
|
||||
abstract fun getStatuses(account: Long): PagingSource<Int, TimelineStatusWithAccount>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT s.serverId, s.url, s.timelineUserId,
|
||||
s.authorServerId, s.inReplyToId, s.inReplyToAccountId, s.createdAt, s.editedAt,
|
||||
s.emojis, s.reblogsCount, s.favouritesCount, s.repliesCount, s.reblogged, s.favourited, s.bookmarked, s.sensitive,
|
||||
s.spoilerText, s.visibility, s.mentions, s.tags, s.application, s.reblogServerId,s.reblogAccountId,
|
||||
s.content, s.attachments, s.poll, s.card, s.muted, s.expanded, s.contentShowing, s.contentCollapsed, s.pinned, s.language, s.filtered,
|
||||
a.serverId as 'a_serverId', a.timelineUserId as 'a_timelineUserId',
|
||||
a.localUsername as 'a_localUsername', a.username as 'a_username',
|
||||
a.displayName as 'a_displayName', a.url as 'a_url', a.avatar as 'a_avatar',
|
||||
a.emojis as 'a_emojis', a.bot as 'a_bot',
|
||||
rb.serverId as 'rb_serverId', rb.timelineUserId 'rb_timelineUserId',
|
||||
rb.localUsername as 'rb_localUsername', rb.username as 'rb_username',
|
||||
rb.displayName as 'rb_displayName', rb.url as 'rb_url', rb.avatar as 'rb_avatar',
|
||||
rb.emojis as 'rb_emojis', rb.bot as 'rb_bot'
|
||||
FROM TimelineStatusEntity s
|
||||
LEFT JOIN TimelineAccountEntity a ON (s.timelineUserId = a.timelineUserId AND s.authorServerId = a.serverId)
|
||||
LEFT JOIN TimelineAccountEntity rb ON (s.timelineUserId = rb.timelineUserId AND s.reblogAccountId = rb.serverId)
|
||||
WHERE (s.serverId = :statusId OR s.reblogServerId = :statusId)
|
||||
AND s.authorServerId IS NOT NULL
|
||||
AND s.timelineUserId = :accountId"""
|
||||
)
|
||||
abstract suspend fun getStatus(accountId: Long, statusId: String): TimelineStatusWithAccount?
|
||||
|
||||
@Query(
|
||||
"""DELETE FROM TimelineStatusEntity WHERE timelineUserId = :accountId AND
|
||||
(LENGTH(serverId) < LENGTH(:maxId) OR LENGTH(serverId) == LENGTH(:maxId) AND serverId <= :maxId)
|
||||
AND
|
||||
(LENGTH(serverId) > LENGTH(:minId) OR LENGTH(serverId) == LENGTH(:minId) AND serverId >= :minId)
|
||||
"""
|
||||
)
|
||||
abstract suspend fun deleteRange(accountId: Long, minId: String, maxId: String): Int
|
||||
|
||||
suspend fun update(accountId: Long, status: Status) {
|
||||
update(
|
||||
accountId = accountId,
|
||||
statusId = status.id,
|
||||
content = status.content,
|
||||
editedAt = status.editedAt?.time,
|
||||
emojis = status.emojis,
|
||||
reblogsCount = status.reblogsCount,
|
||||
favouritesCount = status.favouritesCount,
|
||||
repliesCount = status.repliesCount,
|
||||
reblogged = status.reblogged,
|
||||
bookmarked = status.bookmarked,
|
||||
favourited = status.favourited,
|
||||
sensitive = status.sensitive,
|
||||
spoilerText = status.spoilerText,
|
||||
visibility = status.visibility,
|
||||
attachments = status.attachments,
|
||||
mentions = status.mentions,
|
||||
tags = status.tags,
|
||||
poll = status.poll,
|
||||
muted = status.muted,
|
||||
pinned = status.pinned,
|
||||
card = status.card,
|
||||
language = status.language
|
||||
)
|
||||
}
|
||||
|
||||
@Query(
|
||||
"""UPDATE TimelineStatusEntity
|
||||
SET content = :content,
|
||||
editedAt = :editedAt,
|
||||
emojis = :emojis,
|
||||
reblogsCount = :reblogsCount,
|
||||
favouritesCount = :favouritesCount,
|
||||
repliesCount = :repliesCount,
|
||||
reblogged = :reblogged,
|
||||
bookmarked = :bookmarked,
|
||||
favourited = :favourited,
|
||||
sensitive = :sensitive,
|
||||
spoilerText = :spoilerText,
|
||||
visibility = :visibility,
|
||||
attachments = :attachments,
|
||||
mentions = :mentions,
|
||||
tags = :tags,
|
||||
poll = :poll,
|
||||
muted = :muted,
|
||||
pinned = :pinned,
|
||||
card = :card,
|
||||
language = :language
|
||||
WHERE timelineUserId = :accountId AND (serverId = :statusId OR reblogServerId = :statusId)"""
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
protected abstract suspend fun update(
|
||||
accountId: Long,
|
||||
statusId: String,
|
||||
content: String?,
|
||||
editedAt: Long?,
|
||||
emojis: List<Emoji>,
|
||||
reblogsCount: Int,
|
||||
favouritesCount: Int,
|
||||
repliesCount: Int,
|
||||
reblogged: Boolean,
|
||||
bookmarked: Boolean,
|
||||
favourited: Boolean,
|
||||
sensitive: Boolean,
|
||||
spoilerText: String,
|
||||
visibility: Status.Visibility,
|
||||
attachments: List<Attachment>,
|
||||
mentions: List<Status.Mention>,
|
||||
tags: List<HashTag>?,
|
||||
poll: Poll?,
|
||||
muted: Boolean?,
|
||||
pinned: Boolean,
|
||||
card: Card?,
|
||||
language: String?
|
||||
)
|
||||
|
||||
@Query(
|
||||
"""UPDATE TimelineStatusEntity SET bookmarked = :bookmarked
|
||||
WHERE timelineUserId = :accountId AND (serverId = :statusId OR reblogServerId = :statusId)"""
|
||||
)
|
||||
abstract suspend fun setBookmarked(accountId: Long, statusId: String, bookmarked: Boolean)
|
||||
|
||||
@Query(
|
||||
"""UPDATE TimelineStatusEntity SET reblogged = :reblogged
|
||||
WHERE timelineUserId = :accountId AND (serverId = :statusId OR reblogServerId = :statusId)"""
|
||||
)
|
||||
abstract suspend fun setReblogged(accountId: Long, statusId: String, reblogged: Boolean)
|
||||
|
||||
@Query(
|
||||
"""DELETE FROM TimelineStatusEntity WHERE timelineUserId = :accountId AND
|
||||
(authorServerId = :userId OR reblogAccountId = :userId)"""
|
||||
)
|
||||
abstract suspend fun removeAllByUser(accountId: Long, userId: String)
|
||||
|
||||
/**
|
||||
* Removes everything in the TimelineStatusEntity and TimelineAccountEntity tables for one user account
|
||||
* @param accountId id of the account for which to clean tables
|
||||
*/
|
||||
suspend fun removeAll(accountId: Long) {
|
||||
removeAllStatuses(accountId)
|
||||
removeAllAccounts(accountId)
|
||||
}
|
||||
|
||||
@Query("DELETE FROM TimelineStatusEntity WHERE timelineUserId = :accountId")
|
||||
abstract suspend fun removeAllStatuses(accountId: Long)
|
||||
|
||||
@Query("DELETE FROM TimelineAccountEntity WHERE timelineUserId = :accountId")
|
||||
abstract suspend fun removeAllAccounts(accountId: Long)
|
||||
|
||||
@Query(
|
||||
"""DELETE FROM TimelineStatusEntity WHERE timelineUserId = :accountId
|
||||
AND serverId = :statusId"""
|
||||
)
|
||||
abstract suspend fun delete(accountId: Long, statusId: String)
|
||||
|
||||
/**
|
||||
* Cleans the TimelineStatusEntity and TimelineAccountEntity tables from old entries.
|
||||
* @param accountId id of the account for which to clean tables
|
||||
* @param limit how many statuses to keep
|
||||
*/
|
||||
suspend fun cleanup(accountId: Long, limit: Int) {
|
||||
cleanupStatuses(accountId, limit)
|
||||
cleanupAccounts(accountId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans the TimelineStatusEntity table from old status entries.
|
||||
* @param accountId id of the account for which to clean statuses
|
||||
* @param limit how many statuses to keep
|
||||
*/
|
||||
@Query(
|
||||
"""DELETE FROM TimelineStatusEntity WHERE timelineUserId = :accountId AND serverId NOT IN
|
||||
(SELECT serverId FROM TimelineStatusEntity WHERE timelineUserId = :accountId ORDER BY LENGTH(serverId) DESC, serverId DESC LIMIT :limit)
|
||||
"""
|
||||
)
|
||||
abstract suspend fun cleanupStatuses(accountId: Long, limit: Int)
|
||||
|
||||
/**
|
||||
* Cleans the TimelineAccountEntity table from accounts that are no longer referenced in the TimelineStatusEntity table
|
||||
* @param accountId id of the user account for which to clean timeline accounts
|
||||
*/
|
||||
@Query(
|
||||
"""DELETE FROM TimelineAccountEntity WHERE timelineUserId = :accountId AND serverId NOT IN
|
||||
(SELECT authorServerId FROM TimelineStatusEntity WHERE timelineUserId = :accountId)
|
||||
AND serverId NOT IN
|
||||
(SELECT reblogAccountId FROM TimelineStatusEntity WHERE timelineUserId = :accountId AND reblogAccountId IS NOT NULL)"""
|
||||
)
|
||||
abstract suspend fun cleanupAccounts(accountId: Long)
|
||||
|
||||
@Query(
|
||||
"""UPDATE TimelineStatusEntity SET poll = :poll
|
||||
WHERE timelineUserId = :accountId AND (serverId = :statusId OR reblogServerId = :statusId)"""
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
abstract suspend fun setVoted(accountId: Long, statusId: String, poll: Poll)
|
||||
|
||||
@Query(
|
||||
"""UPDATE TimelineStatusEntity SET expanded = :expanded
|
||||
WHERE timelineUserId = :accountId AND (serverId = :statusId OR reblogServerId = :statusId)"""
|
||||
)
|
||||
abstract suspend fun setExpanded(accountId: Long, statusId: String, expanded: Boolean)
|
||||
|
||||
@Query(
|
||||
"""UPDATE TimelineStatusEntity SET contentShowing = :contentShowing
|
||||
WHERE timelineUserId = :accountId AND (serverId = :statusId OR reblogServerId = :statusId)"""
|
||||
)
|
||||
abstract suspend fun setContentShowing(
|
||||
accountId: Long,
|
||||
statusId: String,
|
||||
contentShowing: Boolean
|
||||
)
|
||||
|
||||
@Query(
|
||||
"""UPDATE TimelineStatusEntity SET contentCollapsed = :contentCollapsed
|
||||
WHERE timelineUserId = :accountId AND (serverId = :statusId OR reblogServerId = :statusId)"""
|
||||
)
|
||||
abstract suspend fun setContentCollapsed(
|
||||
accountId: Long,
|
||||
statusId: String,
|
||||
contentCollapsed: Boolean
|
||||
)
|
||||
|
||||
@Query(
|
||||
"""UPDATE TimelineStatusEntity SET pinned = :pinned
|
||||
WHERE timelineUserId = :accountId AND (serverId = :statusId OR reblogServerId = :statusId)"""
|
||||
)
|
||||
abstract suspend fun setPinned(accountId: Long, statusId: String, pinned: Boolean)
|
||||
|
||||
@Query(
|
||||
"""DELETE FROM TimelineStatusEntity
|
||||
WHERE timelineUserId = :accountId AND authorServerId IN (
|
||||
SELECT serverId FROM TimelineAccountEntity WHERE username LIKE '%@' || :instanceDomain
|
||||
AND timelineUserId = :accountId
|
||||
)"""
|
||||
)
|
||||
abstract suspend fun deleteAllFromInstance(accountId: Long, instanceDomain: String)
|
||||
|
||||
@Query(
|
||||
"UPDATE TimelineStatusEntity SET filtered = NULL WHERE timelineUserId = :accountId AND (serverId = :statusId OR reblogServerId = :statusId)"
|
||||
)
|
||||
abstract suspend fun clearWarning(accountId: Long, statusId: String): Int
|
||||
|
||||
@Query(
|
||||
"SELECT serverId FROM TimelineStatusEntity WHERE timelineUserId = :accountId ORDER BY LENGTH(serverId) DESC, serverId DESC LIMIT 1"
|
||||
)
|
||||
abstract suspend fun getTopId(accountId: Long): String?
|
||||
|
||||
@Query(
|
||||
"SELECT serverId FROM TimelineStatusEntity WHERE timelineUserId = :accountId AND authorServerId IS NULL ORDER BY LENGTH(serverId) DESC, serverId DESC LIMIT 1"
|
||||
)
|
||||
abstract suspend fun getTopPlaceholderId(accountId: Long): String?
|
||||
|
||||
/**
|
||||
* Returns the id directly above [serverId], or null if [serverId] is the id of the top status
|
||||
*/
|
||||
@Query(
|
||||
"SELECT serverId FROM TimelineStatusEntity WHERE timelineUserId = :accountId AND (LENGTH(:serverId) < LENGTH(serverId) OR (LENGTH(:serverId) = LENGTH(serverId) AND :serverId < serverId)) ORDER BY LENGTH(serverId) ASC, serverId ASC LIMIT 1"
|
||||
)
|
||||
abstract suspend fun getIdAbove(accountId: Long, serverId: String): String?
|
||||
|
||||
/**
|
||||
* Returns the ID directly below [serverId], or null if [serverId] is the ID of the bottom
|
||||
* status
|
||||
*/
|
||||
@Query(
|
||||
"SELECT serverId FROM TimelineStatusEntity WHERE timelineUserId = :accountId AND (LENGTH(:serverId) > LENGTH(serverId) OR (LENGTH(:serverId) = LENGTH(serverId) AND :serverId > serverId)) ORDER BY LENGTH(serverId) DESC, serverId DESC LIMIT 1"
|
||||
)
|
||||
abstract suspend fun getIdBelow(accountId: Long, serverId: String): String?
|
||||
|
||||
/**
|
||||
* Returns the id of the next placeholder after [serverId]
|
||||
*/
|
||||
@Query(
|
||||
"SELECT serverId FROM TimelineStatusEntity WHERE timelineUserId = :accountId AND authorServerId IS NULL AND (LENGTH(:serverId) > LENGTH(serverId) OR (LENGTH(:serverId) = LENGTH(serverId) AND :serverId > serverId)) ORDER BY LENGTH(serverId) DESC, serverId DESC LIMIT 1"
|
||||
)
|
||||
abstract suspend fun getNextPlaceholderIdAfter(accountId: Long, serverId: String): String?
|
||||
|
||||
@Query("SELECT COUNT(*) FROM TimelineStatusEntity WHERE timelineUserId = :accountId")
|
||||
abstract suspend fun getStatusCount(accountId: Long): Int
|
||||
|
||||
/** Developer tools: Find N most recent status IDs */
|
||||
@Query(
|
||||
"SELECT serverId FROM TimelineStatusEntity WHERE timelineUserId = :accountId ORDER BY LENGTH(serverId) DESC, serverId DESC LIMIT :count"
|
||||
)
|
||||
abstract suspend fun getMostRecentNStatusIds(accountId: Long, count: Int): List<String>
|
||||
|
||||
/** Developer tools: Convert a status to a placeholder */
|
||||
@Query("UPDATE TimelineStatusEntity SET authorServerId = NULL WHERE serverId = :serverId")
|
||||
abstract suspend fun convertStatustoPlaceholder(serverId: String)
|
||||
}
|
||||
|
|
@ -13,13 +13,14 @@
|
|||
* 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.db
|
||||
package com.keylesspalace.tusky.db.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import com.keylesspalace.tusky.db.entity.AccountEntity
|
||||
|
||||
@Dao
|
||||
interface AccountDao {
|
||||
|
|
@ -13,13 +13,14 @@
|
|||
* 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.db
|
||||
package com.keylesspalace.tusky.db.dao
|
||||
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import com.keylesspalace.tusky.db.entity.DraftEntity
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
|
|
@ -13,12 +13,15 @@
|
|||
* 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.db
|
||||
package com.keylesspalace.tusky.db.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import androidx.room.RewriteQueriesToDropUnusedColumns
|
||||
import androidx.room.Upsert
|
||||
import com.keylesspalace.tusky.db.entity.EmojisEntity
|
||||
import com.keylesspalace.tusky.db.entity.InstanceEntity
|
||||
import com.keylesspalace.tusky.db.entity.InstanceInfoEntity
|
||||
|
||||
@Dao
|
||||
interface InstanceDao {
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
/* Copyright 2023 Tusky Contributors
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.db.dao
|
||||
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy.Companion.REPLACE
|
||||
import androidx.room.Query
|
||||
import com.keylesspalace.tusky.db.entity.NotificationDataEntity
|
||||
import com.keylesspalace.tusky.db.entity.NotificationEntity
|
||||
import com.keylesspalace.tusky.db.entity.NotificationReportEntity
|
||||
|
||||
@Dao
|
||||
abstract class NotificationsDao {
|
||||
|
||||
@Insert(onConflict = REPLACE)
|
||||
abstract suspend fun insertNotification(notificationEntity: NotificationEntity): Long
|
||||
|
||||
@Insert(onConflict = REPLACE)
|
||||
abstract suspend fun insertReport(notificationReportDataEntity: NotificationReportEntity): Long
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT n.tuskyAccountId, n.type, n.id, n.loading,
|
||||
a.serverId as 'a_serverId', a.tuskyAccountId as 'a_tuskyAccountId',
|
||||
a.localUsername as 'a_localUsername', a.username as 'a_username',
|
||||
a.displayName as 'a_displayName', a.url as 'a_url', a.avatar as 'a_avatar',
|
||||
a.emojis as 'a_emojis', a.bot as 'a_bot',
|
||||
s.serverId as 's_serverId', s.url as 's_url', s.tuskyAccountId as 's_tuskyAccountId',
|
||||
s.authorServerId as 's_authorServerId', s.inReplyToId as 's_inReplyToId', s.inReplyToAccountId as 's_inReplyToAccountId',
|
||||
s.content as 's_content', s.createdAt as 's_createdAt', s.editedAt as 's_editedAt', s.emojis as 's_emojis', s.reblogsCount as 's_reblogsCount',
|
||||
s.favouritesCount as 's_favouritesCount', s.repliesCount as 's_repliesCount', s.reblogged as 's_reblogged', s.favourited as 's_favourited',
|
||||
s.bookmarked as 's_bookmarked', s.sensitive as 's_sensitive', s.spoilerText as 's_spoilerText', s.visibility as 's_visibility',
|
||||
s.mentions as 's_mentions', s.tags as 's_tags', s.application as 's_application', s.content as 's_content', s.attachments as 's_attachments', s.poll as 's_poll',
|
||||
s.card as 's_card', s.muted as 's_muted', s.expanded as 's_expanded', s.contentShowing as 's_contentShowing', s.contentCollapsed as 's_contentCollapsed',
|
||||
s.pinned as 's_pinned', s.language as 's_language', s.filtered as 's_filtered',
|
||||
sa.serverId as 'sa_serverId', sa.tuskyAccountId as 'sa_tuskyAccountId',
|
||||
sa.localUsername as 'sa_localUsername', sa.username as 'sa_username',
|
||||
sa.displayName as 'sa_displayName', sa.url as 'sa_url', sa.avatar as 'sa_avatar',
|
||||
sa.emojis as 'sa_emojis', sa.bot as 'sa_bot',
|
||||
r.serverId as 'r_serverId', r.tuskyAccountId as 'r_tuskyAccountId',
|
||||
r.category as 'r_category', r.statusIds as 'r_statusIds',
|
||||
r.createdAt as 'r_createdAt', r.targetAccountId as 'r_targetAccountId',
|
||||
ra.serverId as 'ra_serverId', ra.tuskyAccountId as 'ra_tuskyAccountId',
|
||||
ra.localUsername as 'ra_localUsername', ra.username as 'ra_username',
|
||||
ra.displayName as 'ra_displayName', ra.url as 'ra_url', ra.avatar as 'ra_avatar',
|
||||
ra.emojis as 'ra_emojis', ra.bot as 'ra_bot'
|
||||
FROM NotificationEntity n
|
||||
LEFT JOIN TimelineAccountEntity a ON (n.tuskyAccountId = a.tuskyAccountId AND n.accountId = a.serverId)
|
||||
LEFT JOIN TimelineStatusEntity s ON (n.tuskyAccountId = s.tuskyAccountId AND n.statusId = s.serverId)
|
||||
LEFT JOIN TimelineAccountEntity sa ON (n.tuskyAccountId = sa.tuskyAccountId AND s.authorServerId = sa.serverId)
|
||||
LEFT JOIN NotificationReportEntity r ON (n.tuskyAccountId = r.tuskyAccountId AND n.reportId = r.serverId)
|
||||
LEFT JOIN TimelineAccountEntity ra ON (n.tuskyAccountId = ra.tuskyAccountId AND r.targetAccountId = ra.serverId)
|
||||
WHERE n.tuskyAccountId = :tuskyAccountId
|
||||
ORDER BY LENGTH(n.id) DESC, n.id DESC"""
|
||||
)
|
||||
abstract fun getNotifications(tuskyAccountId: Long): PagingSource<Int, NotificationDataEntity>
|
||||
|
||||
@Query(
|
||||
"""DELETE FROM NotificationEntity WHERE tuskyAccountId = :tuskyAccountId AND id = :notificationId"""
|
||||
)
|
||||
abstract suspend fun delete(tuskyAccountId: Long, notificationId: String): Int
|
||||
|
||||
@Query(
|
||||
"""DELETE FROM NotificationEntity WHERE tuskyAccountId = :tuskyAccountId AND
|
||||
(LENGTH(id) < LENGTH(:maxId) OR LENGTH(id) == LENGTH(:maxId) AND id <= :maxId)
|
||||
AND
|
||||
(LENGTH(id) > LENGTH(:minId) OR LENGTH(id) == LENGTH(:minId) AND id >= :minId)
|
||||
"""
|
||||
)
|
||||
abstract suspend fun deleteRange(tuskyAccountId: Long, minId: String, maxId: String): Int
|
||||
|
||||
@Query(
|
||||
"""DELETE FROM NotificationEntity WHERE tuskyAccountId = :tuskyAccountId"""
|
||||
)
|
||||
internal abstract suspend fun removeAllNotifications(tuskyAccountId: Long)
|
||||
|
||||
/**
|
||||
* Deletes all NotificationReportEntities for Tusky user with id [tuskyAccountId].
|
||||
* Warning: This can violate foreign key constraints if reports are still referenced in the NotificationEntity table.
|
||||
*/
|
||||
@Query(
|
||||
"""DELETE FROM NotificationReportEntity WHERE tuskyAccountId = :tuskyAccountId"""
|
||||
)
|
||||
internal abstract suspend fun removeAllReports(tuskyAccountId: Long)
|
||||
|
||||
@Query(
|
||||
"""DELETE FROM NotificationEntity WHERE tuskyAccountId = :tuskyAccountId AND statusId = :statusId"""
|
||||
)
|
||||
abstract suspend fun deleteAllWithStatus(tuskyAccountId: Long, statusId: String)
|
||||
|
||||
/**
|
||||
* Remove all notifications from user with id [userId] unless they are admin notifications.
|
||||
*/
|
||||
@Query(
|
||||
"""DELETE FROM NotificationEntity WHERE tuskyAccountId = :tuskyAccountId AND
|
||||
statusId IN
|
||||
(SELECT serverId FROM TimelineStatusEntity WHERE tuskyAccountId = :tuskyAccountId AND
|
||||
(authorServerId == :userId OR accountId == :userId))
|
||||
AND type != "admin.sign_up" AND type != "admin.report"
|
||||
"""
|
||||
)
|
||||
abstract suspend fun removeAllByUser(tuskyAccountId: Long, userId: String)
|
||||
|
||||
@Query(
|
||||
"""DELETE FROM NotificationEntity
|
||||
WHERE tuskyAccountId = :tuskyAccountId AND statusId IN (
|
||||
SELECT serverId FROM TimelineStatusEntity WHERE tuskyAccountId = :tuskyAccountId AND authorServerId in
|
||||
( SELECT serverId FROM TimelineAccountEntity WHERE username LIKE '%@' || :instanceDomain
|
||||
AND tuskyAccountId = :tuskyAccountId)
|
||||
OR accountId IN ( SELECT serverId FROM TimelineAccountEntity WHERE username LIKE '%@' || :instanceDomain
|
||||
AND tuskyAccountId = :tuskyAccountId)
|
||||
)"""
|
||||
)
|
||||
abstract suspend fun deleteAllFromInstance(tuskyAccountId: Long, instanceDomain: String)
|
||||
|
||||
@Query("SELECT id FROM NotificationEntity WHERE tuskyAccountId = :accountId ORDER BY LENGTH(id) DESC, id DESC LIMIT 1")
|
||||
abstract suspend fun getTopId(accountId: Long): String?
|
||||
|
||||
@Query("SELECT id FROM NotificationEntity WHERE tuskyAccountId = :accountId AND type IS NULL ORDER BY LENGTH(id) DESC, id DESC LIMIT 1")
|
||||
abstract suspend fun getTopPlaceholderId(accountId: Long): String?
|
||||
|
||||
/**
|
||||
* Cleans the NotificationEntity table from old entries.
|
||||
* @param tuskyAccountId id of the account for which to clean tables
|
||||
* @param limit how many timeline items to keep
|
||||
*/
|
||||
@Query(
|
||||
"""DELETE FROM NotificationEntity WHERE tuskyAccountId = :tuskyAccountId AND id NOT IN
|
||||
(SELECT id FROM NotificationEntity WHERE tuskyAccountId = :tuskyAccountId ORDER BY LENGTH(id) DESC, id DESC LIMIT :limit)
|
||||
"""
|
||||
)
|
||||
internal abstract suspend fun cleanupNotifications(tuskyAccountId: Long, limit: Int)
|
||||
|
||||
/**
|
||||
* Cleans the NotificationReportEntity table from unreferenced entries.
|
||||
* @param tuskyAccountId id of the account for which to clean the table
|
||||
*/
|
||||
@Query(
|
||||
"""DELETE FROM NotificationReportEntity WHERE tuskyAccountId = :tuskyAccountId
|
||||
AND serverId NOT IN
|
||||
(SELECT reportId FROM NotificationEntity WHERE tuskyAccountId = :tuskyAccountId and reportId IS NOT NULL)"""
|
||||
)
|
||||
internal abstract suspend fun cleanupReports(tuskyAccountId: Long)
|
||||
|
||||
/**
|
||||
* Returns the id directly above [id], or null if [id] is the id of the top item
|
||||
*/
|
||||
@Query(
|
||||
"SELECT id FROM NotificationEntity WHERE tuskyAccountId = :tuskyAccountId AND (LENGTH(:id) < LENGTH(id) OR (LENGTH(:id) = LENGTH(id) AND :id < id)) ORDER BY LENGTH(id) ASC, id ASC LIMIT 1"
|
||||
)
|
||||
abstract suspend fun getIdAbove(tuskyAccountId: Long, id: String): String?
|
||||
|
||||
/**
|
||||
* Returns the ID directly below [id], or null if [id] is the ID of the bottom item
|
||||
*/
|
||||
@Query(
|
||||
"SELECT id FROM NotificationEntity WHERE tuskyAccountId = :tuskyAccountId AND (LENGTH(:id) > LENGTH(id) OR (LENGTH(:id) = LENGTH(id) AND :id > id)) ORDER BY LENGTH(id) DESC, id DESC LIMIT 1"
|
||||
)
|
||||
abstract suspend fun getIdBelow(tuskyAccountId: Long, id: String): String?
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
/* Copyright 2024 Tusky Contributors
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.db.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy.Companion.REPLACE
|
||||
import androidx.room.Query
|
||||
import com.keylesspalace.tusky.db.entity.TimelineAccountEntity
|
||||
|
||||
@Dao
|
||||
abstract class TimelineAccountDao {
|
||||
|
||||
@Insert(onConflict = REPLACE)
|
||||
abstract suspend fun insert(timelineAccountEntity: TimelineAccountEntity): Long
|
||||
|
||||
@Query(
|
||||
"""SELECT * FROM TimelineAccountEntity a
|
||||
WHERE a.serverId = :accountId
|
||||
AND a.tuskyAccountId = :tuskyAccountId"""
|
||||
)
|
||||
internal abstract suspend fun getAccount(tuskyAccountId: Long, accountId: String): TimelineAccountEntity?
|
||||
|
||||
@Query("DELETE FROM TimelineAccountEntity WHERE tuskyAccountId = :tuskyAccountId")
|
||||
abstract suspend fun removeAllAccounts(tuskyAccountId: Long)
|
||||
|
||||
/**
|
||||
* Cleans the TimelineAccountEntity table from accounts that are no longer referenced by either TimelineStatusEntity, HomeTimelineEntity or NotificationEntity
|
||||
* @param tuskyAccountId id of the user account for which to clean timeline accounts
|
||||
*/
|
||||
@Query(
|
||||
"""DELETE FROM TimelineAccountEntity WHERE tuskyAccountId = :tuskyAccountId
|
||||
AND serverId NOT IN
|
||||
(SELECT authorServerId FROM TimelineStatusEntity WHERE tuskyAccountId = :tuskyAccountId)
|
||||
AND serverId NOT IN
|
||||
(SELECT reblogAccountId FROM HomeTimelineEntity WHERE tuskyAccountId = :tuskyAccountId AND reblogAccountId IS NOT NULL)
|
||||
AND serverId NOT IN
|
||||
(SELECT accountId FROM NotificationEntity WHERE tuskyAccountId = :tuskyAccountId AND accountId IS NOT NULL)
|
||||
AND serverId NOT IN
|
||||
(SELECT targetAccountId FROM NotificationReportEntity WHERE tuskyAccountId = :tuskyAccountId AND targetAccountId IS NOT NULL)"""
|
||||
)
|
||||
abstract suspend fun cleanupAccounts(tuskyAccountId: Long)
|
||||
}
|
||||
169
app/src/main/java/com/keylesspalace/tusky/db/dao/TimelineDao.kt
Normal file
169
app/src/main/java/com/keylesspalace/tusky/db/dao/TimelineDao.kt
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
/* Copyright 2021 Tusky Contributors
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.db.dao
|
||||
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy.Companion.REPLACE
|
||||
import androidx.room.Query
|
||||
import com.keylesspalace.tusky.db.entity.HomeTimelineData
|
||||
import com.keylesspalace.tusky.db.entity.HomeTimelineEntity
|
||||
|
||||
@Dao
|
||||
abstract class TimelineDao {
|
||||
|
||||
@Insert(onConflict = REPLACE)
|
||||
abstract suspend fun insertHomeTimelineItem(item: HomeTimelineEntity): Long
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT h.id, s.serverId, s.url, s.tuskyAccountId,
|
||||
s.authorServerId, s.inReplyToId, s.inReplyToAccountId, s.createdAt, s.editedAt,
|
||||
s.emojis, s.reblogsCount, s.favouritesCount, s.repliesCount, s.reblogged, s.favourited, s.bookmarked, s.sensitive,
|
||||
s.spoilerText, s.visibility, s.mentions, s.tags, s.application,
|
||||
s.content, s.attachments, s.poll, s.card, s.muted, s.expanded, s.contentShowing, s.contentCollapsed, s.pinned, s.language, s.filtered,
|
||||
a.serverId as 'a_serverId', a.tuskyAccountId as 'a_tuskyAccountId',
|
||||
a.localUsername as 'a_localUsername', a.username as 'a_username',
|
||||
a.displayName as 'a_displayName', a.url as 'a_url', a.avatar as 'a_avatar',
|
||||
a.emojis as 'a_emojis', a.bot as 'a_bot',
|
||||
rb.serverId as 'rb_serverId', rb.tuskyAccountId 'rb_tuskyAccountId',
|
||||
rb.localUsername as 'rb_localUsername', rb.username as 'rb_username',
|
||||
rb.displayName as 'rb_displayName', rb.url as 'rb_url', rb.avatar as 'rb_avatar',
|
||||
rb.emojis as 'rb_emojis', rb.bot as 'rb_bot',
|
||||
h.loading
|
||||
FROM HomeTimelineEntity h
|
||||
LEFT JOIN TimelineStatusEntity s ON (h.statusId = s.serverId AND s.tuskyAccountId = :tuskyAccountId)
|
||||
LEFT JOIN TimelineAccountEntity a ON (s.authorServerId = a.serverId AND a.tuskyAccountId = :tuskyAccountId)
|
||||
LEFT JOIN TimelineAccountEntity rb ON (h.reblogAccountId = rb.serverId AND rb.tuskyAccountId = :tuskyAccountId)
|
||||
WHERE h.tuskyAccountId = :tuskyAccountId
|
||||
ORDER BY LENGTH(h.id) DESC, h.id DESC"""
|
||||
)
|
||||
abstract fun getHomeTimeline(tuskyAccountId: Long): PagingSource<Int, HomeTimelineData>
|
||||
|
||||
@Query(
|
||||
"""DELETE FROM HomeTimelineEntity WHERE tuskyAccountId = :tuskyAccountId AND
|
||||
(LENGTH(id) < LENGTH(:maxId) OR LENGTH(id) == LENGTH(:maxId) AND id <= :maxId)
|
||||
AND
|
||||
(LENGTH(id) > LENGTH(:minId) OR LENGTH(id) == LENGTH(:minId) AND id >= :minId)
|
||||
"""
|
||||
)
|
||||
abstract suspend fun deleteRange(tuskyAccountId: Long, minId: String, maxId: String): Int
|
||||
|
||||
/**
|
||||
* Remove all home timeline items that are statuses or reblogs by the user with id [userId], including reblogs from other people.
|
||||
* (e.g. because user was blocked)
|
||||
*/
|
||||
@Query(
|
||||
"""DELETE FROM HomeTimelineEntity WHERE tuskyAccountId = :tuskyAccountId AND
|
||||
(statusId IN
|
||||
(SELECT serverId FROM TimelineStatusEntity WHERE tuskyAccountId = :tuskyAccountId AND authorServerId == :userId)
|
||||
OR reblogAccountId == :userId)
|
||||
"""
|
||||
)
|
||||
abstract suspend fun removeAllByUser(tuskyAccountId: Long, userId: String)
|
||||
|
||||
/**
|
||||
* Remove all home timeline items that are statuses or reblogs by the user with id [userId], but not reblogs from other users.
|
||||
* (e.g. because user was unfollowed)
|
||||
*/
|
||||
@Query(
|
||||
"""DELETE FROM HomeTimelineEntity WHERE tuskyAccountId = :tuskyAccountId AND
|
||||
((statusId IN
|
||||
(SELECT serverId FROM TimelineStatusEntity WHERE tuskyAccountId = :tuskyAccountId AND authorServerId == :userId)
|
||||
AND reblogAccountId IS NULL)
|
||||
OR reblogAccountId == :userId)
|
||||
"""
|
||||
)
|
||||
abstract suspend fun removeStatusesAndReblogsByUser(tuskyAccountId: Long, userId: String)
|
||||
|
||||
@Query("DELETE FROM HomeTimelineEntity WHERE tuskyAccountId = :tuskyAccountId")
|
||||
abstract suspend fun removeAllHomeTimelineItems(tuskyAccountId: Long)
|
||||
|
||||
@Query(
|
||||
"""DELETE FROM HomeTimelineEntity WHERE tuskyAccountId = :tuskyAccountId AND id = :id"""
|
||||
)
|
||||
abstract suspend fun deleteHomeTimelineItem(tuskyAccountId: Long, id: String)
|
||||
|
||||
/**
|
||||
* Deletes all hometimeline items that reference the status with it [statusId]. They can be regular statuses or reblogs.
|
||||
*/
|
||||
@Query(
|
||||
"""DELETE FROM HomeTimelineEntity WHERE tuskyAccountId = :tuskyAccountId AND statusId = :statusId"""
|
||||
)
|
||||
abstract suspend fun deleteAllWithStatus(tuskyAccountId: Long, statusId: String)
|
||||
|
||||
/**
|
||||
* Trims the HomeTimelineEntity table down to [limit] entries by deleting the oldest in case there are more than [limit].
|
||||
* @param tuskyAccountId id of the account for which to clean the home timeline
|
||||
* @param limit how many timeline items to keep
|
||||
*/
|
||||
@Query(
|
||||
"""DELETE FROM HomeTimelineEntity WHERE tuskyAccountId = :tuskyAccountId AND id NOT IN
|
||||
(SELECT id FROM HomeTimelineEntity WHERE tuskyAccountId = :tuskyAccountId ORDER BY LENGTH(id) DESC, id DESC LIMIT :limit)
|
||||
"""
|
||||
)
|
||||
internal abstract suspend fun cleanupHomeTimeline(tuskyAccountId: Long, limit: Int)
|
||||
|
||||
@Query(
|
||||
"""DELETE FROM HomeTimelineEntity
|
||||
WHERE tuskyAccountId = :tuskyAccountId AND statusId IN (
|
||||
SELECT serverId FROM TimelineStatusEntity WHERE tuskyAccountId = :tuskyAccountId AND authorServerId in
|
||||
( SELECT serverId FROM TimelineAccountEntity WHERE username LIKE '%@' || :instanceDomain
|
||||
AND tuskyAccountId = :tuskyAccountId
|
||||
))"""
|
||||
)
|
||||
abstract suspend fun deleteAllFromInstance(tuskyAccountId: Long, instanceDomain: String)
|
||||
|
||||
@Query(
|
||||
"SELECT id FROM HomeTimelineEntity WHERE tuskyAccountId = :tuskyAccountId ORDER BY LENGTH(id) DESC, id DESC LIMIT 1"
|
||||
)
|
||||
abstract suspend fun getTopId(tuskyAccountId: Long): String?
|
||||
|
||||
@Query(
|
||||
"SELECT id FROM HomeTimelineEntity WHERE tuskyAccountId = :tuskyAccountId AND statusId IS NULL ORDER BY LENGTH(id) DESC, id DESC LIMIT 1"
|
||||
)
|
||||
abstract suspend fun getTopPlaceholderId(tuskyAccountId: Long): String?
|
||||
|
||||
/**
|
||||
* Returns the id directly above [id], or null if [id] is the id of the top item
|
||||
*/
|
||||
@Query(
|
||||
"SELECT id FROM HomeTimelineEntity WHERE tuskyAccountId = :tuskyAccountId AND (LENGTH(:id) < LENGTH(id) OR (LENGTH(:id) = LENGTH(id) AND :id < id)) ORDER BY LENGTH(id) ASC, id ASC LIMIT 1"
|
||||
)
|
||||
abstract suspend fun getIdAbove(tuskyAccountId: Long, id: String): String?
|
||||
|
||||
/**
|
||||
* Returns the ID directly below [id], or null if [id] is the ID of the bottom item
|
||||
*/
|
||||
@Query(
|
||||
"SELECT id FROM HomeTimelineEntity WHERE tuskyAccountId = :tuskyAccountId AND (LENGTH(:id) > LENGTH(id) OR (LENGTH(:id) = LENGTH(id) AND :id > id)) ORDER BY LENGTH(id) DESC, id DESC LIMIT 1"
|
||||
)
|
||||
abstract suspend fun getIdBelow(tuskyAccountId: Long, id: String): String?
|
||||
|
||||
@Query("SELECT COUNT(*) FROM HomeTimelineEntity WHERE tuskyAccountId = :tuskyAccountId")
|
||||
abstract suspend fun getHomeTimelineItemCount(tuskyAccountId: Long): Int
|
||||
|
||||
/** Developer tools: Find N most recent status IDs */
|
||||
@Query(
|
||||
"SELECT id FROM HomeTimelineEntity WHERE tuskyAccountId = :tuskyAccountId ORDER BY LENGTH(id) DESC, id DESC LIMIT :count"
|
||||
)
|
||||
abstract suspend fun getMostRecentNHomeTimelineIds(tuskyAccountId: Long, count: Int): List<String>
|
||||
|
||||
/** Developer tools: Convert a home timeline item to a placeholder */
|
||||
@Query("UPDATE HomeTimelineEntity SET statusId = NULL, reblogAccountId = NULL WHERE id = :serverId")
|
||||
abstract suspend fun convertHomeTimelineItemToPlaceholder(serverId: String)
|
||||
}
|
||||
|
|
@ -0,0 +1,279 @@
|
|||
/* Copyright 2024 Tusky Contributors
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.db.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy.Companion.REPLACE
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import androidx.room.TypeConverters
|
||||
import com.keylesspalace.tusky.db.AppDatabase
|
||||
import com.keylesspalace.tusky.db.Converters
|
||||
import com.keylesspalace.tusky.db.entity.TimelineAccountEntity
|
||||
import com.keylesspalace.tusky.db.entity.TimelineStatusEntity
|
||||
import com.keylesspalace.tusky.entity.Attachment
|
||||
import com.keylesspalace.tusky.entity.Card
|
||||
import com.keylesspalace.tusky.entity.Emoji
|
||||
import com.keylesspalace.tusky.entity.HashTag
|
||||
import com.keylesspalace.tusky.entity.Poll
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.adapter
|
||||
|
||||
@Dao
|
||||
abstract class TimelineStatusDao(
|
||||
private val db: AppDatabase
|
||||
) {
|
||||
|
||||
@Insert(onConflict = REPLACE)
|
||||
abstract suspend fun insert(timelineStatusEntity: TimelineStatusEntity): Long
|
||||
|
||||
@Transaction
|
||||
open suspend fun getStatusWithAccount(tuskyAccountId: Long, statusId: String): Pair<TimelineStatusEntity, TimelineAccountEntity>? {
|
||||
val status = getStatus(tuskyAccountId, statusId) ?: return null
|
||||
val account = db.timelineAccountDao().getAccount(tuskyAccountId, status.authorServerId) ?: return null
|
||||
return status to account
|
||||
}
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM TimelineStatusEntity s
|
||||
WHERE s.serverId = :statusId
|
||||
AND s.authorServerId IS NOT NULL
|
||||
AND s.tuskyAccountId = :tuskyAccountId"""
|
||||
)
|
||||
abstract suspend fun getStatus(tuskyAccountId: Long, statusId: String): TimelineStatusEntity?
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
suspend fun update(tuskyAccountId: Long, status: Status, moshi: Moshi) {
|
||||
update(
|
||||
tuskyAccountId = tuskyAccountId,
|
||||
statusId = status.id,
|
||||
content = status.content,
|
||||
editedAt = status.editedAt?.time,
|
||||
emojis = moshi.adapter<List<Emoji>?>().toJson(status.emojis),
|
||||
reblogsCount = status.reblogsCount,
|
||||
favouritesCount = status.favouritesCount,
|
||||
repliesCount = status.repliesCount,
|
||||
reblogged = status.reblogged,
|
||||
bookmarked = status.bookmarked,
|
||||
favourited = status.favourited,
|
||||
sensitive = status.sensitive,
|
||||
spoilerText = status.spoilerText,
|
||||
visibility = status.visibility,
|
||||
attachments = moshi.adapter<List<Attachment>?>().toJson(status.attachments),
|
||||
mentions = moshi.adapter<List<Status.Mention>?>().toJson(status.mentions),
|
||||
tags = moshi.adapter<List<HashTag>?>().toJson(status.tags),
|
||||
poll = moshi.adapter<Poll?>().toJson(status.poll),
|
||||
muted = status.muted,
|
||||
pinned = status.pinned,
|
||||
card = moshi.adapter<Card?>().toJson(status.card),
|
||||
language = status.language
|
||||
)
|
||||
}
|
||||
|
||||
@Query(
|
||||
"""UPDATE TimelineStatusEntity
|
||||
SET content = :content,
|
||||
editedAt = :editedAt,
|
||||
emojis = :emojis,
|
||||
reblogsCount = :reblogsCount,
|
||||
favouritesCount = :favouritesCount,
|
||||
repliesCount = :repliesCount,
|
||||
reblogged = :reblogged,
|
||||
bookmarked = :bookmarked,
|
||||
favourited = :favourited,
|
||||
sensitive = :sensitive,
|
||||
spoilerText = :spoilerText,
|
||||
visibility = :visibility,
|
||||
attachments = :attachments,
|
||||
mentions = :mentions,
|
||||
tags = :tags,
|
||||
poll = :poll,
|
||||
muted = :muted,
|
||||
pinned = :pinned,
|
||||
card = :card,
|
||||
language = :language
|
||||
WHERE tuskyAccountId = :tuskyAccountId AND serverId = :statusId"""
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
abstract suspend fun update(
|
||||
tuskyAccountId: Long,
|
||||
statusId: String,
|
||||
content: String?,
|
||||
editedAt: Long?,
|
||||
emojis: String?,
|
||||
reblogsCount: Int,
|
||||
favouritesCount: Int,
|
||||
repliesCount: Int,
|
||||
reblogged: Boolean,
|
||||
bookmarked: Boolean,
|
||||
favourited: Boolean,
|
||||
sensitive: Boolean,
|
||||
spoilerText: String,
|
||||
visibility: Status.Visibility,
|
||||
attachments: String?,
|
||||
mentions: String?,
|
||||
tags: String?,
|
||||
poll: String?,
|
||||
muted: Boolean?,
|
||||
pinned: Boolean,
|
||||
card: String?,
|
||||
language: String?
|
||||
)
|
||||
|
||||
@Query(
|
||||
"""UPDATE TimelineStatusEntity SET bookmarked = :bookmarked
|
||||
WHERE tuskyAccountId = :tuskyAccountId AND serverId = :statusId"""
|
||||
)
|
||||
abstract suspend fun setBookmarked(tuskyAccountId: Long, statusId: String, bookmarked: Boolean)
|
||||
|
||||
@Query(
|
||||
"""UPDATE TimelineStatusEntity SET reblogged = :reblogged
|
||||
WHERE tuskyAccountId = :tuskyAccountId AND serverId = :statusId"""
|
||||
)
|
||||
abstract suspend fun setReblogged(tuskyAccountId: Long, statusId: String, reblogged: Boolean)
|
||||
|
||||
@Query("DELETE FROM TimelineStatusEntity WHERE tuskyAccountId = :tuskyAccountId")
|
||||
abstract suspend fun removeAllStatuses(tuskyAccountId: Long)
|
||||
|
||||
@Query(
|
||||
"""DELETE FROM HomeTimelineEntity WHERE tuskyAccountId = :tuskyAccountId AND id = :id"""
|
||||
)
|
||||
abstract suspend fun deleteHomeTimelineItem(tuskyAccountId: Long, id: String)
|
||||
|
||||
/**
|
||||
* Deletes all hometimeline items that reference the status with it [statusId]. They can be regular statuses or reblogs.
|
||||
*/
|
||||
@Query(
|
||||
"""DELETE FROM HomeTimelineEntity WHERE tuskyAccountId = :tuskyAccountId AND statusId = :statusId"""
|
||||
)
|
||||
abstract suspend fun deleteAllWithStatus(tuskyAccountId: Long, statusId: String)
|
||||
|
||||
/**
|
||||
* Cleans the TimelineStatusEntity table from unreferenced status entries.
|
||||
* @param tuskyAccountId id of the account for which to clean statuses
|
||||
*/
|
||||
@Query(
|
||||
"""DELETE FROM TimelineStatusEntity WHERE tuskyAccountId = :tuskyAccountId
|
||||
AND serverId NOT IN
|
||||
(SELECT statusId FROM HomeTimelineEntity WHERE tuskyAccountId = :tuskyAccountId AND statusId IS NOT NULL)
|
||||
AND serverId NOT IN
|
||||
(SELECT statusId FROM NotificationEntity WHERE tuskyAccountId = :tuskyAccountId AND statusId IS NOT NULL)"""
|
||||
)
|
||||
internal abstract suspend fun cleanupStatuses(tuskyAccountId: Long)
|
||||
|
||||
@Query(
|
||||
"""UPDATE TimelineStatusEntity SET poll = :poll
|
||||
WHERE tuskyAccountId = :tuskyAccountId AND serverId = :statusId"""
|
||||
)
|
||||
abstract suspend fun setVoted(tuskyAccountId: Long, statusId: String, poll: String)
|
||||
|
||||
@Query(
|
||||
"""UPDATE TimelineStatusEntity SET expanded = :expanded
|
||||
WHERE tuskyAccountId = :tuskyAccountId AND serverId = :statusId"""
|
||||
)
|
||||
abstract suspend fun setExpanded(tuskyAccountId: Long, statusId: String, expanded: Boolean)
|
||||
|
||||
@Query(
|
||||
"""UPDATE TimelineStatusEntity SET contentShowing = :contentShowing
|
||||
WHERE tuskyAccountId = :tuskyAccountId AND serverId = :statusId"""
|
||||
)
|
||||
abstract suspend fun setContentShowing(
|
||||
tuskyAccountId: Long,
|
||||
statusId: String,
|
||||
contentShowing: Boolean
|
||||
)
|
||||
|
||||
@Query(
|
||||
"""UPDATE TimelineStatusEntity SET contentCollapsed = :contentCollapsed
|
||||
WHERE tuskyAccountId = :tuskyAccountId AND serverId = :statusId"""
|
||||
)
|
||||
abstract suspend fun setContentCollapsed(
|
||||
tuskyAccountId: Long,
|
||||
statusId: String,
|
||||
contentCollapsed: Boolean
|
||||
)
|
||||
|
||||
@Query(
|
||||
"""UPDATE TimelineStatusEntity SET pinned = :pinned
|
||||
WHERE tuskyAccountId = :tuskyAccountId AND serverId = :statusId"""
|
||||
)
|
||||
abstract suspend fun setPinned(tuskyAccountId: Long, statusId: String, pinned: Boolean)
|
||||
|
||||
@Query(
|
||||
"""DELETE FROM HomeTimelineEntity
|
||||
WHERE tuskyAccountId = :tuskyAccountId AND statusId IN (
|
||||
SELECT serverId FROM TimelineStatusEntity WHERE tuskyAccountId = :tuskyAccountId AND authorServerId in
|
||||
( SELECT serverId FROM TimelineAccountEntity WHERE username LIKE '%@' || :instanceDomain
|
||||
AND tuskyAccountId = :tuskyAccountId
|
||||
))"""
|
||||
)
|
||||
abstract suspend fun deleteAllFromInstance(tuskyAccountId: Long, instanceDomain: String)
|
||||
|
||||
@Query(
|
||||
"UPDATE TimelineStatusEntity SET filtered = '[]' WHERE tuskyAccountId = :tuskyAccountId AND serverId = :statusId"
|
||||
)
|
||||
abstract suspend fun clearWarning(tuskyAccountId: Long, statusId: String): Int
|
||||
|
||||
@Query(
|
||||
"SELECT id FROM HomeTimelineEntity WHERE tuskyAccountId = :tuskyAccountId ORDER BY LENGTH(id) DESC, id DESC LIMIT 1"
|
||||
)
|
||||
abstract suspend fun getTopId(tuskyAccountId: Long): String?
|
||||
|
||||
@Query(
|
||||
"SELECT id FROM HomeTimelineEntity WHERE tuskyAccountId = :tuskyAccountId AND statusId IS NULL ORDER BY LENGTH(id) DESC, id DESC LIMIT 1"
|
||||
)
|
||||
abstract suspend fun getTopPlaceholderId(tuskyAccountId: Long): String?
|
||||
|
||||
/**
|
||||
* Returns the id directly above [id], or null if [id] is the id of the top item
|
||||
*/
|
||||
@Query(
|
||||
"SELECT id FROM HomeTimelineEntity WHERE tuskyAccountId = :tuskyAccountId AND (LENGTH(:id) < LENGTH(id) OR (LENGTH(:id) = LENGTH(id) AND :id < id)) ORDER BY LENGTH(id) ASC, id ASC LIMIT 1"
|
||||
)
|
||||
abstract suspend fun getIdAbove(tuskyAccountId: Long, id: String): String?
|
||||
|
||||
/**
|
||||
* Returns the ID directly below [id], or null if [id] is the ID of the bottom item
|
||||
*/
|
||||
@Query(
|
||||
"SELECT id FROM HomeTimelineEntity WHERE tuskyAccountId = :tuskyAccountId AND (LENGTH(:id) > LENGTH(id) OR (LENGTH(:id) = LENGTH(id) AND :id > id)) ORDER BY LENGTH(id) DESC, id DESC LIMIT 1"
|
||||
)
|
||||
abstract suspend fun getIdBelow(tuskyAccountId: Long, id: String): String?
|
||||
|
||||
/**
|
||||
* Returns the id of the next placeholder after [id], or null if there is no placeholder.
|
||||
*/
|
||||
@Query(
|
||||
"SELECT id FROM HomeTimelineEntity WHERE tuskyAccountId = :tuskyAccountId AND statusId IS NULL AND (LENGTH(:id) > LENGTH(id) OR (LENGTH(:id) = LENGTH(id) AND :id > id)) ORDER BY LENGTH(id) DESC, id DESC LIMIT 1"
|
||||
)
|
||||
abstract suspend fun getNextPlaceholderIdAfter(tuskyAccountId: Long, id: String): String?
|
||||
|
||||
@Query("SELECT COUNT(*) FROM HomeTimelineEntity WHERE tuskyAccountId = :tuskyAccountId")
|
||||
abstract suspend fun getHomeTimelineItemCount(tuskyAccountId: Long): Int
|
||||
|
||||
/** Developer tools: Find N most recent status IDs */
|
||||
@Query(
|
||||
"SELECT id FROM HomeTimelineEntity WHERE tuskyAccountId = :tuskyAccountId ORDER BY LENGTH(id) DESC, id DESC LIMIT :count"
|
||||
)
|
||||
abstract suspend fun getMostRecentNStatusIds(tuskyAccountId: Long, count: Int): List<String>
|
||||
|
||||
/** Developer tools: Convert a home timeline item to a placeholder */
|
||||
@Query("UPDATE HomeTimelineEntity SET statusId = NULL, reblogAccountId = NULL WHERE id = :serverId")
|
||||
abstract suspend fun convertStatusToPlaceholder(serverId: String)
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
* 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.db
|
||||
package com.keylesspalace.tusky.db.entity
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
|
|
@ -21,6 +21,7 @@ import androidx.room.Index
|
|||
import androidx.room.PrimaryKey
|
||||
import androidx.room.TypeConverters
|
||||
import com.keylesspalace.tusky.TabData
|
||||
import com.keylesspalace.tusky.db.Converters
|
||||
import com.keylesspalace.tusky.defaultTabs
|
||||
import com.keylesspalace.tusky.entity.Emoji
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
* 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.db
|
||||
package com.keylesspalace.tusky.db.entity
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Parcelable
|
||||
|
|
@ -21,6 +21,7 @@ import androidx.core.net.toUri
|
|||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import androidx.room.TypeConverters
|
||||
import com.keylesspalace.tusky.db.Converters
|
||||
import com.keylesspalace.tusky.entity.Attachment
|
||||
import com.keylesspalace.tusky.entity.NewPoll
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
/* Copyright 2024 Tusky Contributors
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.db.entity
|
||||
|
||||
import androidx.room.Embedded
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import androidx.room.Index
|
||||
|
||||
/**
|
||||
* Entity to store an item on the home timeline. Can be a standalone status, a reblog, or a placeholder.
|
||||
*/
|
||||
@Entity(
|
||||
primaryKeys = ["id", "tuskyAccountId"],
|
||||
foreignKeys = (
|
||||
[
|
||||
ForeignKey(
|
||||
entity = TimelineStatusEntity::class,
|
||||
parentColumns = ["serverId", "tuskyAccountId"],
|
||||
childColumns = ["statusId", "tuskyAccountId"]
|
||||
),
|
||||
ForeignKey(
|
||||
entity = TimelineAccountEntity::class,
|
||||
parentColumns = ["serverId", "tuskyAccountId"],
|
||||
childColumns = ["reblogAccountId", "tuskyAccountId"]
|
||||
)
|
||||
]
|
||||
),
|
||||
indices = [
|
||||
Index("statusId", "tuskyAccountId"),
|
||||
Index("reblogAccountId", "tuskyAccountId"),
|
||||
]
|
||||
)
|
||||
data class HomeTimelineEntity(
|
||||
val tuskyAccountId: Long,
|
||||
// the id by which the timeline is sorted
|
||||
val id: String,
|
||||
// the id of the status, null when a placeholder
|
||||
val statusId: String?,
|
||||
// the id of the account who reblogged the status, null if no reblog
|
||||
val reblogAccountId: String?,
|
||||
// only relevant when this is a placeholder
|
||||
val loading: Boolean = false
|
||||
)
|
||||
|
||||
/**
|
||||
* Helper class for queries that return HomeTimelineEntity including all references
|
||||
*/
|
||||
data class HomeTimelineData(
|
||||
val id: String,
|
||||
@Embedded val status: TimelineStatusEntity?,
|
||||
@Embedded(prefix = "a_") val account: TimelineAccountEntity?,
|
||||
@Embedded(prefix = "rb_") val reblogAccount: TimelineAccountEntity?,
|
||||
val loading: Boolean
|
||||
)
|
||||
|
|
@ -13,11 +13,12 @@
|
|||
* 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.db
|
||||
package com.keylesspalace.tusky.db.entity
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import androidx.room.TypeConverters
|
||||
import com.keylesspalace.tusky.db.Converters
|
||||
import com.keylesspalace.tusky.entity.Emoji
|
||||
|
||||
@Entity
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
/* Copyright 2024 Tusky Contributors
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.db.entity
|
||||
|
||||
import androidx.room.Embedded
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import androidx.room.Index
|
||||
import androidx.room.TypeConverters
|
||||
import com.keylesspalace.tusky.db.Converters
|
||||
import com.keylesspalace.tusky.entity.Notification
|
||||
import java.util.Date
|
||||
|
||||
data class NotificationDataEntity(
|
||||
// id of the account logged into Tusky this notifications belongs to
|
||||
val tuskyAccountId: Long,
|
||||
// null when placeholder
|
||||
val type: Notification.Type?,
|
||||
val id: String,
|
||||
@Embedded(prefix = "a_") val account: TimelineAccountEntity?,
|
||||
@Embedded(prefix = "s_") val status: TimelineStatusEntity?,
|
||||
@Embedded(prefix = "sa_") val statusAccount: TimelineAccountEntity?,
|
||||
@Embedded(prefix = "r_") val report: NotificationReportEntity?,
|
||||
@Embedded(prefix = "ra_") val reportTargetAccount: TimelineAccountEntity?,
|
||||
// relevant when it is a placeholder
|
||||
val loading: Boolean = false
|
||||
)
|
||||
|
||||
@Entity(
|
||||
primaryKeys = ["id", "tuskyAccountId"],
|
||||
foreignKeys = (
|
||||
[
|
||||
ForeignKey(
|
||||
entity = TimelineAccountEntity::class,
|
||||
parentColumns = ["serverId", "tuskyAccountId"],
|
||||
childColumns = ["accountId", "tuskyAccountId"]
|
||||
),
|
||||
ForeignKey(
|
||||
entity = TimelineStatusEntity::class,
|
||||
parentColumns = ["serverId", "tuskyAccountId"],
|
||||
childColumns = ["statusId", "tuskyAccountId"]
|
||||
),
|
||||
ForeignKey(
|
||||
entity = NotificationReportEntity::class,
|
||||
parentColumns = ["serverId", "tuskyAccountId"],
|
||||
childColumns = ["reportId", "tuskyAccountId"]
|
||||
)
|
||||
]
|
||||
),
|
||||
indices = [
|
||||
Index("accountId", "tuskyAccountId"),
|
||||
Index("statusId", "tuskyAccountId"),
|
||||
Index("reportId", "tuskyAccountId"),
|
||||
]
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
data class NotificationEntity(
|
||||
// id of the account logged into Tusky this notifications belongs to
|
||||
val tuskyAccountId: Long,
|
||||
// null when placeholder
|
||||
val type: Notification.Type?,
|
||||
val id: String,
|
||||
val accountId: String?,
|
||||
val statusId: String?,
|
||||
val reportId: String?,
|
||||
// relevant when it is a placeholder
|
||||
val loading: Boolean = false
|
||||
)
|
||||
|
||||
@Entity(
|
||||
primaryKeys = ["serverId", "tuskyAccountId"],
|
||||
foreignKeys = (
|
||||
[
|
||||
ForeignKey(
|
||||
entity = TimelineAccountEntity::class,
|
||||
parentColumns = ["serverId", "tuskyAccountId"],
|
||||
childColumns = ["targetAccountId", "tuskyAccountId"]
|
||||
)
|
||||
]
|
||||
),
|
||||
indices = [
|
||||
Index("targetAccountId", "tuskyAccountId"),
|
||||
]
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
data class NotificationReportEntity(
|
||||
// id of the account logged into Tusky this report belongs to
|
||||
val tuskyAccountId: Long,
|
||||
val serverId: String,
|
||||
val category: String,
|
||||
val statusIds: List<String>?,
|
||||
val createdAt: Date,
|
||||
val targetAccountId: String?
|
||||
)
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/* Copyright 2024 Tusky Contributors
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.db.entity
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.TypeConverters
|
||||
import com.keylesspalace.tusky.db.Converters
|
||||
import com.keylesspalace.tusky.entity.Emoji
|
||||
|
||||
@Entity(
|
||||
primaryKeys = ["serverId", "tuskyAccountId"]
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
data class TimelineAccountEntity(
|
||||
val serverId: String,
|
||||
val tuskyAccountId: Long,
|
||||
val localUsername: String,
|
||||
val username: String,
|
||||
val displayName: String,
|
||||
val url: String,
|
||||
val avatar: String,
|
||||
val emojis: List<Emoji>,
|
||||
val bot: Boolean
|
||||
)
|
||||
|
|
@ -13,40 +13,38 @@
|
|||
* 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.db
|
||||
package com.keylesspalace.tusky.db.entity
|
||||
|
||||
import androidx.room.Embedded
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import androidx.room.Index
|
||||
import androidx.room.TypeConverters
|
||||
import com.keylesspalace.tusky.db.Converters
|
||||
import com.keylesspalace.tusky.entity.Attachment
|
||||
import com.keylesspalace.tusky.entity.Card
|
||||
import com.keylesspalace.tusky.entity.Emoji
|
||||
import com.keylesspalace.tusky.entity.FilterResult
|
||||
import com.keylesspalace.tusky.entity.HashTag
|
||||
import com.keylesspalace.tusky.entity.Poll
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
|
||||
/**
|
||||
* We're trying to play smart here. Server sends us reblogs as two entities one embedded into
|
||||
* another (reblogged status is a field inside of "reblog" status). But it's really inefficient from
|
||||
* the DB perspective and doesn't matter much for the display/interaction purposes.
|
||||
* What if when we store reblog we don't store almost empty "reblog status" but we store
|
||||
* *reblogged* status and we embed "reblog status" into reblogged status. This reversed
|
||||
* relationship takes much less space and is much faster to fetch (no N+1 type queries or JSON
|
||||
* serialization).
|
||||
* "Reblog status", if present, is marked by [reblogServerId], and [reblogAccountId]
|
||||
* fields.
|
||||
* Entity for caching status data. Used within home timelines and notifications.
|
||||
* The information if a status is a reblog is not stored here but in [HomeTimelineEntity].
|
||||
*/
|
||||
@Entity(
|
||||
primaryKeys = ["serverId", "timelineUserId"],
|
||||
primaryKeys = ["serverId", "tuskyAccountId"],
|
||||
foreignKeys = (
|
||||
[
|
||||
ForeignKey(
|
||||
entity = TimelineAccountEntity::class,
|
||||
parentColumns = ["serverId", "timelineUserId"],
|
||||
childColumns = ["authorServerId", "timelineUserId"]
|
||||
parentColumns = ["serverId", "tuskyAccountId"],
|
||||
childColumns = ["authorServerId", "tuskyAccountId"]
|
||||
)
|
||||
]
|
||||
),
|
||||
// Avoiding rescanning status table when accounts table changes. Recommended by Room(c).
|
||||
indices = [Index("authorServerId", "timelineUserId")]
|
||||
indices = [Index("authorServerId", "tuskyAccountId")]
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
data class TimelineStatusEntity(
|
||||
|
|
@ -54,14 +52,14 @@ data class TimelineStatusEntity(
|
|||
val serverId: String,
|
||||
val url: String?,
|
||||
// our local id for the logged in user in case there are multiple accounts per instance
|
||||
val timelineUserId: Long,
|
||||
val authorServerId: String?,
|
||||
val tuskyAccountId: Long,
|
||||
val authorServerId: String,
|
||||
val inReplyToId: String?,
|
||||
val inReplyToAccountId: String?,
|
||||
val content: String?,
|
||||
val content: String,
|
||||
val createdAt: Long,
|
||||
val editedAt: Long?,
|
||||
val emojis: String?,
|
||||
val emojis: List<Emoji>,
|
||||
val reblogsCount: Int,
|
||||
val favouritesCount: Int,
|
||||
val repliesCount: Int,
|
||||
|
|
@ -71,50 +69,19 @@ data class TimelineStatusEntity(
|
|||
val sensitive: Boolean,
|
||||
val spoilerText: String,
|
||||
val visibility: Status.Visibility,
|
||||
val attachments: String?,
|
||||
val mentions: String?,
|
||||
val tags: String?,
|
||||
val application: String?,
|
||||
val attachments: List<Attachment>,
|
||||
val mentions: List<Status.Mention>,
|
||||
val tags: List<HashTag>,
|
||||
val application: Status.Application?,
|
||||
// if it has a reblogged status, it's id is stored here
|
||||
val reblogServerId: String?,
|
||||
val reblogAccountId: String?,
|
||||
val poll: String?,
|
||||
val muted: Boolean?,
|
||||
val poll: Poll?,
|
||||
val muted: Boolean,
|
||||
/** Also used as the "loading" attribute when this TimelineStatusEntity is a placeholder */
|
||||
val expanded: Boolean,
|
||||
val contentCollapsed: Boolean,
|
||||
val contentShowing: Boolean,
|
||||
val pinned: Boolean,
|
||||
val card: String?,
|
||||
val card: Card?,
|
||||
val language: String?,
|
||||
val filtered: List<FilterResult>?
|
||||
) {
|
||||
val isPlaceholder: Boolean
|
||||
get() = this.authorServerId == null
|
||||
}
|
||||
|
||||
@Entity(
|
||||
primaryKeys = ["serverId", "timelineUserId"]
|
||||
)
|
||||
data class TimelineAccountEntity(
|
||||
val serverId: String,
|
||||
val timelineUserId: Long,
|
||||
val localUsername: String,
|
||||
val username: String,
|
||||
val displayName: String,
|
||||
val url: String,
|
||||
val avatar: String,
|
||||
val emojis: String,
|
||||
val bot: Boolean
|
||||
)
|
||||
|
||||
data class TimelineStatusWithAccount(
|
||||
@Embedded
|
||||
val status: TimelineStatusEntity,
|
||||
// null when placeholder
|
||||
@Embedded(prefix = "a_")
|
||||
val account: TimelineAccountEntity? = null,
|
||||
// null when no reblog
|
||||
@Embedded(prefix = "rb_")
|
||||
val reblogAccount: TimelineAccountEntity? = null
|
||||
val filtered: List<FilterResult>
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue