Merge pull request #4446 from nextcloud/coroutine_2

Migrate Rxjava to coroutines #2
This commit is contained in:
Marcel Hibbe 2024-11-18 22:12:33 +01:00 committed by GitHub
commit b748080360
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 135 additions and 149 deletions

View file

@ -195,18 +195,6 @@ public interface NcApi {
@DELETE @DELETE
Observable<GenericOverall> removeSelfFromRoom(@Header("Authorization") String authorization, @Url String url); Observable<GenericOverall> removeSelfFromRoom(@Header("Authorization") String authorization, @Url String url);
/*
Server URL is: baseUrl + ocsApiVersion + spreedApiVersion + /room/roomToken/public
*/
@POST
Observable<GenericOverall> makeRoomPublic(@Header("Authorization") String authorization, @Url String url);
/*
Server URL is: baseUrl + ocsApiVersion + spreedApiVersion + /room/roomToken/public
*/
@DELETE
Observable<GenericOverall> makeRoomPrivate(@Header("Authorization") String authorization, @Url String url);
@DELETE @DELETE
Observable<GenericOverall> deleteRoom(@Header("Authorization") String authorization, @Url String url); Observable<GenericOverall> deleteRoom(@Header("Authorization") String authorization, @Url String url);
@ -341,18 +329,6 @@ public interface NcApi {
Observable<Void> unregisterDeviceForNotificationsWithProxy(@Url String url, Observable<Void> unregisterDeviceForNotificationsWithProxy(@Url String url,
@QueryMap Map<String, String> fields); @QueryMap Map<String, String> fields);
@FormUrlEncoded
@PUT
Observable<GenericOverall> setPassword(@Header("Authorization") String authorization,
@Url String url,
@Field("password") String password);
@FormUrlEncoded
@PUT
Observable<Response<GenericOverall>> setPassword2(@Header("Authorization") String authorization,
@Url String url,
@Field("password") String password);
@GET @GET
Observable<CapabilitiesOverall> getCapabilities(@Header("Authorization") String authorization, @Url String url); Observable<CapabilitiesOverall> getCapabilities(@Header("Authorization") String authorization, @Url String url);

View file

@ -89,10 +89,10 @@ interface NcApiCoroutines {
): AddParticipantOverall ): AddParticipantOverall
@POST @POST
suspend fun makeRoomPublic(@Header("Authorization") authorization: String?, @Url url: String): GenericOverall suspend fun makeRoomPublic(@Header("Authorization") authorization: String, @Url url: String): GenericOverall
@DELETE @DELETE
suspend fun makeRoomPrivate(@Header("Authorization") authorization: String?, @Url url: String): GenericOverall suspend fun makeRoomPrivate(@Header("Authorization") authorization: String, @Url url: String): GenericOverall
@FormUrlEncoded @FormUrlEncoded
@PUT @PUT
@ -132,4 +132,12 @@ interface NcApiCoroutines {
@Url url: String, @Url url: String,
@Body body: RequestBody @Body body: RequestBody
): GenericOverall ): GenericOverall
@FormUrlEncoded
@PUT
suspend fun setPassword2(
@Header("Authorization") authorization: String,
@Url url: String,
@Field("password") password: String
): GenericOverall
} }

View file

@ -168,16 +168,15 @@ class ConversationCreationRepositoryImpl(
val result: GenericOverall = if (allow) { val result: GenericOverall = if (allow) {
ncApiCoroutines.makeRoomPublic( ncApiCoroutines.makeRoomPublic(
credentials, credentials!!,
url url
) )
} else { } else {
ncApiCoroutines.makeRoomPrivate( ncApiCoroutines.makeRoomPrivate(
credentials, credentials!!,
url url
) )
} }
return result return result
} }
} }

View file

@ -916,7 +916,9 @@ class ConversationInfoActivity :
it, it,
conversation!!, conversation!!,
spreedCapabilities, spreedCapabilities,
conversationUser conversationUser,
viewModel,
this
).setupGuestAccess() ).setupGuestAccess()
} }
if (ConversationUtils.isNoteToSelfConversation(conversation!!)) { if (ConversationUtils.isNoteToSelfConversation(conversation!!)) {

View file

@ -12,9 +12,11 @@ import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.LifecycleOwner
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.conversationinfo.viewmodel.ConversationInfoViewModel
import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.ActivityConversationInfoBinding import com.nextcloud.talk.databinding.ActivityConversationInfoBinding
import com.nextcloud.talk.databinding.DialogPasswordBinding import com.nextcloud.talk.databinding.DialogPasswordBinding
@ -33,9 +35,10 @@ class GuestAccessHelper(
private val binding: ActivityConversationInfoBinding, private val binding: ActivityConversationInfoBinding,
private val conversation: ConversationModel, private val conversation: ConversationModel,
private val spreedCapabilities: SpreedCapability, private val spreedCapabilities: SpreedCapability,
private val conversationUser: User private val conversationUser: User,
private val viewModel: ConversationInfoViewModel,
private val lifecycleOwner: LifecycleOwner
) { ) {
private val conversationsRepository = activity.conversationsRepository private val conversationsRepository = activity.conversationsRepository
private val viewThemeUtils = activity.viewThemeUtils private val viewThemeUtils = activity.viewThemeUtils
private val context = activity.context private val context = activity.context
@ -61,19 +64,35 @@ class GuestAccessHelper(
binding.guestAccessView.guestAccessSettingsAllowGuest.setOnClickListener { binding.guestAccessView.guestAccessSettingsAllowGuest.setOnClickListener {
val isChecked = binding.guestAccessView.allowGuestsSwitch.isChecked val isChecked = binding.guestAccessView.allowGuestsSwitch.isChecked
binding.guestAccessView.allowGuestsSwitch.isChecked = !isChecked binding.guestAccessView.allowGuestsSwitch.isChecked = !isChecked
conversationsRepository.allowGuests( viewModel.allowGuests(conversation.token, !isChecked)
conversation.token!!, viewModel.allowGuestsViewState.observe(lifecycleOwner) { uiState ->
!isChecked when (uiState) {
).subscribeOn(Schedulers.io()) is ConversationInfoViewModel.AllowGuestsUIState.Success -> {
.observeOn(AndroidSchedulers.mainThread()).subscribe(AllowGuestsResultObserver()) binding.guestAccessView.allowGuestsSwitch.isChecked = uiState.allow
if (uiState.allow) {
showAllOptions()
} else {
hideAllOptions()
}
}
is ConversationInfoViewModel.AllowGuestsUIState.Error -> {
val exception = uiState.exception
val message = context.getString(R.string.nc_guest_access_allow_failed)
Snackbar.make(binding.root, message, Snackbar.LENGTH_LONG).show()
Log.e(TAG, message, exception)
}
ConversationInfoViewModel.AllowGuestsUIState.None -> {
}
}
}
} }
binding.guestAccessView.guestAccessSettingsPasswordProtection.setOnClickListener { binding.guestAccessView.guestAccessSettingsPasswordProtection.setOnClickListener {
val isChecked = binding.guestAccessView.passwordProtectionSwitch.isChecked val isChecked = binding.guestAccessView.passwordProtectionSwitch.isChecked
binding.guestAccessView.passwordProtectionSwitch.isChecked = !isChecked binding.guestAccessView.passwordProtectionSwitch.isChecked = !isChecked
if (isChecked) { if (isChecked) {
conversationsRepository.password("", conversation.token!!).subscribeOn(Schedulers.io()) viewModel.setPassword("", conversation.token)
.observeOn(AndroidSchedulers.mainThread()).subscribe(PasswordResultObserver(false)) passwordObserver()
} else { } else {
showPasswordDialog() showPasswordDialog()
} }
@ -85,6 +104,25 @@ class GuestAccessHelper(
} }
} }
private fun passwordObserver() {
viewModel.passwordViewState.observe(lifecycleOwner) { uiState ->
when (uiState) {
is ConversationInfoViewModel.PasswordUiState.Success -> {
// unused atm
}
is ConversationInfoViewModel.PasswordUiState.Error -> {
val exception = uiState.exception
val message = context.getString(R.string.nc_guest_access_password_failed)
Snackbar.make(binding.root, message, Snackbar.LENGTH_LONG).show()
Log.e(TAG, message, exception)
}
is ConversationInfoViewModel.PasswordUiState.None -> {
// unused atm
}
}
}
}
private fun showPasswordDialog() { private fun showPasswordDialog() {
val builder = MaterialAlertDialogBuilder(activity) val builder = MaterialAlertDialogBuilder(activity)
builder.apply { builder.apply {
@ -94,16 +132,14 @@ class GuestAccessHelper(
setTitle(R.string.nc_guest_access_password_dialog_title) setTitle(R.string.nc_guest_access_password_dialog_title)
setPositiveButton(R.string.nc_ok) { _, _ -> setPositiveButton(R.string.nc_ok) { _, _ ->
val password = dialogPassword.password.text.toString() val password = dialogPassword.password.text.toString()
conversationsRepository.password(password, conversation.token!!) viewModel.setPassword(password, conversation.token)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(PasswordResultObserver(true))
} }
setNegativeButton(R.string.nc_cancel) { _, _ -> setNegativeButton(R.string.nc_cancel) { _, _ ->
binding.guestAccessView.passwordProtectionSwitch.isChecked = false binding.guestAccessView.passwordProtectionSwitch.isChecked = false
} }
} }
createDialog(builder) createDialog(builder)
passwordObserver()
} }
private fun createDialog(builder: MaterialAlertDialogBuilder) { private fun createDialog(builder: MaterialAlertDialogBuilder) {
@ -143,32 +179,6 @@ class GuestAccessHelper(
} }
} }
inner class AllowGuestsResultObserver : Observer<ConversationsRepository.AllowGuestsResult> {
private lateinit var allowGuestsResult: ConversationsRepository.AllowGuestsResult
override fun onNext(t: ConversationsRepository.AllowGuestsResult) {
allowGuestsResult = t
}
override fun onError(e: Throwable) {
val message = context.getString(R.string.nc_guest_access_allow_failed)
Snackbar.make(binding.root, message, Snackbar.LENGTH_LONG).show()
Log.e(TAG, message, e)
}
override fun onComplete() {
binding.guestAccessView.allowGuestsSwitch.isChecked = allowGuestsResult.allow
if (allowGuestsResult.allow) {
showAllOptions()
} else {
hideAllOptions()
}
}
override fun onSubscribe(d: Disposable) = Unit
}
private fun showAllOptions() { private fun showAllOptions() {
binding.guestAccessView.guestAccessSettingsPasswordProtection.visibility = View.VISIBLE binding.guestAccessView.guestAccessSettingsPasswordProtection.visibility = View.VISIBLE
if (conversationUser.capabilities?.spreedCapability?.features?.contains("sip-support") == true) { if (conversationUser.capabilities?.spreedCapability?.features?.contains("sip-support") == true) {
@ -181,37 +191,6 @@ class GuestAccessHelper(
binding.guestAccessView.resendInvitationsButton.visibility = View.GONE binding.guestAccessView.resendInvitationsButton.visibility = View.GONE
} }
inner class PasswordResultObserver(private val setPassword: Boolean) :
Observer<ConversationsRepository.PasswordResult> {
private lateinit var passwordResult: ConversationsRepository.PasswordResult
override fun onSubscribe(d: Disposable) = Unit
override fun onNext(t: ConversationsRepository.PasswordResult) {
passwordResult = t
}
override fun onError(e: Throwable) {
val message = context.getString(R.string.nc_guest_access_password_failed)
Snackbar.make(binding.root, message, Snackbar.LENGTH_LONG).show()
Log.e(TAG, message, e)
}
override fun onComplete() {
binding.guestAccessView.passwordProtectionSwitch.isChecked = passwordResult.passwordSet && setPassword
if (passwordResult.passwordIsWeak) {
val builder = MaterialAlertDialogBuilder(activity)
builder.apply {
setTitle(R.string.nc_guest_access_password_weak_alert_title)
setMessage(passwordResult.message)
setPositiveButton("OK") { _, _ -> }
}
createDialog(builder)
}
}
}
companion object { companion object {
private val TAG = GuestAccessHelper::class.simpleName private val TAG = GuestAccessHelper::class.simpleName
} }

View file

@ -6,12 +6,14 @@
*/ */
package com.nextcloud.talk.conversationinfo.viewmodel package com.nextcloud.talk.conversationinfo.viewmodel
import android.annotation.SuppressLint
import android.util.Log import android.util.Log
import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.nextcloud.talk.chat.data.network.ChatNetworkDataSource import com.nextcloud.talk.chat.data.network.ChatNetworkDataSource
import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ConversationModel import com.nextcloud.talk.models.domain.ConversationModel
@ -24,6 +26,7 @@ import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
class ConversationInfoViewModel @Inject constructor( class ConversationInfoViewModel @Inject constructor(
@ -95,6 +98,14 @@ class ConversationInfoViewModel @Inject constructor(
object GetCapabilitiesErrorState : ViewState object GetCapabilitiesErrorState : ViewState
open class GetCapabilitiesSuccessState(val spreedCapabilities: SpreedCapability) : ViewState open class GetCapabilitiesSuccessState(val spreedCapabilities: SpreedCapability) : ViewState
private val _allowGuestsViewState = MutableLiveData<AllowGuestsUIState>(AllowGuestsUIState.None)
val allowGuestsViewState: LiveData<AllowGuestsUIState>
get() = _allowGuestsViewState
private val _passwordViewState = MutableLiveData<PasswordUiState>(PasswordUiState.None)
val passwordViewState: LiveData<PasswordUiState>
get() = _passwordViewState
private val _getCapabilitiesViewState: MutableLiveData<ViewState> = MutableLiveData(GetCapabilitiesStartState) private val _getCapabilitiesViewState: MutableLiveData<ViewState> = MutableLiveData(GetCapabilitiesStartState)
val getCapabilitiesViewState: LiveData<ViewState> val getCapabilitiesViewState: LiveData<ViewState>
get() = _getCapabilitiesViewState get() = _getCapabilitiesViewState
@ -233,6 +244,29 @@ class ConversationInfoViewModel @Inject constructor(
}) })
} }
fun allowGuests(token: String, allow: Boolean) {
viewModelScope.launch {
try {
conversationsRepository.allowGuests(token, allow)
_allowGuestsViewState.value = AllowGuestsUIState.Success(allow)
} catch (exception: Exception) {
_allowGuestsViewState.value = AllowGuestsUIState.Error(exception)
}
}
}
@SuppressLint("SuspiciousIndentation")
fun setPassword(password: String, token: String) {
viewModelScope.launch {
try {
conversationsRepository.setPassword(password, token)
_passwordViewState.value = PasswordUiState.Success
} catch (exception: Exception) {
_passwordViewState.value = PasswordUiState.Error(exception)
}
}
}
suspend fun archiveConversation(user: User, token: String) { suspend fun archiveConversation(user: User, token: String) {
val apiVersion = ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V1)) val apiVersion = ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V1))
val url = ApiUtils.getUrlForArchive(apiVersion, user.baseUrl, token) val url = ApiUtils.getUrlForArchive(apiVersion, user.baseUrl, token)
@ -267,4 +301,16 @@ class ConversationInfoViewModel @Inject constructor(
companion object { companion object {
private val TAG = ConversationInfoViewModel::class.simpleName private val TAG = ConversationInfoViewModel::class.simpleName
} }
sealed class AllowGuestsUIState {
data object None : AllowGuestsUIState()
data class Success(val allow: Boolean) : AllowGuestsUIState()
data class Error(val exception: Exception) : AllowGuestsUIState()
}
sealed class PasswordUiState {
data object None : PasswordUiState()
data object Success : PasswordUiState()
data class Error(val exception: Exception) : PasswordUiState()
}
} }

View file

@ -12,19 +12,7 @@ import io.reactivex.Observable
interface ConversationsRepository { interface ConversationsRepository {
data class AllowGuestsResult( suspend fun allowGuests(token: String, allow: Boolean): GenericOverall
val allow: Boolean
)
fun allowGuests(token: String, allow: Boolean): Observable<AllowGuestsResult>
data class PasswordResult(
val passwordSet: Boolean,
val passwordIsWeak: Boolean,
val message: String
)
fun password(password: String, token: String): Observable<PasswordResult>
data class ResendInvitationsResult( data class ResendInvitationsResult(
val successful: Boolean val successful: Boolean
@ -35,5 +23,7 @@ interface ConversationsRepository {
suspend fun unarchiveConversation(credentials: String, url: String): GenericOverall suspend fun unarchiveConversation(credentials: String, url: String): GenericOverall
suspend fun setPassword(password: String, token: String): GenericOverall
fun setConversationReadOnly(credentials: String, url: String, state: Int): Observable<GenericOverall> fun setConversationReadOnly(credentials: String, url: String, state: Int): Observable<GenericOverall>
} }

View file

@ -7,14 +7,10 @@
*/ */
package com.nextcloud.talk.repositories.conversations package com.nextcloud.talk.repositories.conversations
import com.bluelinelabs.logansquare.LoganSquare
import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.api.NcApiCoroutines import com.nextcloud.talk.api.NcApiCoroutines
import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.json.conversations.password.PasswordOverall
import com.nextcloud.talk.models.json.generic.GenericOverall import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.repositories.conversations.ConversationsRepository.AllowGuestsResult
import com.nextcloud.talk.repositories.conversations.ConversationsRepository.PasswordResult
import com.nextcloud.talk.repositories.conversations.ConversationsRepository.ResendInvitationsResult import com.nextcloud.talk.repositories.conversations.ConversationsRepository.ResendInvitationsResult
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
@ -24,8 +20,7 @@ class ConversationsRepositoryImpl(
private val api: NcApi, private val api: NcApi,
private val coroutineApi: NcApiCoroutines, private val coroutineApi: NcApiCoroutines,
private val userProvider: CurrentUserProviderNew private val userProvider: CurrentUserProviderNew
) : ) : ConversationsRepository {
ConversationsRepository {
private val user: User private val user: User
get() = userProvider.currentUser.blockingGet() get() = userProvider.currentUser.blockingGet()
@ -33,48 +28,27 @@ class ConversationsRepositoryImpl(
private val credentials: String private val credentials: String
get() = ApiUtils.getCredentials(user.username, user.token)!! get() = ApiUtils.getCredentials(user.username, user.token)!!
override fun allowGuests(token: String, allow: Boolean): Observable<AllowGuestsResult> { val apiVersion = ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V1))
override suspend fun allowGuests(token: String, allow: Boolean): GenericOverall {
val url = ApiUtils.getUrlForRoomPublic( val url = ApiUtils.getUrlForRoomPublic(
apiVersion(), apiVersion,
user.baseUrl!!, user.baseUrl!!,
token token
) )
val apiObservable = if (allow) { val result: GenericOverall = if (allow) {
api.makeRoomPublic( coroutineApi.makeRoomPublic(
credentials, credentials,
url url
) )
} else { } else {
api.makeRoomPrivate( coroutineApi.makeRoomPrivate(
credentials, credentials,
url url
) )
} }
return result
return apiObservable.map { AllowGuestsResult(it.ocs!!.meta!!.statusCode == STATUS_CODE_OK && allow) }
}
override fun password(password: String, token: String): Observable<PasswordResult> {
val apiObservable = api.setPassword2(
credentials,
ApiUtils.getUrlForRoomPassword(
apiVersion(),
user.baseUrl!!,
token
),
password
)
return apiObservable.map {
val passwordPolicyMessage = if (it.code() == STATUS_CODE_BAD_REQUEST) {
LoganSquare.parse(it.errorBody()!!.string(), PasswordOverall::class.java).ocs!!.data!!
.message!!
} else {
""
}
PasswordResult(it.isSuccessful, passwordPolicyMessage.isNotEmpty(), passwordPolicyMessage)
}
} }
override fun resendInvitations(token: String): Observable<ResendInvitationsResult> { override fun resendInvitations(token: String): Observable<ResendInvitationsResult> {
@ -104,12 +78,24 @@ class ConversationsRepositoryImpl(
return api.setConversationReadOnly(credentials, url, state) return api.setConversationReadOnly(credentials, url, state)
} }
override suspend fun setPassword(password: String, token: String): GenericOverall {
val result = coroutineApi.setPassword(
credentials,
ApiUtils.getUrlForRoomPassword(
apiVersion,
user.baseUrl!!,
token
),
password
)
return result
}
private fun apiVersion(): Int { private fun apiVersion(): Int {
return ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.API_V4)) return ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.API_V4))
} }
companion object { companion object {
const val STATUS_CODE_OK = 200 const val STATUS_CODE_OK = 200
const val STATUS_CODE_BAD_REQUEST = 400
} }
} }