c2c-talk/app/src/main/java/com/nextcloud/talk/threadsoverview/ThreadsOverviewActivity.kt

258 lines
8.7 KiB
Kotlin
Raw Normal View History

2025-09-18 18:11:17 +02:00
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2025 Marcel Hibbe <dev@mhibbe.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.threadsoverview
import android.content.Intent
import android.os.Bundle
import android.text.format.DateUtils
import android.util.Log
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModelProvider
import autodagger.AutoInjector
import com.nextcloud.talk.R
import com.nextcloud.talk.activities.BaseActivity
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.chat.ChatActivity
import com.nextcloud.talk.chat.ChatActivity.Companion.TAG
import com.nextcloud.talk.components.ColoredStatusBar
import com.nextcloud.talk.components.StandardAppBar
import com.nextcloud.talk.data.database.mappers.asModel
import com.nextcloud.talk.models.json.threads.ThreadInfo
import com.nextcloud.talk.threadsoverview.components.ThreadRow
import com.nextcloud.talk.threadsoverview.viewmodels.ThreadsOverviewViewModel
import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_THREAD_ID
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class ThreadsOverviewActivity : BaseActivity() {
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
@Inject
lateinit var ncApi: NcApi
@Inject
lateinit var userManager: UserManager
lateinit var threadsOverviewViewModel: ThreadsOverviewViewModel
var threadsSourceUrl: String = ""
var appbarTitle: String = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
threadsOverviewViewModel = ViewModelProvider(
this,
viewModelFactory
)[ThreadsOverviewViewModel::class.java]
val colorScheme = viewThemeUtils.getColorScheme(this)
val extras: Bundle? = intent.extras
threadsSourceUrl = extras?.getString(KEY_THREADS_SOURCE_URL).orEmpty()
appbarTitle = extras?.getString(KEY_APPBAR_TITLE).orEmpty()
setContent {
val backgroundColor = colorResource(id = R.color.bg_default)
MaterialTheme(
colorScheme = colorScheme
) {
ColoredStatusBar()
Scaffold(
modifier = Modifier
.statusBarsPadding(),
topBar = {
StandardAppBar(
title = appbarTitle,
null
)
},
content = { paddingValues ->
val uiState by threadsOverviewViewModel.threadsListState.collectAsState()
Column(
Modifier
.padding(0.dp, paddingValues.calculateTopPadding(), 0.dp, 0.dp)
.background(backgroundColor)
.fillMaxSize()
) {
ThreadsOverviewScreen(
uiState,
onThreadClick = { roomToken, threadId ->
navigateToChatActivity(roomToken, threadId)
}
)
}
}
)
}
}
}
private fun navigateToChatActivity(roomToken: String, threadId: Int) {
val bundle = Bundle()
bundle.putString(KEY_ROOM_TOKEN, roomToken)
bundle.putLong(KEY_THREAD_ID, threadId.toLong())
val chatIntent = Intent(context, ChatActivity::class.java)
chatIntent.putExtras(bundle)
startActivity(chatIntent)
}
override fun onResume() {
super.onResume()
supportActionBar?.show()
threadsOverviewViewModel.init(threadsSourceUrl)
}
companion object {
val TAG = ThreadsOverviewActivity::class.java.simpleName
val KEY_APPBAR_TITLE = "KEY_APPBAR_TITLE"
val KEY_THREADS_SOURCE_URL = "KEY_THREADS_SOURCE_URL"
}
}
@Composable
fun ThreadsOverviewScreen(
uiState: ThreadsOverviewViewModel.ThreadsListUiState,
onThreadClick: (roomToken: String, threadId: Int) -> Unit
) {
when (val state = uiState) {
is ThreadsOverviewViewModel.ThreadsListUiState.None -> {
LoadingIndicator()
}
is ThreadsOverviewViewModel.ThreadsListUiState.Success -> {
ThreadsList(
threads = state.threadsList!!,
onThreadClick = onThreadClick
)
}
is ThreadsOverviewViewModel.ThreadsListUiState.Error -> {
Log.e(TAG, "Error when retrieving threads", uiState.exception)
ErrorView(message = stringResource(R.string.nc_common_error_sorry))
}
}
}
@Composable
fun ThreadsList(threads: List<ThreadInfo>, onThreadClick: (roomToken: String, threadId: Int) -> Unit) {
val space = ' '
if (threads.isEmpty()) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
contentAlignment = Alignment.Center
) {
Text(stringResource(R.string.threads_list_empty))
}
return
}
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(
items = threads,
key = { threadInfo -> threadInfo.thread!!.id }
) { threadInfo ->
val messageJson = threadInfo.last ?: threadInfo.first
val messageModel = messageJson?.asModel()
ThreadRow(
roomToken = threadInfo.thread!!.roomToken,
threadId = threadInfo.thread!!.id,
title = threadInfo.thread?.title.orEmpty(),
numReplies = pluralStringResource(
R.plurals.thread_replies,
threadInfo.thread?.numReplies ?: 0,
threadInfo.thread?.numReplies ?: 0
),
secondLineTitle = messageModel?.actorDisplayName?.substringBefore(space)?.let { "$it:" }.orEmpty(),
secondLine = messageModel?.text.orEmpty(),
date = getLastActivityDate(threadInfo), // replace with value from api when available
onClick = onThreadClick
)
}
item {
Spacer(modifier = Modifier.height(32.dp))
}
}
}
@Suppress("MagicNumber")
private fun getLastActivityDate(threadInfo: ThreadInfo): String {
val oneSecond = 1000L
val lastActivityTimestamp = threadInfo.thread?.lastActivity ?: 0
val lastActivityDate = DateUtils.getRelativeTimeSpanString(
lastActivityTimestamp.times(oneSecond),
System.currentTimeMillis(),
0,
DateUtils.FORMAT_ABBREV_RELATIVE
).toString()
return lastActivityDate
}
@Composable
fun LoadingIndicator() {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
}
@Composable
fun ErrorView(message: String) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
contentAlignment = Alignment.Center
) {
Text(text = message, color = MaterialTheme.colorScheme.error)
}
}