WIP: refactor: profile rename #121

Closed
septs wants to merge 9 commits from septs:profile-rename-state-restore into master
4 changed files with 97 additions and 82 deletions

View file

@ -6,7 +6,6 @@ import android.text.Editable
import android.widget.EditText import android.widget.EditText
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import im.angry.openeuicc.common.R import im.angry.openeuicc.common.R
import im.angry.openeuicc.service.EuiccChannelManagerService.Companion.waitDone import im.angry.openeuicc.service.EuiccChannelManagerService.Companion.waitDone
@ -14,7 +13,7 @@ import im.angry.openeuicc.util.*
import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class ProfileDeleteFragment : DialogFragment(), EuiccChannelFragmentMarker { class ProfileDeleteFragment : BaseMaterialDialogFragment(), EuiccChannelFragmentMarker {
companion object { companion object {
const val TAG = "ProfileDeleteFragment" const val TAG = "ProfileDeleteFragment"
private const val FIELD_ICCID = "iccid" private const val FIELD_ICCID = "iccid"
@ -92,11 +91,7 @@ class ProfileDeleteFragment : DialogFragment(), EuiccChannelFragmentMarker {
ensureEuiccChannelManager() ensureEuiccChannelManager()
euiccChannelManagerService.waitForForegroundTask() euiccChannelManagerService.waitForForegroundTask()
euiccChannelManagerService.launchProfileDeleteTask(slotId, portId, iccid).onStart { euiccChannelManagerService.launchProfileDeleteTask(slotId, portId, iccid).onStart {
if (parentFragment is EuiccProfilesChangedListener) { notifyEuiccProfilesChanged()
// Trigger a refresh in the parent fragment -- it should wait until
// any foreground task is completed before actually doing a refresh
(parentFragment as EuiccProfilesChangedListener).onEuiccProfilesChanged()
}
try { try {
dismiss() dismiss()

View file

@ -1,13 +1,14 @@
package im.angry.openeuicc.ui package im.angry.openeuicc.ui
import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.EditText
import android.widget.ProgressBar import android.widget.ProgressBar
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.core.widget.addTextChangedListener
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import im.angry.openeuicc.common.R import im.angry.openeuicc.common.R
@ -19,37 +20,62 @@ import net.typeblog.lpac_jni.LocalProfileAssistant
class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragmentMarker { class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragmentMarker {
companion object { companion object {
const val TAG = "ProfileRenameFragment" const val TAG = "ProfileRenameFragment"
const val FIELD_ICCID = "iccid"
const val FIELD_CURRENT_NAME = "currentName"
const val FIELD_EDITED_NAME = "editedName"
fun newInstance(slotId: Int, portId: Int, iccid: String, currentName: String): ProfileRenameFragment { fun newInstance(slotId: Int, portId: Int, iccid: String, currentName: String) =
val instance = newInstanceEuicc(ProfileRenameFragment::class.java, slotId, portId) newInstanceEuicc(ProfileRenameFragment::class.java, slotId, portId) {
instance.requireArguments().apply { putString(FIELD_ICCID, iccid)
putString("iccid", iccid) putString(FIELD_CURRENT_NAME, currentName)
putString("currentName", currentName)
} }
return instance
}
} }
private lateinit var toolbar: Toolbar private lateinit var toolbar: Toolbar
private lateinit var profileRenameNewName: TextInputLayout private lateinit var editText: EditText
private lateinit var progress: ProgressBar private lateinit var progress: ProgressBar
private val iccid by lazy {
requireArguments().getString(FIELD_ICCID)!!
}
private val currentName by lazy {
requireArguments().getString(FIELD_CURRENT_NAME)!!
}
private val editedName: String
get() = editText.text.toString().trim()
private var renaming = false private var renaming = false
set(value) {
progress.isIndeterminate = value
progress.visibility = if (value) View.VISIBLE else View.GONE
field = value
}
private var toast: Toast? = null
set(value) {
field?.cancel()
field = value
field?.show()
}
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View = inflater.inflate(R.layout.fragment_profile_rename, container, false).apply {
val view = inflater.inflate(R.layout.fragment_profile_rename, container, false) toolbar = requireViewById<Toolbar>(R.id.toolbar).apply {
inflateMenu(R.menu.fragment_profile_rename)
toolbar = view.requireViewById(R.id.toolbar) }
profileRenameNewName = view.requireViewById(R.id.profile_rename_new_name) editText = requireViewById<TextInputLayout>(R.id.profile_rename_new_name).editText!!.apply {
progress = view.requireViewById(R.id.progress) addTextChangedListener {
val isUnchanged = currentName == editedName
toolbar.inflateMenu(R.menu.fragment_profile_rename) dialog!!.setCancelable(isUnchanged)
dialog!!.setCanceledOnTouchOutside(isUnchanged)
return view }
}
progress = requireViewById(R.id.progress)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -60,15 +86,24 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment
if (!renaming) dismiss() if (!renaming) dismiss()
} }
setOnMenuItemClickListener { setOnMenuItemClickListener {
if (!renaming) rename() if (!renaming) lifecycleScope.launch {
renaming = true
invokeRename()
renaming = false
}
true true
} }
} }
} }
override fun onStart() { override fun onSaveInstanceState(outState: Bundle) {
super.onStart() super.onSaveInstanceState(outState)
profileRenameNewName.editText!!.setText(requireArguments().getString("currentName")) outState.putString(FIELD_EDITED_NAME, editedName)
}
override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState)
editText.setText(savedInstanceState?.getString(FIELD_EDITED_NAME) ?: currentName)
} }
override fun onResume() { override fun onResume() {
@ -76,63 +111,41 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment
setWidthPercent(95) setWidthPercent(95)
} }
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { private suspend fun invokeRename() {
return super.onCreateDialog(savedInstanceState).also { toast = null
it.setCanceledOnTouchOutside(false)
}
}
private fun showErrorAndCancel(errorStrRes: Int) { ensureEuiccChannelManager()
Toast.makeText( euiccChannelManagerService.waitForForegroundTask()
requireContext(),
errorStrRes,
Toast.LENGTH_LONG
).show()
renaming = false val throwable = euiccChannelManagerService
progress.visibility = View.GONE .launchProfileRenameTask(slotId, portId, iccid, editedName)
} .waitDone()
private fun rename() { val toastResId = when (throwable) {
renaming = true is LocalProfileAssistant.ProfileNameTooLongException ->
progress.isIndeterminate = true R.string.profile_rename_too_long
progress.visibility = View.VISIBLE is LocalProfileAssistant.ProfileNameIsInvalidUTF8Exception ->
R.string.profile_rename_encoding_error
lifecycleScope.launch { is Throwable ->
ensureEuiccChannelManager() R.string.profile_rename_failure
euiccChannelManagerService.waitForForegroundTask() else -> {
val res = euiccChannelManagerService.launchProfileRenameTask( notifyEuiccProfilesChanged()
slotId, try {
portId, dismiss()
requireArguments().getString("iccid")!!, } catch (e: IllegalStateException) {
profileRenameNewName.editText!!.text.toString().trim() // Ignored
).waitDone()
when (res) {
is LocalProfileAssistant.ProfileNameTooLongException -> {
showErrorAndCancel(R.string.profile_rename_too_long)
} }
when {
is LocalProfileAssistant.ProfileNameIsInvalidUTF8Exception -> { editedName.isEmpty() ->
showErrorAndCancel(R.string.profile_rename_encoding_error) R.string.profile_rename_restore_defaults
} editedName == currentName ->
R.string.profile_rename_unchanged
is Throwable -> { else -> null
showErrorAndCancel(R.string.profile_rename_failure)
}
else -> {
if (parentFragment is EuiccProfilesChangedListener) {
(parentFragment as EuiccProfilesChangedListener).onEuiccProfilesChanged()
}
try {
dismiss()
} catch (e: IllegalStateException) {
// Ignored
}
} }
} }
} }
if (toastResId != null) toast = Toast
.makeText(requireContext(), toastResId, Toast.LENGTH_LONG)
} }
} }

View file

@ -13,7 +13,7 @@ interface EuiccChannelFragmentMarker: OpenEuiccContextMarker
// in the definition of an interface, so the only way is to limit where the extension functions // in the definition of an interface, so the only way is to limit where the extension functions
// can be applied. // can be applied.
fun <T> newInstanceEuicc(clazz: Class<T>, slotId: Int, portId: Int, addArguments: Bundle.() -> Unit = {}): T where T: Fragment, T: EuiccChannelFragmentMarker { fun <T> newInstanceEuicc(clazz: Class<T>, slotId: Int, portId: Int, addArguments: Bundle.() -> Unit = {}): T where T: Fragment, T: EuiccChannelFragmentMarker {
val instance = clazz.newInstance() val instance = clazz.getDeclaredConstructor().newInstance()
instance.arguments = Bundle().apply { instance.arguments = Bundle().apply {
putInt("slotId", slotId) putInt("slotId", slotId)
putInt("portId", portId) putInt("portId", portId)
@ -37,6 +37,11 @@ val <T> T.euiccChannelManager: EuiccChannelManager where T: Fragment, T: OpenEui
val <T> T.euiccChannelManagerService: EuiccChannelManagerService where T: Fragment, T: OpenEuiccContextMarker val <T> T.euiccChannelManagerService: EuiccChannelManagerService where T: Fragment, T: OpenEuiccContextMarker
get() = (requireActivity() as BaseEuiccAccessActivity).euiccChannelManagerService get() = (requireActivity() as BaseEuiccAccessActivity).euiccChannelManagerService
fun <T> T.notifyEuiccProfilesChanged() where T : Fragment, T : OpenEuiccContextMarker {
val fragment = parentFragment
if (fragment is EuiccProfilesChangedListener) fragment.onEuiccProfilesChanged()
}
suspend fun <T, R> T.withEuiccChannel(fn: suspend (EuiccChannel) -> R): R where T : Fragment, T : EuiccChannelFragmentMarker { suspend fun <T, R> T.withEuiccChannel(fn: suspend (EuiccChannel) -> R): R where T : Fragment, T : EuiccChannelFragmentMarker {
ensureEuiccChannelManager() ensureEuiccChannelManager()
return euiccChannelManager.withEuiccChannel(slotId, portId, fn) return euiccChannelManager.withEuiccChannel(slotId, portId, fn)

View file

@ -96,6 +96,8 @@
<string name="logs_saved_message">Logs have been saved to the selected path. Would you like to share the log through another app?</string> <string name="logs_saved_message">Logs have been saved to the selected path. Would you like to share the log through another app?</string>
<string name="profile_rename_new_name">New nickname</string> <string name="profile_rename_new_name">New nickname</string>
<string name="profile_rename_unchanged">The profile nickname is unchanged</string>
<string name="profile_rename_restore_defaults">The profile nickname restored to defaults</string>
<string name="profile_rename_encoding_error">Failed to encode nickname as UTF-8</string> <string name="profile_rename_encoding_error">Failed to encode nickname as UTF-8</string>
<string name="profile_rename_too_long">Nickname is too long</string> <string name="profile_rename_too_long">Nickname is too long</string>
<string name="profile_rename_failure">Unknown failure when renaming profile</string> <string name="profile_rename_failure">Unknown failure when renaming profile</string>