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:
Konrad Pozniak 2019-02-12 19:22:37 +01:00 committed by GitHub
commit e371fa0e24
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
75 changed files with 3663 additions and 296 deletions

View file

@ -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"

View file

@ -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`))");
}
};
}

View file

@ -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)
}

View file

@ -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)
}
}