Skip to content

Commit

Permalink
Merge pull request #4333 from nextcloud/issue-4257-archive-conversation
Browse files Browse the repository at this point in the history
Archived Conversations 🗃️
  • Loading branch information
rapterjet2004 authored Oct 28, 2024
2 parents eac3403 + 7285c0a commit 085711d
Show file tree
Hide file tree
Showing 21 changed files with 258 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 11,
"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 @@ -86,6 +88,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 @@ -756,6 +759,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

0 comments on commit 085711d

Please sign in to comment.