WIP rewriting translate feature to use MVVM

Signed-off-by: Julius Linus <julius.linus@nextcloud.com>
This commit is contained in:
rapterjet2004 2023-05-19 17:12:37 -05:00
parent fa7cd52b6e
commit c62a904c82
10 changed files with 187 additions and 94 deletions

View File

@ -190,7 +190,7 @@
android:theme="@style/AppTheme" /> android:theme="@style/AppTheme" />
<activity <activity
android:name=".translate.TranslateActivity" android:name=".translate.ui.TranslateActivity"
android:theme="@style/AppTheme" /> android:theme="@style/AppTheme" />
<activity <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.SignalingOverall;
import com.nextcloud.talk.models.json.signaling.settings.SignalingSettingsOverall; import com.nextcloud.talk.models.json.signaling.settings.SignalingSettingsOverall;
import com.nextcloud.talk.models.json.status.StatusOverall; 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.unifiedsearch.UnifiedSearchOverall;
import com.nextcloud.talk.models.json.userprofile.UserProfileFieldsOverall; import com.nextcloud.talk.models.json.userprofile.UserProfileFieldsOverall;
import com.nextcloud.talk.models.json.userprofile.UserProfileOverall; import com.nextcloud.talk.models.json.userprofile.UserProfileOverall;

View File

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

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
class TranslateRepositoryImpl(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 * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * 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 android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField import com.bluelinelabs.logansquare.annotation.JsonField

View File

@ -17,7 +17,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * 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 android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField import com.bluelinelabs.logansquare.annotation.JsonField

View File

@ -17,7 +17,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * 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 android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField import com.bluelinelabs.logansquare.annotation.JsonField

View File

@ -19,68 +19,83 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * 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.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.os.Bundle import android.os.Bundle
import android.text.method.ScrollingMovementMethod import android.text.method.ScrollingMovementMethod
import android.util.Log
import android.view.View import android.view.View
import android.widget.AdapterView import android.widget.AdapterView
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import androidx.appcompat.app.AlertDialog import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import autodagger.AutoInjector import autodagger.AutoInjector
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.activities.BaseActivity import com.nextcloud.talk.activities.BaseActivity
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.ActivityTranslateBinding 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.users.UserManager
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.bundle.BundleKeys import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew 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 org.json.JSONArray
import java.util.Locale import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class) @AutoInjector(NextcloudTalkApplication::class)
class TranslateActivity : BaseActivity() { class TranslateActivity : BaseActivity() {
private lateinit var binding: ActivityTranslateBinding
@Inject
lateinit var ncApi: NcApi
@Inject @Inject
lateinit var userManager: UserManager lateinit var userManager: UserManager
private var fromLanguages = arrayOf<String>() @Inject
private var toLanguages = arrayOf<String>() lateinit var viewModelFactory: ViewModelProvider.Factory
private var text: String? = null
private lateinit var viewModel: TranslateViewModel
private lateinit var binding: ActivityTranslateBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this) NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
binding = ActivityTranslateBinding.inflate(layoutInflater) binding = ActivityTranslateBinding.inflate(layoutInflater)
viewModel = ViewModelProvider(this, viewModelFactory)[TranslateViewModel::class.java] // fixme bug here
setupActionBar() setupActionBar()
setContentView(binding.root) setContentView(binding.root)
setupSystemColors() setupSystemColors()
setupTextViews() setupTextViews()
getLanguageOptions()
setupSpinners() setupSpinners()
setupCopyButton() setupCopyButton()
getLanguageOptions()
val translateViewModelObserver = Observer<TranslateViewModel.TranslateUiState> { state ->
enableSpinners(state.enableSpinners)
binding.progressBar.visibility = if(state.showProgressBar) View.VISIBLE else View.GONE
binding.translatedMessageContainer.visibility =
if(state.showTranslatedMessageContainer) View.VISIBLE else View.GONE
if(state.translatedMessageText != null)
binding.translatedMessageTextview.text = state.translatedMessageText
if(state.errorOccurred)
showDialog()
}
viewModel.viewState.observe(this, translateViewModelObserver)
if (savedInstanceState == null) { if (savedInstanceState == null) {
translate(null, Locale.getDefault().language) viewModel.translateMessage(Locale.getDefault().language, null)
} else { } else {
binding.translatedMessageTextview.text = savedInstanceState.getString(BundleKeys.SAVED_TRANSLATED_MESSAGE) binding.translatedMessageTextview.text = savedInstanceState.getString(BundleKeys.SAVED_TRANSLATED_MESSAGE)
} }
@ -90,7 +105,6 @@ class TranslateActivity : BaseActivity() {
super.onResume() super.onResume()
setItems() setItems()
} }
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
outState.run { outState.run {
putString(BundleKeys.SAVED_TRANSLATED_MESSAGE, binding.translatedMessageTextview.text.toString()) putString(BundleKeys.SAVED_TRANSLATED_MESSAGE, binding.translatedMessageTextview.text.toString())
@ -137,14 +151,14 @@ class TranslateActivity : BaseActivity() {
binding.originalMessageTextview.movementMethod = ScrollingMovementMethod() binding.originalMessageTextview.movementMethod = ScrollingMovementMethod()
binding.translatedMessageTextview.movementMethod = ScrollingMovementMethod() binding.translatedMessageTextview.movementMethod = ScrollingMovementMethod()
val bundle = intent.extras val bundle = intent.extras
binding.originalMessageTextview.text = bundle?.getString(BundleKeys.KEY_TRANSLATE_MESSAGE) val text = bundle?.getString(BundleKeys.KEY_TRANSLATE_MESSAGE)
text = bundle?.getString(BundleKeys.KEY_TRANSLATE_MESSAGE) binding.originalMessageTextview.text = text
viewModel.viewState.value!!.originalMessageText = text
} }
private fun getLanguageOptions() { private fun getLanguageOptions() {
val currentUser: User = userManager.currentUser.blockingGet() val currentUser = userManager.currentUser.blockingGet()
val json = JSONArray(CapabilitiesUtilNew.getLanguages(currentUser).toString()) val json = JSONArray(CapabilitiesUtilNew.getLanguages(currentUser).toString())
val fromLanguagesSet = mutableSetOf(resources.getString(R.string.translation_detect_language)) val fromLanguagesSet = mutableSetOf(resources.getString(R.string.translation_detect_language))
@ -159,9 +173,8 @@ class TranslateActivity : BaseActivity() {
fromLanguagesSet.add(current.getString(TO_LABEL)) fromLanguagesSet.add(current.getString(TO_LABEL))
} }
fromLanguages = fromLanguagesSet.toTypedArray() viewModel.viewState.value!!.toLanguages = toLanguagesSet.toTypedArray()
toLanguages = toLanguagesSet.toTypedArray() viewModel.viewState.value!!.fromLanguages = fromLanguagesSet.toTypedArray()
fillSpinners()
} }
private fun enableSpinners(value: Boolean) { private fun enableSpinners(value: Boolean) {
@ -169,65 +182,31 @@ class TranslateActivity : BaseActivity() {
binding.toLanguageInputLayout.isEnabled = value binding.toLanguageInputLayout.isEnabled = value
} }
private fun translate(fromLanguage: String?, toLanguage: String) { private fun showDialog() {
val currentUser: User = userManager.currentUser.blockingGet() val dialogBuilder = MaterialAlertDialogBuilder(this@TranslateActivity)
val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token) .setIcon(
val translateURL = ApiUtils.getUrlForTranslation(currentUser.baseUrl) viewThemeUtils.dialog.colorMaterialAlertDialogIcon(
val calculatedFromLanguage = if (fromLanguage == null || fromLanguage == "") { context,
null R.drawable.ic_warning_white
} else { )
fromLanguage )
} .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) viewThemeUtils.dialog.colorMaterialAlertDialogBackground(context, dialogBuilder)
?.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
}
override fun onNext(translationOverall: TranslationsOverall) { val dialog = dialogBuilder.show()
binding.progressBar.visibility = View.GONE
binding.translatedMessageContainer.visibility = View.VISIBLE
binding.translatedMessageTextview.text = translationOverall.ocs?.data?.text
}
override fun onError(e: Throwable) { viewThemeUtils.platform.colorTextButtons(
Log.w(TAG, "Error while translating message", e) dialog.getButton(AlertDialog.BUTTON_POSITIVE)
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)
} }
private fun getISOFromLanguage(language: String): String { private fun getISOFromLanguage(language: String): String {
if (resources.getString(R.string.translation_device_settings) == language) { if (resources.getString(R.string.translation_device_settings) == language) {
return Locale.getDefault().language return Locale.getDefault().language
@ -237,7 +216,7 @@ class TranslateActivity : BaseActivity() {
} }
private fun getISOFromServer(language: String): String { private fun getISOFromServer(language: String): String {
val currentUser: User = userManager.currentUser.blockingGet() val currentUser = userManager.currentUser.blockingGet()
val json = JSONArray(CapabilitiesUtilNew.getLanguages(currentUser).toString()) val json = JSONArray(CapabilitiesUtilNew.getLanguages(currentUser).toString())
for (i in 0 until json.length()) { for (i in 0 until json.length()) {
@ -258,17 +237,20 @@ class TranslateActivity : BaseActivity() {
binding.fromLanguage.onItemClickListener = AdapterView.OnItemClickListener { parent, _, position, _ -> binding.fromLanguage.onItemClickListener = AdapterView.OnItemClickListener { parent, _, position, _ ->
val fromLabel: String = getISOFromLanguage(parent.getItemAtPosition(position).toString()) val fromLabel: String = getISOFromLanguage(parent.getItemAtPosition(position).toString())
val toLabel: String = getISOFromLanguage(binding.toLanguage.text.toString()) val toLabel: String = getISOFromLanguage(binding.toLanguage.text.toString())
translate(fromLabel, toLabel) viewModel.translateMessage(toLabel, fromLabel)
} }
binding.toLanguage.onItemClickListener = AdapterView.OnItemClickListener { parent, _, position, _ -> binding.toLanguage.onItemClickListener = AdapterView.OnItemClickListener { parent, _, position, _ ->
val toLabel: String = getISOFromLanguage(parent.getItemAtPosition(position).toString()) val toLabel: String = getISOFromLanguage(parent.getItemAtPosition(position).toString())
val fromLabel: String = getISOFromLanguage(binding.fromLanguage.text.toString()) val fromLabel: String = getISOFromLanguage(binding.fromLanguage.text.toString())
translate(fromLabel, toLabel) viewModel.translateMessage(toLabel, fromLabel)
} }
} }
private fun fillSpinners() { private fun fillSpinners() {
val fromLanguages = viewModel.viewState.value!!.fromLanguages!!
val toLanguages = viewModel.viewState.value!!.toLanguages!!
binding.fromLanguage.setAdapter( binding.fromLanguage.setAdapter(
ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, fromLanguages) ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, fromLanguages)
) )
@ -285,14 +267,14 @@ class TranslateActivity : BaseActivity() {
} }
private fun setItems() { private fun setItems() {
binding.fromLanguage.setSimpleItems(fromLanguages) binding.fromLanguage.setSimpleItems(viewModel.viewState.value!!.fromLanguages!!)
binding.toLanguage.setSimpleItems(toLanguages) binding.toLanguage.setSimpleItems(viewModel.viewState.value!!.toLanguages!!)
} }
companion object { companion object {
private val TAG = TranslateActivity::class.simpleName
private const val FROM_ID = "from" private const val FROM_ID = "from"
private const val FROM_LABEL = "fromLabel" private const val FROM_LABEL = "fromLabel"
private const val TO_LABEL = "toLabel" private const val TO_LABEL = "toLabel"
} }
} }

View File

@ -0,0 +1,79 @@
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) : ViewModel() {
@Inject
lateinit var userManager: UserManager
data class TranslateUiState(
var enableSpinners : Boolean = false,
var showProgressBar : Boolean = false,
var showTranslatedMessageContainer : Boolean = false,
var translatedMessageText : String? = null,
var originalMessageText : String? = null,
var errorOccurred : Boolean = false,
var toLanguages : Array<String>? = null,
var fromLanguages : Array<String>? = null
)
private val _viewState = MutableLiveData(TranslateUiState())
val viewState : LiveData<TranslateUiState>
get() = _viewState
fun translateMessage(toLanguage: String, fromLanguage: 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 }
repository.translateMessage(authorization, url,_viewState.value!!.originalMessageText!!,toLanguage,
calculatedFromLanguage)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(TranslateObserver())
}
inner class TranslateObserver() : Observer<String> {
override fun onSubscribe(d: Disposable) {
_viewState.value!!.enableSpinners = false
_viewState.value!!.showTranslatedMessageContainer = false
_viewState.value!!.showProgressBar = true
}
override fun onNext(translatedMessage: String) {
_viewState.value!!.showProgressBar = false
_viewState.value!!.showTranslatedMessageContainer = true
_viewState.value!!.translatedMessageText = translatedMessage
_viewState.value!!.enableSpinners = true
}
override fun onError(e: Throwable) {
_viewState.value!!.showProgressBar = false
_viewState.value!!.errorOccurred = true
_viewState.value!!.enableSpinners = true
Log.w(TAG, "Error while translating message", e)
}
override fun onComplete() {
// nothing?
}
}
companion object {
private val TAG = TranslateViewModel::class.simpleName
}
}