Tab customization & direct messages tab (#1012)
* custom tabs * custom tabs interface * implement custom tab functionality * add database migration * fix bugs, improve ThemeUtils nullability handling * implement conversationsfragment * setup ConversationViewHolder * implement favs * add button functionality * revert 10.json * revert item_status_notification.xml * implement more menu, replying, fix stuff, clean up * fix tests * fix bug with expanding statuses * min and max number of tabs * settings support, fix bugs * database migration * fix scrolling to top after refresh * fix bugs * fix warning in item_conversation
This commit is contained in:
parent
adf573646e
commit
e371fa0e24
75 changed files with 3663 additions and 296 deletions
|
|
@ -19,6 +19,8 @@ import androidx.room.Entity
|
|||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
import androidx.room.TypeConverters
|
||||
import com.keylesspalace.tusky.TabData
|
||||
import com.keylesspalace.tusky.defaultTabs
|
||||
|
||||
import com.keylesspalace.tusky.entity.Emoji
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
|
|
@ -48,7 +50,8 @@ data class AccountEntity(@field:PrimaryKey(autoGenerate = true) var id: Long,
|
|||
var mediaPreviewEnabled: Boolean = true,
|
||||
var lastNotificationId: String = "0",
|
||||
var activeNotifications: String = "[]",
|
||||
var emojis: List<Emoji> = emptyList()) {
|
||||
var emojis: List<Emoji> = emptyList(),
|
||||
var tabPreferences: List<TabData> = defaultTabs()) {
|
||||
|
||||
val identifier: String
|
||||
get() = "$domain:$accountId"
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@
|
|||
|
||||
package com.keylesspalace.tusky.db;
|
||||
|
||||
import com.keylesspalace.tusky.TabDataKt;
|
||||
import com.keylesspalace.tusky.components.conversation.ConversationEntity;
|
||||
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||
import androidx.room.Database;
|
||||
import androidx.room.RoomDatabase;
|
||||
|
|
@ -25,14 +28,15 @@ import androidx.annotation.NonNull;
|
|||
* DB version & declare DAO
|
||||
*/
|
||||
|
||||
@Database(entities = {TootEntity.class, AccountEntity.class, InstanceEntity.class,TimelineStatusEntity.class,
|
||||
TimelineAccountEntity.class
|
||||
}, version = 11)
|
||||
@Database(entities = {TootEntity.class, AccountEntity.class, InstanceEntity.class, TimelineStatusEntity.class,
|
||||
TimelineAccountEntity.class, ConversationEntity.class
|
||||
}, version = 12)
|
||||
public abstract class AppDatabase extends RoomDatabase {
|
||||
|
||||
public abstract TootDao tootDao();
|
||||
public abstract AccountDao accountDao();
|
||||
public abstract InstanceDao instanceDao();
|
||||
public abstract ConversationsDao conversationDao();
|
||||
public abstract TimelineDao timelineDao();
|
||||
|
||||
public static final Migration MIGRATION_2_3 = new Migration(2, 3) {
|
||||
|
|
@ -166,4 +170,41 @@ public abstract class AppDatabase extends RoomDatabase {
|
|||
}
|
||||
};
|
||||
|
||||
public static final Migration MIGRATION_11_12 = new Migration(11, 12) {
|
||||
@Override
|
||||
public void migrate(@NonNull SupportSQLiteDatabase database) {
|
||||
String defaultTabs = TabDataKt.HOME + ";" +
|
||||
TabDataKt.NOTIFICATIONS + ";" +
|
||||
TabDataKt.LOCAL + ";" +
|
||||
TabDataKt.FEDERATED;
|
||||
database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `tabPreferences` TEXT NOT NULL DEFAULT '" + defaultTabs + "'");
|
||||
|
||||
database.execSQL("CREATE TABLE IF NOT EXISTS `ConversationEntity` (" +
|
||||
"`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, " +
|
||||
"PRIMARY KEY(`id`, `accountId`))");
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
/* Copyright 2018 Conny Duck
|
||||
*
|
||||
* 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.DataSource
|
||||
import androidx.room.*
|
||||
import com.keylesspalace.tusky.components.conversation.ConversationEntity
|
||||
|
||||
@Dao
|
||||
interface ConversationsDao {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(conversations: List<ConversationEntity>)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(conversation: ConversationEntity)
|
||||
|
||||
@Delete
|
||||
fun delete(conversation: ConversationEntity)
|
||||
|
||||
@Query("SELECT * FROM ConversationEntity WHERE accountId = :accountId ORDER BY s_createdAt DESC")
|
||||
fun conversationsForAccount(accountId: Long) : DataSource.Factory<Int, ConversationEntity>
|
||||
|
||||
@Query("DELETE FROM ConversationEntity WHERE accountId = :accountId")
|
||||
fun deleteForAccount(accountId: Long)
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -15,15 +15,25 @@
|
|||
|
||||
package com.keylesspalace.tusky.db
|
||||
|
||||
import android.text.Spanned
|
||||
import androidx.room.TypeConverter
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.keylesspalace.tusky.TabData
|
||||
import com.keylesspalace.tusky.components.conversation.ConversationAccountEntity
|
||||
import com.keylesspalace.tusky.createTabDataFromId
|
||||
import com.keylesspalace.tusky.entity.Attachment
|
||||
import com.keylesspalace.tusky.entity.Emoji
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.json.SpannedTypeAdapter
|
||||
import com.keylesspalace.tusky.util.HtmlUtils
|
||||
import java.util.*
|
||||
|
||||
class Converters {
|
||||
|
||||
private val gson = Gson()
|
||||
private val gson = GsonBuilder()
|
||||
.registerTypeAdapter(Spanned::class.java, SpannedTypeAdapter())
|
||||
.create()
|
||||
|
||||
@TypeConverter
|
||||
fun jsonToEmojiList(emojiListJson: String?): List<Emoji>? {
|
||||
|
|
@ -36,12 +46,90 @@ class Converters {
|
|||
}
|
||||
|
||||
@TypeConverter
|
||||
fun visibilityToInt(visibility: Status.Visibility): Int {
|
||||
return visibility.num
|
||||
fun visibilityToInt(visibility: Status.Visibility?): Int {
|
||||
return visibility?.num ?: Status.Visibility.UNKNOWN.num
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun intToVisibility(visibility: Int): Status.Visibility {
|
||||
return Status.Visibility.byNum(visibility)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun stringToTabData(str: String?): List<TabData>? {
|
||||
return str?.split(";")
|
||||
?.map { createTabDataFromId(it) }
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun tabDataToString(tabData: List<TabData>?): String? {
|
||||
return tabData?.joinToString(";") { it.id }
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun accountToJson(account: ConversationAccountEntity?): String {
|
||||
return gson.toJson(account)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun jsonToAccount(accountJson: String?): ConversationAccountEntity? {
|
||||
return gson.fromJson(accountJson, ConversationAccountEntity::class.java)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun accountListToJson(accountList: List<ConversationAccountEntity>?): String {
|
||||
return gson.toJson(accountList)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun jsonToAccountList(accountListJson: String?): List<ConversationAccountEntity>? {
|
||||
return gson.fromJson(accountListJson, object : TypeToken<List<ConversationAccountEntity>>() {}.type)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun attachmentListToJson(attachmentList: List<Attachment>?): String {
|
||||
return gson.toJson(attachmentList)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun jsonToAttachmentList(attachmentListJson: String?): List<Attachment>? {
|
||||
return gson.fromJson(attachmentListJson, object : TypeToken<List<Attachment>>() {}.type)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun mentionArrayToJson(mentionArray: Array<Status.Mention>?): String? {
|
||||
return gson.toJson(mentionArray)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun jsonToMentionArray(mentionListJson: String?): Array<Status.Mention>? {
|
||||
return gson.fromJson(mentionListJson, object : TypeToken<Array<Status.Mention>>() {}.type)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun dateToLong(date: Date): Long {
|
||||
return date.time
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun longToDate(date: Long): Date {
|
||||
return Date(date)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun spannedToString(spanned: Spanned?): String? {
|
||||
if(spanned == null) {
|
||||
return null
|
||||
}
|
||||
return HtmlUtils.toHtml(spanned)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun stringToSpanned(spannedString: String?): Spanned? {
|
||||
if(spannedString == null) {
|
||||
return null
|
||||
}
|
||||
return HtmlUtils.fromHtml(spannedString)
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue