diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileDeleteFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileDeleteFragment.kt index 7f82f22..22bdc74 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileDeleteFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileDeleteFragment.kt @@ -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() diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt index e3f2d8d..65698e6 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt @@ -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(R.id.toolbar).apply { + inflateMenu(R.menu.fragment_profile_rename) + } + editText = requireViewById(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) } } \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/util/EuiccChannelFragmentUtils.kt b/app-common/src/main/java/im/angry/openeuicc/util/EuiccChannelFragmentUtils.kt index 3f3c4ee..fff0e69 100644 --- a/app-common/src/main/java/im/angry/openeuicc/util/EuiccChannelFragmentUtils.kt +++ b/app-common/src/main/java/im/angry/openeuicc/util/EuiccChannelFragmentUtils.kt @@ -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 newInstanceEuicc(clazz: Class, 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.euiccChannelManager: EuiccChannelManager where T: Fragment, T: OpenEui val T.euiccChannelManagerService: EuiccChannelManagerService where T: Fragment, T: OpenEuiccContextMarker get() = (requireActivity() as BaseEuiccAccessActivity).euiccChannelManagerService +fun T.notifyEuiccProfilesChanged() where T : Fragment, T : OpenEuiccContextMarker { + val fragment = parentFragment + if (fragment is EuiccProfilesChangedListener) fragment.onEuiccProfilesChanged() +} + suspend fun T.withEuiccChannel(fn: suspend (EuiccChannel) -> R): R where T : Fragment, T : EuiccChannelFragmentMarker { ensureEuiccChannelManager() return euiccChannelManager.withEuiccChannel(slotId, portId, fn) diff --git a/app-common/src/main/res/values/strings.xml b/app-common/src/main/res/values/strings.xml index 95ea261..3ac3e0a 100644 --- a/app-common/src/main/res/values/strings.xml +++ b/app-common/src/main/res/values/strings.xml @@ -96,6 +96,8 @@ Logs have been saved to the selected path. Would you like to share the log through another app? New nickname + The profile nickname is unchanged + The profile nickname restored to defaults Failed to encode nickname as UTF-8 Nickname is too long Unknown failure when renaming profile