Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Archived Conversations πŸ—ƒοΈ #4333

Merged
merged 7 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 11,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the databse version must be bumped and a database migration must be added to alter the affected table

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was merged too fast. there should have been a 12.json file instead to modify 11.json
should be fixed with #4417

"identityHash": "bc802cadfdef41d3eb94ffbb0729eb89",
"identityHash": "7edb537b6987d0de6586a6760c970958",
"entities": [
{
"tableName": "User",
Expand Down Expand Up @@ -138,7 +138,7 @@
},
{
"tableName": "Conversations",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`internalId` TEXT NOT NULL, `accountId` INTEGER NOT NULL, `token` TEXT NOT NULL, `displayName` TEXT NOT NULL, `actorId` TEXT NOT NULL, `actorType` TEXT NOT NULL, `avatarVersion` TEXT NOT NULL, `callFlag` INTEGER NOT NULL, `callRecording` INTEGER NOT NULL, `callStartTime` INTEGER NOT NULL, `canDeleteConversation` INTEGER NOT NULL, `canLeaveConversation` INTEGER NOT NULL, `canStartCall` INTEGER NOT NULL, `description` TEXT NOT NULL, `hasCall` INTEGER NOT NULL, `hasPassword` INTEGER NOT NULL, `isCustomAvatar` INTEGER NOT NULL, `isFavorite` INTEGER NOT NULL, `lastActivity` INTEGER NOT NULL, `lastCommonReadMessage` INTEGER NOT NULL, `lastMessage` TEXT, `lastPing` INTEGER NOT NULL, `lastReadMessage` INTEGER NOT NULL, `lobbyState` TEXT NOT NULL, `lobbyTimer` INTEGER NOT NULL, `messageExpiration` INTEGER NOT NULL, `name` TEXT NOT NULL, `notificationCalls` INTEGER NOT NULL, `notificationLevel` TEXT NOT NULL, `objectType` TEXT NOT NULL, `participantType` TEXT NOT NULL, `permissions` INTEGER NOT NULL, `readOnly` TEXT NOT NULL, `recordingConsent` INTEGER NOT NULL, `remoteServer` TEXT, `remoteToken` TEXT, `sessionId` TEXT NOT NULL, `status` TEXT, `statusClearAt` INTEGER, `statusIcon` TEXT, `statusMessage` TEXT, `type` TEXT NOT NULL, `unreadMention` INTEGER NOT NULL, `unreadMentionDirect` INTEGER NOT NULL, `unreadMessages` INTEGER NOT NULL, PRIMARY KEY(`internalId`), FOREIGN KEY(`accountId`) REFERENCES `User`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`internalId` TEXT NOT NULL, `accountId` INTEGER NOT NULL, `token` TEXT NOT NULL, `displayName` TEXT NOT NULL, `actorId` TEXT NOT NULL, `actorType` TEXT NOT NULL, `avatarVersion` TEXT NOT NULL, `callFlag` INTEGER NOT NULL, `callRecording` INTEGER NOT NULL, `callStartTime` INTEGER NOT NULL, `canDeleteConversation` INTEGER NOT NULL, `canLeaveConversation` INTEGER NOT NULL, `canStartCall` INTEGER NOT NULL, `description` TEXT NOT NULL, `hasCall` INTEGER NOT NULL, `hasPassword` INTEGER NOT NULL, `isCustomAvatar` INTEGER NOT NULL, `isFavorite` INTEGER NOT NULL, `lastActivity` INTEGER NOT NULL, `lastCommonReadMessage` INTEGER NOT NULL, `lastMessage` TEXT, `lastPing` INTEGER NOT NULL, `lastReadMessage` INTEGER NOT NULL, `lobbyState` TEXT NOT NULL, `lobbyTimer` INTEGER NOT NULL, `messageExpiration` INTEGER NOT NULL, `name` TEXT NOT NULL, `notificationCalls` INTEGER NOT NULL, `notificationLevel` TEXT NOT NULL, `objectType` TEXT NOT NULL, `participantType` TEXT NOT NULL, `permissions` INTEGER NOT NULL, `readOnly` TEXT NOT NULL, `recordingConsent` INTEGER NOT NULL, `remoteServer` TEXT, `remoteToken` TEXT, `sessionId` TEXT NOT NULL, `status` TEXT, `statusClearAt` INTEGER, `statusIcon` TEXT, `statusMessage` TEXT, `type` TEXT NOT NULL, `unreadMention` INTEGER NOT NULL, `unreadMentionDirect` INTEGER NOT NULL, `unreadMessages` INTEGER NOT NULL, `hasArchived` INTEGER NOT NULL, PRIMARY KEY(`internalId`), FOREIGN KEY(`accountId`) REFERENCES `User`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "internalId",
Expand Down Expand Up @@ -409,6 +409,12 @@
"columnName": "unreadMessages",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "hasArchived",
"columnName": "hasArchived",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
Expand Down Expand Up @@ -713,7 +719,7 @@
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'bc802cadfdef41d3eb94ffbb0729eb89')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '7edb537b6987d0de6586a6760c970958')"
]
}
}
6 changes: 6 additions & 0 deletions app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,10 @@ interface NcApiCoroutines {

@DELETE
suspend fun deleteConversationAvatar(@Header("Authorization") authorization: String, @Url url: String): RoomOverall

@POST
suspend fun archiveConversation(@Header("Authorization") authorization: String, @Url url: String): GenericOverall

@DELETE
suspend fun unarchiveConversation(@Header("Authorization") authorization: String, @Url url: String): GenericOverall
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import androidx.appcompat.app.AlertDialog
import androidx.core.content.res.ResourcesCompat
import androidx.fragment.app.FragmentTransaction
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.work.Data
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
Expand Down Expand Up @@ -85,6 +87,7 @@ import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import java.util.Calendar
Expand Down Expand Up @@ -741,6 +744,40 @@ class ConversationInfoActivity :
}
}

if (!CapabilitiesUtil.isArchiveConversationsAvailable(spreedCapabilities)) {
binding.archiveConversationBtn.visibility = GONE
}

binding.archiveConversationBtn.setOnClickListener {
this.lifecycleScope.launch {
if (conversation!!.hasArchived) {
viewModel.unarchiveConversation(conversationUser, conversationToken)
binding.archiveConversationIcon
.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.outline_archive_24, null))
binding.archiveConversationText.text = resources.getString(R.string.archive_conversation)
binding.archiveConversationTextHint.text = resources.getString(R.string.archive_hint)
} else {
viewModel.archiveConversation(conversationUser, conversationToken)
binding.archiveConversationIcon
.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_eye, null))
binding.archiveConversationText.text = resources.getString(R.string.unarchive_conversation)
binding.archiveConversationTextHint.text = resources.getString(R.string.unarchive_hint)
}
}
}

if (conversation!!.hasArchived) {
binding.archiveConversationIcon
.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_eye, null))
binding.archiveConversationText.text = resources.getString(R.string.unarchive_conversation)
binding.archiveConversationTextHint.text = resources.getString(R.string.unarchive_hint)
} else {
binding.archiveConversationIcon
.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.outline_archive_24, null))
binding.archiveConversationText.text = resources.getString(R.string.archive_conversation)
binding.archiveConversationTextHint.text = resources.getString(R.string.archive_hint)
}

if (!isDestroyed) {
binding.dangerZoneOptions.visibility = VISIBLE

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.participants.TalkBan
import com.nextcloud.talk.repositories.conversations.ConversationsRepository
import com.nextcloud.talk.utils.ApiUtils
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
Expand All @@ -26,7 +27,8 @@ import io.reactivex.schedulers.Schedulers
import javax.inject.Inject

class ConversationInfoViewModel @Inject constructor(
private val chatNetworkDataSource: ChatNetworkDataSource
private val chatNetworkDataSource: ChatNetworkDataSource,
private val conversationsRepository: ConversationsRepository
) : ViewModel() {

object LifeCycleObserver : DefaultLifecycleObserver {
Expand Down Expand Up @@ -200,6 +202,18 @@ class ConversationInfoViewModel @Inject constructor(
})
}

suspend fun archiveConversation(user: User, token: String) {
val apiVersion = ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V1))
val url = ApiUtils.getUrlForArchive(apiVersion, user.baseUrl, token)
conversationsRepository.archiveConversation(user.getCredentials(), url)
}

suspend fun unarchiveConversation(user: User, token: String) {
val apiVersion = ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V1))
val url = ApiUtils.getUrlForArchive(apiVersion, user.baseUrl, token)
conversationsRepository.unarchiveConversation(user.getCredentials(), url)
}

inner class GetRoomObserver : Observer<ConversationModel> {
override fun onSubscribe(d: Disposable) {
// unused atm
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,9 @@ class ConversationsListActivity :
private var filterState =
mutableMapOf(
FilterConversationFragment.MENTION to false,
FilterConversationFragment.UNREAD to false
FilterConversationFragment.UNREAD to false,
FilterConversationFragment.ARCHIVE to false,
FilterConversationFragment.DEFAULT to true
)
val searchBehaviorSubject = BehaviorSubject.createDefault(false)
private lateinit var accountIconBadge: BadgeDrawable
Expand Down Expand Up @@ -380,7 +382,7 @@ class ConversationsListActivity :
sortConversations(conversationItemsWithHeader)

// Filter Conversations
if (!filterState.containsValue(true)) filterableConversationItems = conversationItems
if (!hasFilterEnabled()) filterableConversationItems = conversationItems
filterConversation()
adapter!!.updateDataSet(filterableConversationItems, false)
Handler().postDelayed({ checkToShowUnreadBubble() }, UNREAD_BUBBLE_DELAY.toLong())
Expand All @@ -395,6 +397,14 @@ class ConversationsListActivity :
}
}

private fun hasFilterEnabled(): Boolean {
for ((k, v) in filterState) {
if (k != FilterConversationFragment.DEFAULT && v) return true
}

return false
}

fun filterConversation() {
val accountId = UserIdUtils.getIdForUser(userManager.currentUser.blockingGet())
filterState[FilterConversationFragment.UNREAD] = (
Expand All @@ -413,22 +423,24 @@ class ConversationsListActivity :
).blockingGet()?.value ?: ""
) == "true"

filterState[FilterConversationFragment.ARCHIVE] = (
arbitraryStorageManager.getStorageSetting(
accountId,
FilterConversationFragment.ARCHIVE,
""
).blockingGet()?.value ?: ""
) == "true"

val newItems: MutableList<AbstractFlexibleItem<*>> = ArrayList()
if (filterState[FilterConversationFragment.UNREAD] == false &&
filterState[FilterConversationFragment.MENTION] == false
) {
adapter!!.updateDataSet(conversationItems, true)
} else {
val items = conversationItems
for (i in items) {
val conversation = (i as ConversationItem).model
if (filter(conversation)) {
newItems.add(i)
}
val items = conversationItems
for (i in items) {
val conversation = (i as ConversationItem).model
if (filter(conversation)) {
newItems.add(i)
}
adapter!!.updateDataSet(newItems, true)
setFilterableItems(newItems)
}
adapter!!.updateDataSet(newItems, true)
setFilterableItems(newItems)

updateFilterConversationButtonColor()
}
Expand All @@ -449,10 +461,19 @@ class ConversationsListActivity :
)

FilterConversationFragment.UNREAD -> result = result && (conversation.unreadMessages > 0)

FilterConversationFragment.DEFAULT -> {
result = if (filterState[FilterConversationFragment.ARCHIVE] == true) {
result && conversation.hasArchived
} else {
result && !conversation.hasArchived
}
}
}
}
}

Log.d(TAG, "Conversation: ${conversation.name} Result: $result")
return result
}

Expand Down Expand Up @@ -649,7 +670,7 @@ class ConversationsListActivity :
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
initSearchDisposable()
adapter!!.setHeadersShown(true)
if (!filterState.containsValue(true)) filterableConversationItems = searchableConversationItems
if (!hasFilterEnabled()) filterableConversationItems = searchableConversationItems
adapter!!.updateDataSet(filterableConversationItems, false)
adapter!!.showAllHeaders()
binding.swipeRefreshLayoutView?.isEnabled = false
Expand All @@ -659,7 +680,7 @@ class ConversationsListActivity :

override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
adapter!!.setHeadersShown(false)
if (!filterState.containsValue(true)) filterableConversationItems = conversationItemsWithHeader
if (!hasFilterEnabled()) filterableConversationItems = conversationItemsWithHeader
adapter!!.updateDataSet(filterableConversationItems, false)
adapter!!.hideAllHeaders()
if (searchHelper != null) {
Expand Down Expand Up @@ -1826,7 +1847,7 @@ class ConversationsListActivity :
}

fun updateFilterConversationButtonColor() {
if (filterState.containsValue(true)) {
if (hasFilterEnabled()) {
binding.filterConversationsButton.let { viewThemeUtils.platform.colorImageView(it, ColorRole.PRIMARY) }
} else {
binding.filterConversationsButton.let {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,12 @@ import okhttp3.OkHttpClient
class RepositoryModule {

@Provides
fun provideConversationsRepository(ncApi: NcApi, userProvider: CurrentUserProviderNew): ConversationsRepository {
return ConversationsRepositoryImpl(ncApi, userProvider)
fun provideConversationsRepository(
ncApi: NcApi,
ncApiCoroutines: NcApiCoroutines,
userProvider: CurrentUserProviderNew
): ConversationsRepository {
return ConversationsRepositoryImpl(ncApi, ncApiCoroutines, userProvider)
}

@Provides
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ fun ConversationModel.asEntity() =
callStartTime = callStartTime,
recordingConsentRequired = recordingConsentRequired,
remoteServer = remoteServer,
remoteToken = remoteToken
remoteToken = remoteToken,
hasArchived = hasArchived
)

fun ConversationEntity.asModel() =
Expand Down Expand Up @@ -109,7 +110,8 @@ fun ConversationEntity.asModel() =
callStartTime = callStartTime,
recordingConsentRequired = recordingConsentRequired,
remoteServer = remoteServer,
remoteToken = remoteToken
remoteToken = remoteToken,
hasArchived = hasArchived
)

fun Conversation.asEntity(accountId: Long) =
Expand Down Expand Up @@ -158,5 +160,6 @@ fun Conversation.asEntity(accountId: Long) =
callStartTime = callStartTime,
recordingConsentRequired = recordingConsentRequired,
remoteServer = remoteServer,
remoteToken = remoteToken
remoteToken = remoteToken,
hasArchived = hasArchived
)
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ data class ConversationEntity(
@ColumnInfo(name = "type") var type: ConversationEnums.ConversationType,
@ColumnInfo(name = "unreadMention") var unreadMention: Boolean = false,
@ColumnInfo(name = "unreadMentionDirect") var unreadMentionDirect: Boolean,
@ColumnInfo(name = "unreadMessages") var unreadMessages: Int = 0
@ColumnInfo(name = "unreadMessages") var unreadMessages: Int = 0,
@ColumnInfo(name = "hasArchived") var hasArchived: Boolean = false
// missing/not needed: attendeeId
// missing/not needed: attendeePin
// missing/not needed: attendeePermissions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package com.nextcloud.talk.data.source.local
import android.util.Log
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import java.sql.SQLException

@Suppress("MagicNumber")
object Migrations {
Expand Down Expand Up @@ -40,6 +41,13 @@ object Migrations {
}
}

val MIGRATION_11_12 = object : Migration(11, 12) {
override fun migrate(db: SupportSQLiteDatabase) {
Log.i("Migrations", "Migrating 11 to 12")
addArchiveConversations(db)
}
}

fun migrateToRoom(db: SupportSQLiteDatabase) {
db.execSQL(
"CREATE TABLE User_new (" +
Expand Down Expand Up @@ -237,4 +245,15 @@ object Migrations {
"ON `ChatBlocks` (`internalConversationId`)"
)
}

fun addArchiveConversations(db: SupportSQLiteDatabase) {
try {
db.execSQL(
"ALTER TABLE Conversations " +
"ADD `hasArchived` INTEGER;"
)
} catch (e: SQLException) {
Log.i("Migrations", "hasArchived already exists")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ import java.util.Locale
ChatMessageEntity::class,
ChatBlockEntity::class
],
version = 11,
version = 12,
autoMigrations = [
AutoMigration(from = 9, to = 10)
AutoMigration(from = 9, to = 11)
],
exportSchema = true
)
Expand Down Expand Up @@ -113,7 +113,8 @@ abstract class TalkDatabase : RoomDatabase() {
Migrations.MIGRATION_6_8,
Migrations.MIGRATION_7_8,
Migrations.MIGRATION_8_9,
Migrations.MIGRATION_10_11
Migrations.MIGRATION_10_11,
Migrations.MIGRATION_11_12
)
.allowMainThreadQueries()
.addCallback(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class ConversationModel(
var recordingConsentRequired: Int = 0,
var remoteServer: String? = null,
var remoteToken: String? = null,
var hasArchived: Boolean = false,

// attributes that don't come from API. This should be changed?!
var password: String? = null
Expand Down Expand Up @@ -120,7 +121,8 @@ class ConversationModel(
callStartTime = conversation.callStartTime,
recordingConsentRequired = conversation.recordingConsentRequired,
remoteServer = conversation.remoteServer,
remoteToken = conversation.remoteToken
remoteToken = conversation.remoteToken,
hasArchived = conversation.hasArchived
)
}
}
Expand Down
Loading
Loading