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.Toast
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import im.angry.openeuicc.common.R
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.launch
class ProfileDeleteFragment : DialogFragment(), EuiccChannelFragmentMarker {
class ProfileDeleteFragment : BaseMaterialDialogFragment(), EuiccChannelFragmentMarker {
companion object {
const val TAG = "ProfileDeleteFragment"
private const val FIELD_ICCID = "iccid"
@ -92,11 +91,7 @@ class ProfileDeleteFragment : DialogFragment(), EuiccChannelFragmentMarker {
ensureEuiccChannelManager()
euiccChannelManagerService.waitForForegroundTask()
euiccChannelManagerService.launchProfileDeleteTask(slotId, portId, iccid).onStart {
if (parentFragment is EuiccProfilesChangedListener) {
// 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()
}
notifyEuiccProfilesChanged()
try {
dismiss()

View file

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