feat: profile rename more toast hints #111
4 changed files with 119 additions and 27 deletions
|
@ -1,38 +1,64 @@
|
|||
package im.angry.openeuicc.ui
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
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.view.isVisible
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import im.angry.openeuicc.common.R
|
||||
import im.angry.openeuicc.service.EuiccChannelManagerService.Companion.waitDone
|
||||
import im.angry.openeuicc.util.*
|
||||
import kotlinx.coroutines.launch
|
||||
import net.typeblog.lpac_jni.LocalProfileAssistant.ProfileNicknameException as NicknameException
|
||||
import net.typeblog.lpac_jni.LocalProfileAssistant.ProfileNicknameException.Kind as SetFailedKind
|
||||
|
||||
class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragmentMarker {
|
||||
companion object {
|
||||
const val TAG = "ProfileRenameFragment"
|
||||
const val FIELD_ICCID = "iccid"
|
||||
const val FIELD_CURRENT_NAME = "currentName"
|
||||
|
||||
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)
|
||||
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 var toast: Toast? = null
|
||||
set(toast) {
|
||||
field?.cancel()
|
||||
field = toast
|
||||
field?.show()
|
||||
}
|
||||
|
||||
private val editedName: String
|
||||
get() = editText.text.toString().trim()
|
||||
|
||||
private val iccid by lazy {
|
||||
requireArguments().getString(FIELD_ICCID)!!
|
||||
}
|
||||
|
||||
private val currentName by lazy {
|
||||
requireArguments().getString(FIELD_CURRENT_NAME)!!
|
||||
}
|
||||
|
||||
private var renaming = false
|
||||
|
||||
override fun onCreateView(
|
||||
|
@ -40,14 +66,12 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment
|
|||
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)
|
||||
|
||||
val view = inflater.inflate(R.layout.fragment_profile_rename, container, false).apply {
|
||||
toolbar = requireViewById(R.id.toolbar)
|
||||
editText = requireViewById<TextInputLayout>(R.id.profile_rename_new_name).editText!!
|
||||
progress = requireViewById(R.id.progress)
|
||||
}
|
||||
toolbar.inflateMenu(R.menu.fragment_profile_rename)
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
|
@ -63,11 +87,16 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment
|
|||
true
|
||||
}
|
||||
}
|
||||
editText.addTextChangedListener {
|
||||
val isUnchanged = it.toString().trim() == currentName
|
||||
dialog!!.setCancelable(isUnchanged)
|
||||
dialog!!.setCanceledOnTouchOutside(isUnchanged)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
profileRenameNewName.editText!!.setText(requireArguments().getString("currentName"))
|
||||
editText.setText(currentName)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
@ -75,6 +104,15 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment
|
|||
setWidthPercent(95)
|
||||
}
|
||||
|
||||
override fun onCancel(dialog: DialogInterface) {
|
||||
super.onCancel(dialog)
|
||||
toast = Toast.makeText(
|
||||
requireContext(),
|
||||
R.string.toast_profile_name_is_unchanged,
|
||||
Toast.LENGTH_LONG
|
||||
)
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return super.onCreateDialog(savedInstanceState).also {
|
||||
it.setCanceledOnTouchOutside(false)
|
||||
|
@ -82,25 +120,53 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment
|
|||
}
|
||||
|
||||
private fun rename() {
|
||||
val name = profileRenameNewName.editText!!.text.toString().trim()
|
||||
if (name.length >= 64) {
|
||||
Toast.makeText(context, R.string.toast_profile_name_too_long, Toast.LENGTH_LONG).show()
|
||||
return
|
||||
toast = when {
|
||||
editedName.isEmpty() -> Toast.makeText(
|
||||
requireContext(),
|
||||
R.string.toast_profile_name_restore_defaults,
|
||||
Toast.LENGTH_LONG
|
||||
)
|
||||
|
||||
editedName == currentName -> Toast.makeText(
|
||||
requireContext(),
|
||||
R.string.toast_profile_name_is_unchanged,
|
||||
Toast.LENGTH_LONG
|
||||
)
|
||||
|
||||
else -> Toast.makeText(
|
||||
requireContext(),
|
||||
getString(R.string.toast_profile_name_changed, currentName, editedName),
|
||||
Toast.LENGTH_LONG
|
||||
)
|
||||
}
|
||||
|
||||
renaming = true
|
||||
progress.isIndeterminate = true
|
||||
progress.visibility = View.VISIBLE
|
||||
progress.isVisible = true
|
||||
|
||||
lifecycleScope.launch {
|
||||
ensureEuiccChannelManager()
|
||||
euiccChannelManagerService.waitForForegroundTask()
|
||||
euiccChannelManagerService.launchProfileRenameTask(
|
||||
slotId,
|
||||
portId,
|
||||
requireArguments().getString("iccid")!!,
|
||||
name
|
||||
).waitDone()
|
||||
try {
|
||||
if (editedName != currentName) euiccChannelManagerService
|
||||
.launchProfileRenameTask(slotId, portId, iccid, editedName)
|
||||
.waitDone()
|
||||
} catch (e: NicknameException) {
|
||||
toast = when (e.kind) {
|
||||
SetFailedKind.NicknameTooLong -> Toast.makeText(
|
||||
requireContext(),
|
||||
R.string.toast_profile_name_too_long,
|
||||
Toast.LENGTH_LONG
|
||||
)
|
||||
|
||||
SetFailedKind.InvalidUTF8Sequence -> Toast.makeText(
|
||||
requireContext(),
|
||||
R.string.toast_profile_name_encode_failed,
|
||||
Toast.LENGTH_LONG
|
||||
)
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
|
||||
if (parentFragment is EuiccProfilesChangedListener) {
|
||||
(parentFragment as EuiccProfilesChangedListener).onEuiccProfilesChanged()
|
||||
|
|
|
@ -28,7 +28,11 @@
|
|||
<string name="switch_did_not_refresh">The operation was successful, but your phone\'s modem refused to refresh. You might need to toggle airplane mode or reboot in order to use the new profile.</string>
|
||||
|
||||
<string name="toast_profile_enable_failed">Cannot switch to new eSIM profile.</string>
|
||||
<string name="toast_profile_name_changed">The profile name will be updated from \'%s\' to \'%s\'</string>
|
||||
<string name="toast_profile_name_is_unchanged">Nickname has is unchanged</string>
|
||||
|
||||
<string name="toast_profile_name_too_long">Nickname cannot be longer than 64 characters</string>
|
||||
<string name="toast_profile_name_encode_failed">could not be encoded as UTF-8</string>
|
||||
PeterCxy
commented
could not be encoded as UTF-8 could not be encoded as UTF-8
|
||||
<string name="toast_profile_name_restore_defaults">This profile name will be restored to default</string>
|
||||
PeterCxy
commented
delete "the" delete "the"
|
||||
<string name="toast_profile_delete_confirm_text_mismatched">Confirmation string mismatch</string>
|
||||
<string name="toast_iccid_copied">ICCID copied to clipboard</string>
|
||||
|
||||
|
|
|
@ -12,6 +12,14 @@ interface LocalProfileAssistant {
|
|||
val lastApduException: Exception?,
|
||||
) : Exception("Failed to download profile")
|
||||
|
||||
data class ProfileNicknameException(val kind: Kind) :
|
||||
Exception("Failed to set nickname profile") {
|
||||
enum class Kind {
|
||||
NicknameTooLong,
|
||||
InvalidUTF8Sequence
|
||||
}
|
||||
}
|
||||
|
||||
val valid: Boolean
|
||||
val profiles: List<LocalProfileInfo>
|
||||
val notifications: List<LocalProfileNotification>
|
||||
|
@ -40,9 +48,7 @@ interface LocalProfileAssistant {
|
|||
|
||||
fun euiccMemoryReset()
|
||||
|
||||
fun setNickname(
|
||||
iccid: String, nickname: String
|
||||
): Boolean
|
||||
fun setNickname(iccid: String, nickname: String): Boolean
|
||||
|
||||
fun close()
|
||||
}
|
|
@ -239,8 +239,24 @@ class LocalProfileAssistantImpl(
|
|||
} == 0
|
||||
|
||||
@Synchronized
|
||||
override fun setNickname(iccid: String, nickname: String): Boolean =
|
||||
LpacJni.es10cSetNickname(contextHandle, iccid, nickname) == 0
|
||||
override fun setNickname(iccid: String, nickname: String): Boolean {
|
||||
try {
|
||||
// SGP.22 v2.2.2 (Page 205 of 268)
|
||||
// https://www.gsma.com/solutions-and-impact/technologies/esim/wp-content/uploads/2020/06/SGP.22-v2.2.2.pdf
|
||||
// ASN.1 definition is `profileNickname [16] UTF8String (SIZE(0..64))`
|
||||
val length = nickname.toByteArray(Charsets.UTF_8).size
|
||||
if (length > 64) {
|
||||
throw LocalProfileAssistant.ProfileNicknameException(
|
||||
kind = LocalProfileAssistant.ProfileNicknameException.Kind.NicknameTooLong
|
||||
)
|
||||
}
|
||||
} catch (e: CharacterCodingException) {
|
||||
throw LocalProfileAssistant.ProfileNicknameException(
|
||||
kind = LocalProfileAssistant.ProfileNicknameException.Kind.InvalidUTF8Sequence
|
||||
)
|
||||
}
|
||||
return LpacJni.es10cSetNickname(contextHandle, iccid, nickname) == 0
|
||||
}
|
||||
|
||||
override fun euiccMemoryReset() {
|
||||
LpacJni.es10cEuiccMemoryReset(contextHandle)
|
||||
|
|
Loading…
Add table
Reference in a new issue
is unchanged