Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
700efdc
Rename .java to .kt
sowjanyakch Aug 5, 2025
e5922ef
add composable dialog fragment
sowjanyakch Aug 5, 2025
baa5c4a
add click listeners - add account
sowjanyakch Aug 5, 2025
f0d3bf4
add click listeners - open settings
sowjanyakch Aug 5, 2025
9f91144
add click listeners - open status
sowjanyakch Aug 5, 2025
c1481c1
show red badge when actionsRequired
sowjanyakch Aug 6, 2025
2fa56af
fix duplicate accounts
sowjanyakch Aug 6, 2025
2eac6b9
status should be display first
sowjanyakch Aug 6, 2025
2fa47e5
format
sowjanyakch Aug 6, 2025
6f02663
implementing MVVM and coroutines
sowjanyakch Aug 7, 2025
56270be
add repo in RepositoryModule
sowjanyakch Aug 11, 2025
2387874
update ViewModelModule
sowjanyakch Aug 11, 2025
d13fe4a
using composable with MVVM and coroutines
sowjanyakch Aug 12, 2025
9deea61
show accounts first and then apply badge if pending invitations are p…
sowjanyakch Aug 13, 2025
680e2a0
update header
sowjanyakch Aug 13, 2025
5d9051d
ktlintFormat
sowjanyakch Aug 13, 2025
cdd95e6
add header
sowjanyakch Aug 29, 2025
08bf6f8
merge conflicts
sowjanyakch Sep 17, 2025
0043ee3
split status
sowjanyakch Sep 17, 2025
cb3aab0
use tag
sowjanyakch Sep 18, 2025
f5bfcd2
create data class - AccountItem
sowjanyakch Sep 18, 2025
e9f120d
add license header info
sowjanyakch Sep 18, 2025
4b6b05b
some more improvements to UI
sowjanyakch Sep 22, 2025
e2fb0e2
some more improvements to UI
sowjanyakch Sep 22, 2025
3f8c76e
format code
sowjanyakch Sep 22, 2025
a71d246
format code
sowjanyakch Sep 22, 2025
4c98bbb
fix build
sowjanyakch Sep 22, 2025
2f3badc
fix lint
sowjanyakch Sep 22, 2025
bfc25a7
fix detekt
sowjanyakch Sep 23, 2025
979ce16
remove choose account xml file
sowjanyakch Sep 23, 2025
0a2dec0
some UI changes
sowjanyakch Sep 23, 2025
b14fe2e
small layout changes
sowjanyakch Sep 23, 2025
53cd86b
show settings option when there is no internet connection
sowjanyakch Oct 6, 2025
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
@@ -0,0 +1,12 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2025 Sowjanya Kota <[email protected]>
* SPDX-License-Identifier: GPL-3.0-or-later
*/

package com.nextcloud.talk.account.data.model

import com.nextcloud.talk.data.user.model.User

data class AccountItem(val user: User, val userId: String?, val pendingInvitation: Int)
8 changes: 8 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 @@ -14,10 +14,12 @@ import com.nextcloud.talk.models.json.chat.ChatOverall
import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage
import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.invitation.InvitationOverall
import com.nextcloud.talk.models.json.participants.AddParticipantOverall
import com.nextcloud.talk.models.json.participants.TalkBan
import com.nextcloud.talk.models.json.participants.TalkBanOverall
import com.nextcloud.talk.models.json.profile.ProfileOverall
import com.nextcloud.talk.models.json.status.StatusOverall
import com.nextcloud.talk.models.json.testNotification.TestNotificationOverall
import com.nextcloud.talk.models.json.threads.ThreadOverall
import com.nextcloud.talk.models.json.threads.ThreadsOverall
Expand Down Expand Up @@ -307,4 +309,10 @@ interface NcApiCoroutines {
@Url url: String,
@Field("level") level: Int
): ThreadOverall

@GET
suspend fun getInvitations(@Header("Authorization") authorization: String, @Url url: String): InvitationOverall

@GET
suspend fun status(@Header("Authorization") authorization: String, @Url url: String): StatusOverall
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2025 Sowjanya Kota <[email protected]>
* SPDX-License-Identifier: GPL-3.0-or-later
*/

package com.nextcloud.talk.chooseaccount

import com.nextcloud.talk.models.json.status.StatusOverall

interface StatusRepository {
suspend fun setStatus(): StatusOverall
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2025 Sowjanya Kota <[email protected]>
* SPDX-License-Identifier: GPL-3.0-or-later
*/

package com.nextcloud.talk.chooseaccount

import com.nextcloud.talk.api.NcApiCoroutines
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.json.status.StatusOverall
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import javax.inject.Inject

class StatusRepositoryImplementation @Inject constructor(
private val ncApiCoroutines: NcApiCoroutines,
private val currentUserProvider: CurrentUserProviderNew
) : StatusRepository {
private val _currentUser = currentUserProvider.currentUser.blockingGet()
val currentUser: User = _currentUser
val credentials = ApiUtils.getCredentials(_currentUser.username, _currentUser.token)

override suspend fun setStatus(): StatusOverall {
val url = ApiUtils.getUrlForStatus(currentUser.baseUrl!!)
val statusOverall = credentials?.let {
ncApiCoroutines.status(credentials, url)
}
return statusOverall!!
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2025 Sowjanya Kota <[email protected]>
* SPDX-License-Identifier: GPL-3.0-or-later
*/

package com.nextcloud.talk.chooseaccount

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.nextcloud.talk.models.json.status.StatusOverall
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject

class StatusViewModel @Inject constructor(private val repository: StatusRepository) : ViewModel() {

private val _statusViewState = MutableStateFlow<StatusUiState>(StatusUiState.None)
val statusViewState: StateFlow<StatusUiState> = _statusViewState

@Suppress("Detekt.TooGenericExceptionCaught")
fun getStatus() {
viewModelScope.launch {
try {
val status = repository.setStatus()
_statusViewState.value = StatusUiState.Success(status)
} catch (exception: Exception) {
_statusViewState.value = StatusUiState.Error(exception.message ?: "")
}
}
}
}

sealed class StatusUiState {
data object None : StatusUiState()
open class Success(val status: StatusOverall) : StatusUiState()
open class Error(val message: String) : StatusUiState()
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import androidx.activity.OnBackPressedCallback
import androidx.annotation.OptIn
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.SearchView
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
Expand Down Expand Up @@ -113,7 +114,7 @@ import com.nextcloud.talk.repositories.unifiedsearch.UnifiedSearchRepository
import com.nextcloud.talk.settings.SettingsActivity
import com.nextcloud.talk.threadsoverview.ThreadsOverviewActivity
import com.nextcloud.talk.ui.BackgroundVoiceMessageCard
import com.nextcloud.talk.ui.dialog.ChooseAccountDialogFragment
import com.nextcloud.talk.ui.dialog.ChooseAccountDialogCompose
import com.nextcloud.talk.ui.dialog.ChooseAccountShareToDialogFragment
import com.nextcloud.talk.ui.dialog.ConversationsListBottomDialog
import com.nextcloud.talk.ui.dialog.FilterConversationFragment
Expand Down Expand Up @@ -799,6 +800,15 @@ class ConversationsListActivity :
}
}

private fun showChooseAccountDialog() {
binding.genericComposeView.apply {
val shouldDismiss = mutableStateOf(false)
setContent {
ChooseAccountDialogCompose().GetChooseAccountDialog(shouldDismiss, this@ConversationsListActivity)
}
}
}

private fun loadUserAvatar(button: MaterialButton) {
val target = object : Target {
override fun onStart(placeholder: Drawable?) {
Expand Down Expand Up @@ -1150,8 +1160,7 @@ class ConversationsListActivity :

if (resources!!.getBoolean(R.bool.multiaccount_support) && userManager.users.blockingGet().size > 1) {
dialogBuilder.setPositiveButton(R.string.nc_switch_account) { _, _ ->
val newFragment: DialogFragment = ChooseAccountDialogFragment.newInstance()
newFragment.show(supportFragmentManager, ChooseAccountDialogFragment.TAG)
showChooseAccountDialog()
}
}

Expand Down Expand Up @@ -1308,8 +1317,7 @@ class ConversationsListActivity :

binding.switchAccountButton.setOnClickListener {
if (resources != null && resources!!.getBoolean(R.bool.multiaccount_support)) {
val newFragment: DialogFragment = ChooseAccountDialogFragment.newInstance()
newFragment.show(supportFragmentManager, ChooseAccountDialogFragment.TAG)
showChooseAccountDialog()
} else {
val intent = Intent(context, SettingsActivity::class.java)
startActivity(intent)
Expand Down Expand Up @@ -2081,8 +2089,7 @@ class ConversationsListActivity :

if (resources!!.getBoolean(R.bool.multiaccount_support) && userManager.users.blockingGet().size > 1) {
dialogBuilder.setNegativeButton(R.string.nc_switch_account) { _, _ ->
val newFragment: DialogFragment = ChooseAccountDialogFragment.newInstance()
newFragment.show(supportFragmentManager, ChooseAccountDialogFragment.TAG)
showChooseAccountDialog()
}
}

Expand Down Expand Up @@ -2125,8 +2132,7 @@ class ConversationsListActivity :

if (resources!!.getBoolean(R.bool.multiaccount_support) && userManager.users.blockingGet().size > 1) {
dialogBuilder.setNegativeButton(R.string.nc_switch_account) { _, _ ->
val newFragment: DialogFragment = ChooseAccountDialogFragment.newInstance()
newFragment.show(supportFragmentManager, ChooseAccountDialogFragment.TAG)
showChooseAccountDialog()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import com.nextcloud.talk.chat.data.ChatMessageRepository
import com.nextcloud.talk.chat.data.network.ChatNetworkDataSource
import com.nextcloud.talk.chat.data.network.OfflineFirstChatRepository
import com.nextcloud.talk.chat.data.network.RetrofitChatNetwork
import com.nextcloud.talk.chooseaccount.StatusRepository
import com.nextcloud.talk.chooseaccount.StatusRepositoryImplementation
import com.nextcloud.talk.contacts.ContactsRepository
import com.nextcloud.talk.contacts.ContactsRepositoryImpl
import com.nextcloud.talk.conversationcreation.ConversationCreationRepository
Expand Down Expand Up @@ -147,7 +149,8 @@ class RepositoryModule {
): ConversationInfoEditRepository = ConversationInfoEditRepositoryImpl(ncApi, ncApiCoroutines, userProvider)

@Provides
fun provideInvitationsRepository(ncApi: NcApi): InvitationsRepository = InvitationsRepositoryImpl(ncApi)
fun provideInvitationsRepository(ncApi: NcApi, ncApiCoroutines: NcApiCoroutines): InvitationsRepository =
InvitationsRepositoryImpl(ncApi, ncApiCoroutines)

@Provides
fun provideOfflineFirstChatRepository(
Expand Down Expand Up @@ -215,4 +218,10 @@ class RepositoryModule {
networkLoginDataSource: NetworkLoginDataSource,
localLoginDataSource: LocalLoginDataSource
): LoginRepository = LoginRepository(networkLoginDataSource, localLoginDataSource)

@Provides
fun provideStatusRepository(
ncApiCoroutines: NcApiCoroutines,
currentUserProviderNew: CurrentUserProviderNew
): StatusRepository = StatusRepositoryImplementation(ncApiCoroutines, currentUserProviderNew)
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import androidx.lifecycle.ViewModelProvider
import com.nextcloud.talk.account.viewmodels.BrowserLoginActivityViewModel
import com.nextcloud.talk.chat.viewmodels.ChatViewModel
import com.nextcloud.talk.chat.viewmodels.MessageInputViewModel
import com.nextcloud.talk.chooseaccount.StatusViewModel
import com.nextcloud.talk.contacts.ContactsViewModel
import com.nextcloud.talk.contextchat.ContextChatViewModel
import com.nextcloud.talk.conversationcreation.ConversationCreationViewModel
Expand Down Expand Up @@ -172,4 +173,9 @@ abstract class ViewModelModule {
@IntoMap
@ViewModelKey(ContextChatViewModel::class)
abstract fun contextChatViewModel(viewModel: ContextChatViewModel): ViewModel

@Binds
@IntoMap
@ViewModelKey(StatusViewModel::class)
abstract fun statusRepositoryViewModel(viewModel: StatusViewModel): ViewModel
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ interface InvitationsRepository {
fun fetchInvitations(user: User): Observable<InvitationsModel>
fun acceptInvitation(user: User, invitation: Invitation): Observable<InvitationActionModel>
fun rejectInvitation(user: User, invitation: Invitation): Observable<InvitationActionModel>
suspend fun getInvitations(user: User): InvitationsModel
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
package com.nextcloud.talk.invitation.data

import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.api.NcApiCoroutines
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.utils.ApiUtils
import io.reactivex.Observable

class InvitationsRepositoryImpl(private val ncApi: NcApi) : InvitationsRepository {
class InvitationsRepositoryImpl(private val ncApi: NcApi, private val ncApiCoroutines: NcApiCoroutines) :
InvitationsRepository {

override fun fetchInvitations(user: User): Observable<InvitationsModel> {
val credentials: String = ApiUtils.getCredentials(user.username, user.token)!!
Expand Down Expand Up @@ -40,6 +42,16 @@ class InvitationsRepositoryImpl(private val ncApi: NcApi) : InvitationsRepositor
).map { InvitationActionModel(ActionEnum.REJECT, it.ocs?.meta?.statusCode!!, invitation) }
}

override suspend fun getInvitations(user: User): InvitationsModel {
val credentials: String = ApiUtils.getCredentials(user.username, user.token)!!

val invitationsOverall = ncApiCoroutines.getInvitations(
credentials,
ApiUtils.getUrlForInvitation(user.baseUrl!!)
)
return mapToInvitationsModel(user, invitationsOverall.ocs?.data!!)
}

private fun mapToInvitationsModel(
user: User,
invitations: List<com.nextcloud.talk.models.json.invitation.Invitation>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.invitation.data.ActionEnum
import com.nextcloud.talk.invitation.data.Invitation
Expand All @@ -20,6 +21,9 @@ import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject

class InvitationsViewModel @Inject constructor(private val repository: InvitationsRepository) : ViewModel() {
Expand All @@ -35,6 +39,14 @@ class InvitationsViewModel @Inject constructor(private val repository: Invitatio
val fetchInvitationsViewState: LiveData<ViewState>
get() = _fetchInvitationsViewState

object GetInvitationsStartState : ViewState
object GetInvitationsEmptyState : ViewState
open class GetInvitationsErrorState(val error: Exception) : ViewState
open class GetInvitationsSuccessState(val invitations: List<Invitation>) : ViewState

private val _getInvitationsViewState = MutableStateFlow<ViewState>(GetInvitationsStartState)
val getInvitationsViewState: StateFlow<ViewState> = _getInvitationsViewState

object InvitationActionStartState : ViewState
object InvitationActionErrorState : ViewState

Expand All @@ -53,6 +65,22 @@ class InvitationsViewModel @Inject constructor(private val repository: Invitatio
?.subscribe(FetchInvitationsObserver())
}

@Suppress("TooGenericExceptionCaught")
fun getInvitations(user: User) {
viewModelScope.launch {
try {
val invitationsModel = repository.getInvitations(user)
if (invitationsModel.invitations.isEmpty()) {
_getInvitationsViewState.value = GetInvitationsEmptyState
} else {
_getInvitationsViewState.value = GetInvitationsSuccessState(invitationsModel.invitations)
}
} catch (e: Exception) {
_getInvitationsViewState.value = GetInvitationsErrorState(e)
}
}
}

fun acceptInvitation(user: User, invitation: Invitation) {
repository.acceptInvitation(user, invitation)
.subscribeOn(Schedulers.io())
Expand Down
Loading
Loading