Skip to content

Commit 5c6db67

Browse files
feat: Migrate account chooser dialog away from flex-adapter and implementing mvvm
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
1 parent e4b16ca commit 5c6db67

4 files changed

Lines changed: 182 additions & 83 deletions

File tree

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.dialog.viewmodels.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: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Nextcloud Talk - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2022 Marcel Hibbe <dev@mhibbe.de>
5+
* SPDX-FileCopyrightText: 2021 Andy Scherzinger <info@andy-scherzinger.de>
6+
* SPDX-FileCopyrightText: 2017 Mario Danic <mario@lovelyhq.com>
7+
* SPDX-License-Identifier: GPL-3.0-or-later
8+
*/
9+
package com.nextcloud.talk.ui.dialog
10+
11+
import android.view.LayoutInflater
12+
import android.view.View
13+
import android.view.ViewGroup
14+
import androidx.core.net.toUri
15+
import androidx.recyclerview.widget.DiffUtil
16+
import androidx.recyclerview.widget.ListAdapter
17+
import androidx.recyclerview.widget.RecyclerView
18+
import com.nextcloud.talk.data.user.model.User
19+
import com.nextcloud.talk.databinding.AccountItemBinding
20+
import com.nextcloud.talk.extensions.loadUserAvatar
21+
22+
class ChooseAccountShareToAdapter(private val onClick: (User) -> Unit) :
23+
ListAdapter<User, ChooseAccountShareToAdapter.AccountViewHolder>(AccountItemCallback) {
24+
25+
inner class AccountViewHolder(val binding: AccountItemBinding) : RecyclerView.ViewHolder(binding.root) {
26+
var currentUser: User? = null
27+
28+
init {
29+
binding.root.setOnClickListener {
30+
currentUser?.let { onClick(it) }
31+
}
32+
}
33+
34+
fun bindItem(user: User) {
35+
currentUser = user
36+
binding.userName.text = user.displayName
37+
val host = user.baseUrl?.toUri()?.host
38+
binding.account.text = if (!host.isNullOrEmpty()) host else user.baseUrl
39+
if (user.baseUrl != null &&
40+
(user.baseUrl!!.startsWith("http://") || user.baseUrl!!.startsWith("https://"))
41+
) {
42+
val userId = user.userId ?: user.username
43+
binding.userIcon.loadUserAvatar(user, userId!!, true, false)
44+
}
45+
binding.actionRequired.visibility = View.GONE
46+
}
47+
}
48+
49+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AccountViewHolder =
50+
AccountViewHolder(AccountItemBinding.inflate(LayoutInflater.from(parent.context), parent, false))
51+
52+
override fun onBindViewHolder(holder: AccountViewHolder, position: Int) {
53+
holder.bindItem(getItem(position))
54+
}
55+
}
56+
57+
object AccountItemCallback : DiffUtil.ItemCallback<User>() {
58+
override fun areItemsTheSame(oldItem: User, newItem: User): Boolean = oldItem.id == newItem.id
59+
override fun areContentsTheSame(oldItem: User, newItem: User): Boolean = oldItem == newItem
60+
}

app/src/main/java/com/nextcloud/talk/ui/dialog/ChooseAccountShareToDialogFragment.kt

Lines changed: 50 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Nextcloud Talk - Android Client
33
*
44
* SPDX-FileCopyrightText: 2022 Marcel Hibbe <dev@mhibbe.de>
5-
+ SPDX-FileCopyrightText: 2021 Andy Scherzinger <info@andy-scherzinger.de>
5+
* SPDX-FileCopyrightText: 2021 Andy Scherzinger <info@andy-scherzinger.de>
66
* SPDX-FileCopyrightText: 2017 Mario Danic <mario@lovelyhq.com>
77
* SPDX-License-Identifier: GPL-3.0-or-later
88
*/
@@ -16,44 +16,36 @@ import android.view.View
1616
import android.view.ViewGroup
1717
import androidx.core.net.toUri
1818
import androidx.fragment.app.DialogFragment
19-
import androidx.recyclerview.widget.LinearLayoutManager
19+
import androidx.lifecycle.ViewModelProvider
2020
import autodagger.AutoInjector
2121
import com.google.android.material.dialog.MaterialAlertDialogBuilder
2222
import com.nextcloud.android.common.ui.theme.utils.ColorRole
23-
import com.nextcloud.talk.adapters.items.AdvancedUserItem
2423
import com.nextcloud.talk.application.NextcloudTalkApplication
2524
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
26-
import com.nextcloud.talk.data.user.model.User
2725
import com.nextcloud.talk.databinding.DialogChooseAccountShareToBinding
2826
import com.nextcloud.talk.extensions.loadUserAvatar
29-
import com.nextcloud.talk.models.json.participants.Participant
27+
import com.nextcloud.talk.ui.dialog.viewmodels.ChooseAccountShareToViewModel
3028
import com.nextcloud.talk.ui.theme.ViewThemeUtils
31-
import com.nextcloud.talk.users.UserManager
32-
import com.nextcloud.talk.utils.database.user.CurrentUserProviderOld
33-
import eu.davidea.flexibleadapter.FlexibleAdapter
34-
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
3529
import java.net.CookieManager
3630
import javax.inject.Inject
3731

3832
@AutoInjector(NextcloudTalkApplication::class)
3933
class ChooseAccountShareToDialogFragment : DialogFragment() {
40-
@JvmField
41-
@Inject
42-
var userManager: UserManager? = null
4334

4435
@Inject
45-
lateinit var currentUserProvider: CurrentUserProviderOld
36+
lateinit var viewModelFactory: ViewModelProvider.Factory
4637

47-
@JvmField
4838
@Inject
49-
var cookieManager: CookieManager? = null
39+
lateinit var viewThemeUtils: ViewThemeUtils
5040

5141
@Inject
52-
lateinit var viewThemeUtils: ViewThemeUtils
42+
lateinit var cookieManager: CookieManager
43+
5344
private var binding: DialogChooseAccountShareToBinding? = null
5445
private var dialogView: View? = null
55-
private var adapter: FlexibleAdapter<AdvancedUserItem>? = null
56-
private val userItems: MutableList<AdvancedUserItem> = ArrayList()
46+
47+
private lateinit var viewModel: ChooseAccountShareToViewModel
48+
private lateinit var adapter: ChooseAccountShareToAdapter
5749

5850
@SuppressLint("InflateParams")
5951
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
@@ -65,73 +57,60 @@ class ChooseAccountShareToDialogFragment : DialogFragment() {
6557
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
6658
super.onViewCreated(view, savedInstanceState)
6759
sharedApplication!!.componentApplication.inject(this)
68-
val user = currentUserProvider.currentUser.blockingGet()
60+
61+
viewModel = ViewModelProvider(this, viewModelFactory)[ChooseAccountShareToViewModel::class.java]
62+
6963
themeViews()
70-
setupCurrentUser(user)
71-
setupListeners(user)
7264
setupAdapter()
73-
prepareViews()
74-
}
65+
initObservers()
7566

76-
private fun setupCurrentUser(user: User?) {
77-
binding!!.currentAccount.userIcon.tag = ""
78-
if (user != null) {
79-
binding!!.currentAccount.userName.text = user.displayName
80-
binding!!.currentAccount.ticker.visibility = View.GONE
81-
binding!!.currentAccount.account.text = user.baseUrl!!.toUri().host
82-
viewThemeUtils!!.platform.colorImageView(binding!!.currentAccount.accountMenu, ColorRole.PRIMARY)
83-
if (user.baseUrl != null &&
84-
(user.baseUrl!!.startsWith("http://") || user.baseUrl!!.startsWith("https://"))
85-
) {
86-
binding!!.currentAccount.userIcon.loadUserAvatar(user, user.userId!!, true, false)
87-
} else {
88-
binding!!.currentAccount.userIcon.visibility = View.INVISIBLE
89-
}
90-
}
67+
viewModel.loadUsers()
9168
}
9269

93-
@Suppress("Detekt.NestedBlockDepth")
9470
private fun setupAdapter() {
95-
if (adapter == null) {
96-
adapter = FlexibleAdapter(userItems, activity, false)
97-
var userEntity: User
98-
var participant: Participant
99-
for (userItem in userManager!!.users.blockingGet()) {
100-
userEntity = userItem
101-
if (!userEntity.current) {
102-
var userId: String?
103-
userId = if (userEntity.userId != null) {
104-
userEntity.userId
105-
} else {
106-
userEntity.username
107-
}
108-
participant = Participant()
109-
participant.actorType = Participant.ActorType.USERS
110-
participant.actorId = userId
111-
participant.displayName = userEntity.displayName
112-
userItems.add(AdvancedUserItem(participant, userEntity, null, viewThemeUtils, 0))
71+
adapter = ChooseAccountShareToAdapter { user -> viewModel.switchToUser(user) }
72+
binding!!.accountsList.adapter = adapter
73+
}
74+
75+
private fun initObservers() {
76+
viewModel.viewState.observe(this) { state ->
77+
when (state) {
78+
is ChooseAccountShareToViewModel.LoadUsersSuccessState -> {
79+
setupCurrentUser()
80+
adapter.submitList(state.users)
81+
}
82+
is ChooseAccountShareToViewModel.SwitchUserSuccessState -> {
83+
cookieManager.cookieStore.removeAll()
84+
activity?.recreate()
85+
dismiss()
11386
}
87+
else -> {}
11488
}
115-
adapter!!.addListener(onSwitchItemClickListener)
116-
adapter!!.updateDataSet(userItems, false)
11789
}
11890
}
11991

120-
private fun setupListeners(user: User) {
121-
binding!!.currentAccount.root.setOnClickListener { v: View? -> dismiss() }
92+
private fun setupCurrentUser() {
93+
val currentAccount = binding!!.currentAccount
94+
val user = viewModel.currentUser
95+
if (user != null) {
96+
currentAccount.userIcon.tag = ""
97+
currentAccount.userName.text = user.displayName
98+
currentAccount.ticker.visibility = View.GONE
99+
currentAccount.account.text = user.baseUrl!!.toUri().host
100+
viewThemeUtils.platform.colorImageView(currentAccount.accountMenu, ColorRole.PRIMARY)
101+
if (user.baseUrl != null &&
102+
(user.baseUrl!!.startsWith("http://") || user.baseUrl!!.startsWith("https://"))
103+
) {
104+
currentAccount.userIcon.loadUserAvatar(user, user.userId!!, true, false)
105+
} else {
106+
currentAccount.userIcon.visibility = View.INVISIBLE
107+
}
108+
}
109+
currentAccount.root.setOnClickListener { dismiss() }
122110
}
123111

124112
private fun themeViews() {
125-
viewThemeUtils!!.platform.themeDialog(binding!!.root)
126-
}
127-
128-
private fun prepareViews() {
129-
if (activity != null) {
130-
val layoutManager: LinearLayoutManager = SmoothScrollLinearLayoutManager(activity)
131-
binding!!.accountsList.layoutManager = layoutManager
132-
}
133-
binding!!.accountsList.setHasFixedSize(true)
134-
binding!!.accountsList.adapter = adapter
113+
viewThemeUtils.platform.themeDialog(binding!!.root)
135114
}
136115

137116
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
@@ -142,18 +121,6 @@ class ChooseAccountShareToDialogFragment : DialogFragment() {
142121
binding = null
143122
}
144123

145-
private val onSwitchItemClickListener = FlexibleAdapter.OnItemClickListener { view, position ->
146-
if (userItems.size > position) {
147-
val user = userItems[position].user
148-
if (userManager!!.setUserAsActive(user!!).blockingGet()) {
149-
cookieManager!!.cookieStore.removeAll()
150-
activity?.recreate()
151-
dismiss()
152-
}
153-
}
154-
true
155-
}
156-
157124
companion object {
158125
val TAG = ChooseAccountShareToDialogFragment::class.java.simpleName
159126
fun newInstance(): ChooseAccountShareToDialogFragment = ChooseAccountShareToDialogFragment()
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Nextcloud Talk - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2022 Marcel Hibbe <dev@mhibbe.de>
5+
* SPDX-FileCopyrightText: 2021 Andy Scherzinger <info@andy-scherzinger.de>
6+
* SPDX-FileCopyrightText: 2017 Mario Danic <mario@lovelyhq.com>
7+
* SPDX-License-Identifier: GPL-3.0-or-later
8+
*/
9+
package com.nextcloud.talk.ui.dialog.viewmodels
10+
11+
import android.util.Log
12+
import androidx.lifecycle.LiveData
13+
import androidx.lifecycle.MutableLiveData
14+
import androidx.lifecycle.ViewModel
15+
import com.nextcloud.talk.data.user.model.User
16+
import com.nextcloud.talk.users.UserManager
17+
import io.reactivex.android.schedulers.AndroidSchedulers
18+
import io.reactivex.schedulers.Schedulers
19+
import javax.inject.Inject
20+
21+
class ChooseAccountShareToViewModel @Inject constructor(private val userManager: UserManager) : ViewModel() {
22+
23+
val currentUser: User? = userManager.currentUser.blockingGet()
24+
25+
sealed interface ViewState
26+
27+
object LoadUsersStartState : ViewState
28+
open class LoadUsersSuccessState(val users: List<User>) : ViewState
29+
object SwitchUserSuccessState : ViewState
30+
object SwitchUserErrorState : ViewState
31+
32+
private val _viewState: MutableLiveData<ViewState> = MutableLiveData(LoadUsersStartState)
33+
val viewState: LiveData<ViewState>
34+
get() = _viewState
35+
36+
fun loadUsers() {
37+
_viewState.value = LoadUsersStartState
38+
userManager.users
39+
.subscribeOn(Schedulers.io())
40+
.observeOn(AndroidSchedulers.mainThread())
41+
.subscribe(
42+
{ users -> _viewState.value = LoadUsersSuccessState(users.filter { !it.current }) },
43+
{ e ->
44+
Log.e(TAG, "Error loading users", e)
45+
_viewState.value = LoadUsersSuccessState(emptyList())
46+
}
47+
)
48+
}
49+
50+
fun switchToUser(user: User) {
51+
userManager.setUserAsActive(user)
52+
.subscribeOn(Schedulers.io())
53+
.observeOn(AndroidSchedulers.mainThread())
54+
.subscribe(
55+
{ success -> _viewState.value = if (success) SwitchUserSuccessState else SwitchUserErrorState },
56+
{ e ->
57+
Log.e(TAG, "Error switching user", e)
58+
_viewState.value = SwitchUserErrorState
59+
}
60+
)
61+
}
62+
63+
companion object {
64+
private val TAG = ChooseAccountShareToViewModel::class.simpleName
65+
}
66+
}

0 commit comments

Comments
 (0)