Merge pull request #3038 from nextcloud/translate-message-mvvm-impl

rewriting translate feature to use MVVM
This commit is contained in:
Marcel Hibbe 2023-05-23 19:09:34 +02:00 committed by GitHub
commit fd5fec937f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 212 additions and 100 deletions

View file

@ -190,7 +190,7 @@
android:theme="@style/AppTheme" />
<activity
android:name=".translate.TranslateActivity"
android:name=".translate.ui.TranslateActivity"
android:theme="@style/AppTheme" />
<activity

View file

@ -44,7 +44,7 @@ import com.nextcloud.talk.models.json.search.ContactsByNumberOverall;
import com.nextcloud.talk.models.json.signaling.SignalingOverall;
import com.nextcloud.talk.models.json.signaling.settings.SignalingSettingsOverall;
import com.nextcloud.talk.models.json.status.StatusOverall;
import com.nextcloud.talk.models.json.translations.TranslationsOverall;
import com.nextcloud.talk.translate.repositories.model.TranslationsOverall;
import com.nextcloud.talk.models.json.unifiedsearch.UnifiedSearchOverall;
import com.nextcloud.talk.models.json.userprofile.UserProfileFieldsOverall;
import com.nextcloud.talk.models.json.userprofile.UserProfileOverall;

View file

@ -155,8 +155,8 @@ import com.nextcloud.talk.remotefilebrowser.activities.RemoteFileBrowserActivity
import com.nextcloud.talk.repositories.reactions.ReactionsRepository
import com.nextcloud.talk.shareditems.activities.SharedItemsActivity
import com.nextcloud.talk.signaling.SignalingMessageReceiver
import com.nextcloud.talk.translate.ui.TranslateActivity
import com.nextcloud.talk.signaling.SignalingMessageSender
import com.nextcloud.talk.translate.TranslateActivity
import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet
import com.nextcloud.talk.ui.dialog.AttachmentDialog
import com.nextcloud.talk.ui.dialog.MessageActionsDialog

View file

@ -47,6 +47,8 @@ import com.nextcloud.talk.repositories.unifiedsearch.UnifiedSearchRepository
import com.nextcloud.talk.repositories.unifiedsearch.UnifiedSearchRepositoryImpl
import com.nextcloud.talk.shareditems.repositories.SharedItemsRepository
import com.nextcloud.talk.shareditems.repositories.SharedItemsRepositoryImpl
import com.nextcloud.talk.translate.repositories.TranslateRepository
import com.nextcloud.talk.translate.repositories.TranslateRepositoryImpl
import com.nextcloud.talk.utils.DateUtils
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import dagger.Module
@ -107,4 +109,10 @@ class RepositoryModule {
RequestAssistanceRepository {
return RequestAssistanceRepositoryImpl(ncApi, userProvider)
}
@Provides
fun translateRepository(ncApi: NcApi):
TranslateRepository {
return TranslateRepositoryImpl(ncApi)
}
}

View file

@ -31,6 +31,7 @@ import com.nextcloud.talk.polls.viewmodels.PollVoteViewModel
import com.nextcloud.talk.raisehand.viewmodel.RaiseHandViewModel
import com.nextcloud.talk.remotefilebrowser.viewmodels.RemoteFileBrowserItemsViewModel
import com.nextcloud.talk.shareditems.viewmodels.SharedItemsViewModel
import com.nextcloud.talk.translate.viewmodels.TranslateViewModel
import com.nextcloud.talk.viewmodels.CallRecordingViewModel
import dagger.Binds
import dagger.MapKey
@ -101,4 +102,9 @@ abstract class ViewModelModule {
@IntoMap
@ViewModelKey(RaiseHandViewModel::class)
abstract fun raiseHandViewModel(viewModel: RaiseHandViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(TranslateViewModel::class)
abstract fun translateViewModel(viewModel: TranslateViewModel): ViewModel
}

View file

@ -0,0 +1,14 @@
package com.nextcloud.talk.translate.repositories
import io.reactivex.Observable
interface TranslateRepository {
fun translateMessage(
authorization: String,
url: String,
text: String,
toLanguage: String,
fromLanguage: String?
): Observable<String>
}

View file

@ -0,0 +1,18 @@
package com.nextcloud.talk.translate.repositories
import com.nextcloud.talk.api.NcApi
import io.reactivex.Observable
import javax.inject.Inject
class TranslateRepositoryImpl @Inject constructor(private val ncApi: NcApi) : TranslateRepository {
override fun translateMessage(
authorization: String,
url: String,
text: String,
toLanguage: String,
fromLanguage: String?
): Observable<String> {
return ncApi.translateMessage(authorization, url, text, toLanguage, fromLanguage).map { it.ocs?.data!!.text }
}
}

View file

@ -17,7 +17,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.models.json.translations
package com.nextcloud.talk.translate.repositories.model
import android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField

View file

@ -17,7 +17,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.models.json.translations
package com.nextcloud.talk.translate.repositories.model
import android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField

View file

@ -17,7 +17,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.models.json.translations
package com.nextcloud.talk.translate.repositories.model
import android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField

View file

@ -19,68 +19,82 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.translate
package com.nextcloud.talk.translate.ui
import android.app.AlertDialog
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.text.method.ScrollingMovementMethod
import android.util.Log
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.ViewModelProvider
import autodagger.AutoInjector
import com.google.android.material.dialog.MaterialAlertDialogBuilder
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.data.user.model.User
import com.nextcloud.talk.databinding.ActivityTranslateBinding
import com.nextcloud.talk.models.json.translations.TranslationsOverall
import com.nextcloud.talk.translate.viewmodels.TranslateViewModel
import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import org.json.JSONArray
import java.util.Locale
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class TranslateActivity : BaseActivity() {
private lateinit var binding: ActivityTranslateBinding
@Inject
lateinit var ncApi: NcApi
@Inject
lateinit var userManager: UserManager
private var fromLanguages = arrayOf<String>()
private var toLanguages = arrayOf<String>()
private var text: String? = null
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
lateinit var viewModel: TranslateViewModel
lateinit var binding: ActivityTranslateBinding
private var toLanguages: Array<String>? = null
private var fromLanguages: Array<String>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
binding = ActivityTranslateBinding.inflate(layoutInflater)
viewModel = ViewModelProvider(this, viewModelFactory)[TranslateViewModel::class.java]
viewModel.viewState.observe(this) { state ->
when (state) {
is TranslateViewModel.StartState -> {
onStartState()
}
is TranslateViewModel.TranslatedState -> {
onTranslatedState(state.msg)
}
is TranslateViewModel.ErrorState -> {
onErrorState()
}
}
}
setupActionBar()
setContentView(binding.root)
setupSystemColors()
setupTextViews()
getLanguageOptions()
setupSpinners()
setupCopyButton()
getLanguageOptions()
if (savedInstanceState == null) {
translate(null, Locale.getDefault().language)
val text = intent.extras!!.getString(BundleKeys.KEY_TRANSLATE_MESSAGE)
viewModel.translateMessage(Locale.getDefault().language, null, text!!)
} else {
binding.translatedMessageTextview.text = savedInstanceState.getString(BundleKeys.SAVED_TRANSLATED_MESSAGE)
}
@ -90,7 +104,6 @@ class TranslateActivity : BaseActivity() {
super.onResume()
setItems()
}
override fun onSaveInstanceState(outState: Bundle) {
outState.run {
putString(BundleKeys.SAVED_TRANSLATED_MESSAGE, binding.translatedMessageTextview.text.toString())
@ -137,14 +150,12 @@ class TranslateActivity : BaseActivity() {
binding.originalMessageTextview.movementMethod = ScrollingMovementMethod()
binding.translatedMessageTextview.movementMethod = ScrollingMovementMethod()
val bundle = intent.extras
binding.originalMessageTextview.text = bundle?.getString(BundleKeys.KEY_TRANSLATE_MESSAGE)
text = bundle?.getString(BundleKeys.KEY_TRANSLATE_MESSAGE)
val text = intent.extras!!.getString(BundleKeys.KEY_TRANSLATE_MESSAGE)
binding.originalMessageTextview.text = text
}
private fun getLanguageOptions() {
val currentUser: User = userManager.currentUser.blockingGet()
val currentUser = userManager.currentUser.blockingGet()
val json = JSONArray(CapabilitiesUtilNew.getLanguages(currentUser).toString())
val fromLanguagesSet = mutableSetOf(resources.getString(R.string.translation_detect_language))
@ -159,9 +170,8 @@ class TranslateActivity : BaseActivity() {
fromLanguagesSet.add(current.getString(TO_LABEL))
}
fromLanguages = fromLanguagesSet.toTypedArray()
toLanguages = toLanguagesSet.toTypedArray()
fillSpinners()
fromLanguages = fromLanguagesSet.toTypedArray()
}
private fun enableSpinners(value: Boolean) {
@ -169,63 +179,27 @@ class TranslateActivity : BaseActivity() {
binding.toLanguageInputLayout.isEnabled = value
}
private fun translate(fromLanguage: String?, toLanguage: String) {
val currentUser: User = userManager.currentUser.blockingGet()
val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)
val translateURL = ApiUtils.getUrlForTranslation(currentUser.baseUrl)
val calculatedFromLanguage = if (fromLanguage == null || fromLanguage == "") {
null
} else {
fromLanguage
}
private fun showDialog() {
val dialogBuilder = MaterialAlertDialogBuilder(this@TranslateActivity)
.setIcon(
viewThemeUtils.dialog.colorMaterialAlertDialogIcon(
context,
R.drawable.ic_warning_white
)
)
.setTitle(R.string.translation_error_title)
.setMessage(R.string.translation_error_message)
.setPositiveButton(R.string.nc_ok) { dialog, _ ->
dialog.dismiss()
}
ncApi.translateMessage(credentials, translateURL, text, toLanguage, calculatedFromLanguage)
?.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(object : Observer<TranslationsOverall> {
override fun onSubscribe(d: Disposable) {
enableSpinners(false)
binding.translatedMessageContainer.visibility = View.GONE
binding.progressBar.visibility = View.VISIBLE
}
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(context, dialogBuilder)
override fun onNext(translationOverall: TranslationsOverall) {
binding.progressBar.visibility = View.GONE
binding.translatedMessageContainer.visibility = View.VISIBLE
binding.translatedMessageTextview.text = translationOverall.ocs?.data?.text
}
val dialog = dialogBuilder.show()
override fun onError(e: Throwable) {
Log.w(TAG, "Error while translating message", e)
binding.progressBar.visibility = View.GONE
val dialogBuilder = MaterialAlertDialogBuilder(this@TranslateActivity)
.setIcon(
viewThemeUtils.dialog.colorMaterialAlertDialogIcon(
context,
R.drawable.ic_warning_white
)
)
.setTitle(R.string.translation_error_title)
.setMessage(R.string.translation_error_message)
.setPositiveButton(R.string.nc_ok) { dialog, _ ->
dialog.dismiss()
}
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(context, dialogBuilder)
val dialog = dialogBuilder.show()
viewThemeUtils.platform.colorTextButtons(
dialog.getButton(AlertDialog.BUTTON_POSITIVE)
)
}
override fun onComplete() {
// nothing?
}
})
enableSpinners(true)
viewThemeUtils.platform.colorTextButtons(
dialog.getButton(AlertDialog.BUTTON_POSITIVE)
)
}
private fun getISOFromLanguage(language: String): String {
@ -237,7 +211,7 @@ class TranslateActivity : BaseActivity() {
}
private fun getISOFromServer(language: String): String {
val currentUser: User = userManager.currentUser.blockingGet()
val currentUser = userManager.currentUser.blockingGet()
val json = JSONArray(CapabilitiesUtilNew.getLanguages(currentUser).toString())
for (i in 0 until json.length()) {
@ -254,43 +228,64 @@ class TranslateActivity : BaseActivity() {
viewThemeUtils.material.colorTextInputLayout(binding.fromLanguageInputLayout)
viewThemeUtils.material.colorTextInputLayout(binding.toLanguageInputLayout)
fillSpinners()
val text = intent.extras!!.getString(BundleKeys.KEY_TRANSLATE_MESSAGE)
binding.fromLanguage.onItemClickListener = AdapterView.OnItemClickListener { parent, _, position, _ ->
val fromLabel: String = getISOFromLanguage(parent.getItemAtPosition(position).toString())
val toLabel: String = getISOFromLanguage(binding.toLanguage.text.toString())
translate(fromLabel, toLabel)
viewModel.translateMessage(toLabel, fromLabel, text!!)
}
binding.toLanguage.onItemClickListener = AdapterView.OnItemClickListener { parent, _, position, _ ->
val toLabel: String = getISOFromLanguage(parent.getItemAtPosition(position).toString())
val fromLabel: String = getISOFromLanguage(binding.fromLanguage.text.toString())
translate(fromLabel, toLabel)
viewModel.translateMessage(toLabel, fromLabel, text!!)
}
}
private fun fillSpinners() {
binding.fromLanguage.setAdapter(
ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, fromLanguages)
ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, fromLanguages!!)
)
if (fromLanguages.isNotEmpty()) {
binding.fromLanguage.setText(fromLanguages[0])
if (fromLanguages!!.isNotEmpty()) {
binding.fromLanguage.setText(fromLanguages!![0])
}
binding.toLanguage.setAdapter(
ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, toLanguages)
ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, toLanguages!!)
)
if (toLanguages.isNotEmpty()) {
binding.toLanguage.setText(toLanguages[0])
if (toLanguages!!.isNotEmpty()) {
binding.toLanguage.setText(toLanguages!![0])
}
}
private fun setItems() {
binding.fromLanguage.setSimpleItems(fromLanguages)
binding.toLanguage.setSimpleItems(toLanguages)
binding.fromLanguage.setSimpleItems(fromLanguages!!)
binding.toLanguage.setSimpleItems(toLanguages!!)
}
private fun onStartState() {
enableSpinners(false)
binding.translatedMessageContainer.visibility = View.GONE
binding.progressBar.visibility = View.VISIBLE
binding.copyTranslatedMessage.visibility = View.GONE
}
private fun onTranslatedState(msg: String) {
binding.progressBar.visibility = View.GONE
binding.translatedMessageContainer.visibility = View.VISIBLE
binding.translatedMessageTextview.text = msg
binding.copyTranslatedMessage.visibility = View.VISIBLE
enableSpinners(true)
}
private fun onErrorState() {
binding.progressBar.visibility = View.GONE
enableSpinners(true)
showDialog()
}
companion object {
private val TAG = TranslateActivity::class.simpleName
private const val FROM_ID = "from"
private const val FROM_LABEL = "fromLabel"
private const val TO_LABEL = "toLabel"

View file

@ -0,0 +1,71 @@
package com.nextcloud.talk.translate.viewmodels
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.translate.repositories.TranslateRepository
import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.ApiUtils
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import javax.inject.Inject
class TranslateViewModel @Inject constructor(
private val repository: TranslateRepository,
private val userManager: UserManager
) : ViewModel() {
sealed interface ViewState
object StartState : ViewState
class TranslatedState(val msg: String) : ViewState
object ErrorState : ViewState
private val _viewState: MutableLiveData<ViewState> = MutableLiveData(StartState)
val viewState: LiveData<ViewState>
get() = _viewState
fun translateMessage(toLanguage: String, fromLanguage: String?, text: String) {
val currentUser: User = userManager.currentUser.blockingGet()
val authorization: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)
val url: String = ApiUtils.getUrlForTranslation(currentUser.baseUrl)
val calculatedFromLanguage = if (fromLanguage == null || fromLanguage == "") { null } else { fromLanguage }
Log.i(TAG, "translateMessage Called")
repository.translateMessage(
authorization,
url,
text,
toLanguage,
calculatedFromLanguage
)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(TranslateObserver())
}
inner class TranslateObserver : Observer<String> {
override fun onSubscribe(d: Disposable) {
_viewState.value = StartState
}
override fun onNext(translatedMessage: String) {
_viewState.value = TranslatedState(translatedMessage)
}
override fun onError(e: Throwable) {
_viewState.value = ErrorState
Log.e(TAG, "Error while translating message", e)
}
override fun onComplete() {
// nothing?
}
}
companion object {
private val TAG = TranslateViewModel::class.simpleName
}
}