Skip to content

Commit eaff182

Browse files
Merge pull request #5914 from nextcloud/chore/noid/flexMig1
⚒️ Migrate account chooser for sharing from flex adapter to composables
2 parents e4b16ca + 20d92b2 commit eaff182

9 files changed

Lines changed: 432 additions & 330 deletions

app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ import com.nextcloud.talk.settings.SettingsActivity
110110
import com.nextcloud.talk.threadsoverview.ThreadsOverviewActivity
111111
import com.nextcloud.talk.ui.BackgroundVoiceMessageCard
112112
import com.nextcloud.talk.ui.dialog.ChooseAccountDialogCompose
113-
import com.nextcloud.talk.ui.dialog.ChooseAccountShareToDialogFragment
113+
import com.nextcloud.talk.ui.chooseaccount.ChooseAccountShareToDialogFragment
114114
import com.nextcloud.talk.ui.dialog.ConversationsListBottomDialog
115115
import com.nextcloud.talk.ui.dialog.FilterConversationFragment
116116
import com.nextcloud.talk.ui.dialog.FilterConversationFragment.Companion.ARCHIVE

app/src/main/java/com/nextcloud/talk/dagger/modules/ViewModelModule.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import com.nextcloud.talk.diagnosis.DiagnosisViewModel
2424
import com.nextcloud.talk.invitation.viewmodels.InvitationsViewModel
2525
import com.nextcloud.talk.messagesearch.MessageSearchViewModel
2626
import com.nextcloud.talk.openconversations.viewmodels.OpenConversationsViewModel
27+
import com.nextcloud.talk.ui.chooseaccount.ChooseAccountShareToViewModel
2728
import com.nextcloud.talk.polls.viewmodels.PollCreateViewModel
2829
import com.nextcloud.talk.polls.viewmodels.PollMainViewModel
2930
import com.nextcloud.talk.polls.viewmodels.PollResultsViewModel
@@ -184,4 +185,9 @@ abstract class ViewModelModule {
184185
@IntoMap
185186
@ViewModelKey(ScheduledMessagesViewModel::class)
186187
abstract fun scheduledMessagesViewModel(viewModel: ScheduledMessagesViewModel): ViewModel
188+
189+
@Binds
190+
@IntoMap
191+
@ViewModelKey(ChooseAccountShareToViewModel::class)
192+
abstract fun chooseAccountShareToViewModel(viewModel: ChooseAccountShareToViewModel): ViewModel
187193
}
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
/*
2+
* Nextcloud Talk - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
5+
* SPDX-License-Identifier: GPL-3.0-or-later
6+
*/
7+
8+
package com.nextcloud.talk.ui.chooseaccount
9+
10+
import android.content.res.Configuration
11+
import androidx.compose.foundation.clickable
12+
import androidx.compose.foundation.isSystemInDarkTheme
13+
import androidx.compose.foundation.layout.Column
14+
import androidx.compose.foundation.layout.Row
15+
import androidx.compose.foundation.layout.fillMaxWidth
16+
import androidx.compose.foundation.layout.height
17+
import androidx.compose.foundation.layout.padding
18+
import androidx.compose.foundation.layout.size
19+
import androidx.compose.foundation.lazy.LazyColumn
20+
import androidx.compose.foundation.lazy.items
21+
import androidx.compose.material3.Icon
22+
import androidx.compose.material3.MaterialTheme
23+
import androidx.compose.material3.Surface
24+
import androidx.compose.material3.Text
25+
import androidx.compose.material3.darkColorScheme
26+
import androidx.compose.material3.lightColorScheme
27+
import androidx.compose.runtime.Composable
28+
import androidx.compose.ui.Alignment
29+
import androidx.compose.ui.Modifier
30+
import androidx.compose.ui.platform.LocalContext
31+
import androidx.compose.ui.res.colorResource
32+
import androidx.compose.ui.res.painterResource
33+
import androidx.compose.ui.res.stringResource
34+
import androidx.compose.ui.text.style.TextOverflow
35+
import androidx.compose.ui.tooling.preview.Preview
36+
import androidx.compose.ui.unit.dp
37+
import androidx.core.net.toUri
38+
import coil.compose.AsyncImage
39+
import com.nextcloud.talk.R
40+
import com.nextcloud.talk.contacts.loadImage
41+
import com.nextcloud.talk.data.user.model.User
42+
import com.nextcloud.talk.utils.ApiUtils
43+
44+
@Composable
45+
fun ChooseAccountShareToContent(
46+
currentUser: User?,
47+
otherUsers: List<User>,
48+
onCurrentUserClick: () -> Unit,
49+
onOtherUserClick: (User) -> Unit
50+
) {
51+
Surface(
52+
color = MaterialTheme.colorScheme.surfaceContainerHigh,
53+
modifier = Modifier.fillMaxWidth()
54+
) {
55+
LazyColumn(
56+
modifier = Modifier.fillMaxWidth()
57+
) {
58+
currentUser?.let {
59+
stickyHeader {
60+
CurrentAccountRow(user = currentUser, onClick = onCurrentUserClick)
61+
}
62+
}
63+
items(otherUsers) { user ->
64+
OtherAccountRow(user = user, onClick = { onOtherUserClick(user) })
65+
}
66+
}
67+
}
68+
}
69+
70+
@Composable
71+
private fun CurrentAccountRow(user: User, onClick: () -> Unit) {
72+
val context = LocalContext.current
73+
Row(
74+
modifier = Modifier
75+
.fillMaxWidth()
76+
.height(72.dp)
77+
.padding(4.dp)
78+
.clickable { onClick() },
79+
verticalAlignment = Alignment.CenterVertically
80+
) {
81+
AsyncImage(
82+
model = loadImage(
83+
ApiUtils.getUrlForAvatar(user.baseUrl, user.userId, true),
84+
context,
85+
R.drawable.account_circle_48dp
86+
),
87+
contentDescription = stringResource(R.string.avatar),
88+
modifier = Modifier
89+
.padding(start = 12.dp)
90+
.size(48.dp)
91+
)
92+
Column(
93+
modifier = Modifier
94+
.padding(start = 8.dp)
95+
.weight(1f)
96+
) {
97+
Text(
98+
text = user.displayName ?: user.username ?: "",
99+
style = MaterialTheme.typography.bodyLarge,
100+
maxLines = 1,
101+
overflow = TextOverflow.Ellipsis
102+
)
103+
Text(
104+
text = user.baseUrl?.toUri()?.host ?: user.baseUrl ?: "",
105+
style = MaterialTheme.typography.bodyMedium,
106+
color = colorResource(R.color.textColorMaxContrast),
107+
maxLines = 1,
108+
overflow = TextOverflow.Ellipsis
109+
)
110+
}
111+
Icon(
112+
painter = painterResource(R.drawable.ic_check_circle),
113+
contentDescription = stringResource(R.string.nc_account_chooser_active_user),
114+
tint = MaterialTheme.colorScheme.primary,
115+
modifier = Modifier
116+
.padding(end = 10.dp)
117+
.size(32.dp)
118+
)
119+
}
120+
}
121+
122+
@Composable
123+
private fun OtherAccountRow(user: User, onClick: () -> Unit) {
124+
val context = LocalContext.current
125+
Row(
126+
modifier = Modifier
127+
.fillMaxWidth()
128+
.height(72.dp)
129+
.padding(4.dp)
130+
.clickable { onClick() },
131+
verticalAlignment = Alignment.CenterVertically
132+
) {
133+
AsyncImage(
134+
model = loadImage(
135+
ApiUtils.getUrlForAvatar(user.baseUrl, user.userId, true),
136+
context,
137+
R.drawable.account_circle_48dp
138+
),
139+
contentDescription = stringResource(R.string.avatar),
140+
modifier = Modifier
141+
.padding(start = 12.dp)
142+
.size(48.dp)
143+
)
144+
Column(
145+
modifier = Modifier
146+
.padding(start = 8.dp)
147+
.weight(1f)
148+
) {
149+
Text(
150+
text = user.displayName ?: user.username ?: "",
151+
style = MaterialTheme.typography.bodyLarge,
152+
maxLines = 1,
153+
overflow = TextOverflow.Ellipsis
154+
)
155+
Text(
156+
text = user.baseUrl?.toUri()?.host ?: user.baseUrl ?: "",
157+
style = MaterialTheme.typography.bodyMedium,
158+
color = colorResource(R.color.textColorMaxContrast),
159+
maxLines = 1,
160+
overflow = TextOverflow.Ellipsis
161+
)
162+
}
163+
}
164+
}
165+
166+
@Preview(name = "Light Mode", showBackground = true)
167+
@Preview(
168+
name = "Dark Mode",
169+
showBackground = true,
170+
uiMode = Configuration.UI_MODE_NIGHT_YES or Configuration.UI_MODE_TYPE_NORMAL
171+
)
172+
@Preview(
173+
name = "R-t-L",
174+
showBackground = true,
175+
locale = "ar"
176+
)
177+
@Composable
178+
private fun ChooseAccountShareToContentPreview() {
179+
val sampleCurrentUser = User(
180+
userId = "alice",
181+
username = "alice",
182+
baseUrl = "https://cloud.example.com",
183+
displayName = "Alice Example"
184+
)
185+
val sampleOtherUsers = listOf(
186+
User(
187+
userId = "bob",
188+
username = "bob",
189+
baseUrl = "https://nextcloud.example.org",
190+
displayName = "Bob Smith"
191+
),
192+
User(
193+
userId = "carol",
194+
username = "carol",
195+
baseUrl = "https://nc.example.net",
196+
displayName = "Carol Jones"
197+
)
198+
)
199+
val colorScheme = if (isSystemInDarkTheme()) darkColorScheme() else lightColorScheme()
200+
MaterialTheme(colorScheme = colorScheme) {
201+
Surface {
202+
ChooseAccountShareToContent(
203+
currentUser = sampleCurrentUser,
204+
otherUsers = sampleOtherUsers,
205+
onCurrentUserClick = {},
206+
onOtherUserClick = {}
207+
)
208+
}
209+
}
210+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
* Nextcloud Talk - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2017-2026 Nextcloud GmbH and Nextcloud contributors
5+
* SPDX-License-Identifier: GPL-3.0-or-later
6+
*/
7+
8+
package com.nextcloud.talk.ui.chooseaccount
9+
10+
import android.annotation.SuppressLint
11+
import android.app.Dialog
12+
import android.os.Bundle
13+
import android.view.LayoutInflater
14+
import android.view.View
15+
import android.view.ViewGroup
16+
import androidx.compose.material3.MaterialTheme
17+
import androidx.compose.runtime.mutableStateOf
18+
import androidx.compose.ui.platform.ComposeView
19+
import androidx.compose.ui.platform.ViewCompositionStrategy
20+
import androidx.fragment.app.DialogFragment
21+
import androidx.lifecycle.Lifecycle
22+
import androidx.lifecycle.ViewModelProvider
23+
import androidx.lifecycle.lifecycleScope
24+
import androidx.lifecycle.repeatOnLifecycle
25+
import autodagger.AutoInjector
26+
import com.google.android.material.dialog.MaterialAlertDialogBuilder
27+
import com.nextcloud.talk.application.NextcloudTalkApplication
28+
import com.nextcloud.talk.data.user.model.User
29+
import com.nextcloud.talk.ui.chooseaccount.model.LoadUsersSuccessStateChooseAccountShareTo
30+
import com.nextcloud.talk.ui.chooseaccount.model.SwitchUserSuccessStateChooseAccountShareTo
31+
import com.nextcloud.talk.ui.theme.ViewThemeUtils
32+
import kotlinx.coroutines.flow.collectLatest
33+
import kotlinx.coroutines.launch
34+
import java.net.CookieManager
35+
import javax.inject.Inject
36+
37+
@AutoInjector(NextcloudTalkApplication::class)
38+
class ChooseAccountShareToDialogFragment : DialogFragment() {
39+
40+
@Inject
41+
lateinit var viewModelFactory: ViewModelProvider.Factory
42+
43+
@Inject
44+
lateinit var viewThemeUtils: ViewThemeUtils
45+
46+
@Inject
47+
lateinit var cookieManager: CookieManager
48+
49+
private var composeView: ComposeView? = null
50+
private lateinit var viewModel: ChooseAccountShareToViewModel
51+
52+
@SuppressLint("InflateParams")
53+
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
54+
composeView = ComposeView(requireContext())
55+
return MaterialAlertDialogBuilder(requireContext()).setView(composeView).create()
56+
}
57+
58+
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
59+
composeView
60+
61+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
62+
super.onViewCreated(view, savedInstanceState)
63+
NextcloudTalkApplication.Companion.sharedApplication!!.componentApplication.inject(this)
64+
65+
viewModel = ViewModelProvider(this, viewModelFactory)[ChooseAccountShareToViewModel::class.java]
66+
67+
val otherUsers = mutableStateOf<List<User>>(emptyList())
68+
69+
lifecycleScope.launch {
70+
repeatOnLifecycle(Lifecycle.State.STARTED) {
71+
viewModel.chooseAccountShareToViewState.collectLatest { state ->
72+
when (state) {
73+
is LoadUsersSuccessStateChooseAccountShareTo -> {
74+
otherUsers.value = state.users
75+
}
76+
is SwitchUserSuccessStateChooseAccountShareTo -> {
77+
cookieManager.cookieStore.removeAll()
78+
activity?.recreate()
79+
dismiss()
80+
}
81+
else -> {}
82+
}
83+
}
84+
}
85+
}
86+
87+
val colorScheme = viewThemeUtils.getColorScheme(requireActivity())
88+
89+
composeView?.apply {
90+
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
91+
setContent {
92+
MaterialTheme(colorScheme = colorScheme) {
93+
ChooseAccountShareToContent(
94+
currentUser = viewModel.currentUser,
95+
otherUsers = otherUsers.value,
96+
onCurrentUserClick = { dismiss() },
97+
onOtherUserClick = { user -> viewModel.switchToUser(user) }
98+
)
99+
}
100+
}
101+
}
102+
103+
viewModel.loadUsers()
104+
}
105+
106+
override fun onDestroyView() {
107+
super.onDestroyView()
108+
composeView = null
109+
}
110+
111+
companion object {
112+
const val TAG = "ChooseAccountShareToDialogFragment"
113+
fun newInstance(): ChooseAccountShareToDialogFragment = ChooseAccountShareToDialogFragment()
114+
}
115+
}

0 commit comments

Comments
 (0)