refactor: download wizard details form #163
5 changed files with 147 additions and 30 deletions
|
@ -1,10 +1,13 @@
|
|||
package im.angry.openeuicc.ui.wizard
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Patterns
|
||||
import android.text.InputType
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import im.angry.openeuicc.common.R
|
||||
|
@ -48,8 +51,13 @@ class DownloadWizardDetailsFragment : DownloadWizardActivity.DownloadWizardStepF
|
|||
matchingId = view.requireViewById(R.id.profile_download_code)
|
||||
confirmationCode = view.requireViewById(R.id.profile_download_confirmation_code)
|
||||
imei = view.requireViewById(R.id.profile_download_imei)
|
||||
smdp.editText!!.addTextChangedListener {
|
||||
updateInputCompleteness()
|
||||
smdp.editText!!.addTextChangedListener { updateInputCompleteness() }
|
||||
matchingId.editText!!.addTextChangedListener { updateInputCompleteness() }
|
||||
confirmationCode.editText!!.addTextChangedListener { updateInputCompleteness() }
|
||||
imei.editText!!.addTextChangedListener { updateInputCompleteness() }
|
||||
confirmationCode.setEndIconOnClickListener {
|
||||
onConfirmationCodeEndIconClick(confirmationCode)
|
||||
validate()
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
@ -69,7 +77,84 @@ class DownloadWizardDetailsFragment : DownloadWizardActivity.DownloadWizardStepF
|
|||
}
|
||||
|
||||
private fun updateInputCompleteness() {
|
||||
inputComplete = Patterns.DOMAIN_NAME.matcher(smdp.editText!!.text).matches()
|
||||
validate()
|
||||
val layouts = arrayOf(smdp, matchingId, confirmationCode, imei)
|
||||
for (layout in layouts) layout.isErrorEnabled = layout.error != null
|
||||
inputComplete = layouts.all { it.error == null }
|
||||
refreshButtons()
|
||||
}
|
||||
|
||||
private fun validate() {
|
||||
smdp.error = smdp.editText!!.text?.let {
|
||||
if (it.isEmpty()) return@let getString(R.string.download_wizard_details_error_address_required)
|
||||
if (it.contains("://")) return@let getString(R.string.download_wizard_details_error_cannot_url)
|
||||
val (host, port) = splitHostPort(it)
|
||||
if (isFQDN(host) && port in 1..65535) return@let null
|
||||
getString(R.string.download_wizard_details_error_address_incorrect_format)
|
||||
}
|
||||
matchingId.error = matchingId.editText!!.text?.let {
|
||||
if (isMatchingID(it)) return@let null
|
||||
getString(R.string.download_wizard_details_error_matching_id_incorrect_format)
|
||||
}
|
||||
confirmationCode.error = confirmationCode.editText!!.let {
|
||||
if (it.text.isEmpty()) return@let null
|
||||
val passed = when (it.inputType and EditorInfo.TYPE_MASK_CLASS) {
|
||||
EditorInfo.TYPE_CLASS_NUMBER -> it.text.all(Char::isDigit)
|
||||
EditorInfo.TYPE_CLASS_TEXT -> true
|
||||
else -> return@let null
|
||||
}
|
||||
if (passed) return@let null
|
||||
getString(R.string.download_wizard_details_error_confirmation_code_incorrect_format)
|
||||
}
|
||||
imei.error = imei.editText!!.text?.let {
|
||||
if (it.isEmpty()) return@let null
|
||||
if (it.length == 15 && it.all(Char::isDigit) && luhnValid(it)) return@let null
|
||||
getString(R.string.download_wizard_details_error_imei_incorrect_format)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onConfirmationCodeEndIconClick(layout: TextInputLayout) {
|
||||
val editText = layout.editText ?: return
|
||||
fun getDrawable(@DrawableRes resId: Int) =
|
||||
ContextCompat.getDrawable(requireActivity(), resId)
|
||||
editText.inputType = when (editText.inputType and EditorInfo.TYPE_MASK_CLASS) {
|
||||
EditorInfo.TYPE_CLASS_NUMBER -> InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
||||
EditorInfo.TYPE_CLASS_TEXT -> InputType.TYPE_CLASS_NUMBER
|
||||
else -> EditorInfo.TYPE_NULL
|
||||
}
|
||||
layout.endIconDrawable = when (editText.inputType and EditorInfo.TYPE_MASK_CLASS) {
|
||||
EditorInfo.TYPE_CLASS_NUMBER -> getDrawable(R.drawable.ic_format_number)
|
||||
EditorInfo.TYPE_CLASS_TEXT -> getDrawable(R.drawable.ic_format_text)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun splitHostPort(input: CharSequence): Pair<CharSequence, Int?> {
|
||||
val portIndex = input.lastIndexOf(':')
|
||||
if (portIndex == -1) return Pair(input, 443)
|
||||
return Pair(
|
||||
input.slice(0 until portIndex),
|
||||
input.slice(portIndex + 1 until input.length).toString().toIntOrNull()
|
||||
)
|
||||
}
|
||||
|
||||
private fun isFQDN(input: CharSequence): Boolean {
|
||||
if (input.isEmpty() || input.length > 255) return false
|
||||
if (!input.contains('.')) return false
|
||||
for (label in input.split('.')) {
|
||||
if (label.isEmpty() || label.length > 63) return false
|
||||
if (label.all { it.isLetterOrDigit() || it == '-' }) continue
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun isMatchingID(input: CharSequence) =
|
||||
input.isEmpty() || input.all { it.isLetterOrDigit() || it == '-' }
|
||||
|
||||
private fun luhnValid(input: CharSequence) = input
|
||||
.map(Char::digitToInt)
|
||||
.mapIndexed { index, digit -> if (index % 2 == 0) digit else digit * 2 }
|
||||
.sumOf { if (it > 9) it - 9 else it }
|
||||
.rem(10) == 0
|
11
app-common/src/main/res/drawable/ic_format_number.xml
Normal file
11
app-common/src/main/res/drawable/ic_format_number.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:alpha="0.8"
|
||||
android:tint="@android:color/white"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M7 15H5.5v-4.5H4V9h3v6zm6.5-1.5h-3v-1h2c0.55 0 1-0.45 1-1V10c0-0.55-0.45-1-1-1H9v1.5h3v1h-2c-0.55 0-1 0.45-1 1V15h4.5v-1.5zm6 0.5v-4c0-0.55-0.45-1-1-1H15v1.5h3v1h-2v1h2v1h-3V15h3.5c0.55 0 1-0.45 1-1z" />
|
||||
</vector>
|
11
app-common/src/main/res/drawable/ic_format_text.xml
Normal file
11
app-common/src/main/res/drawable/ic_format_text.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:alpha="0.8"
|
||||
android:tint="@android:color/white"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M21 11h-1.5v-0.5h-2v3h2V13H21v1c0 0.55-0.45 1-1 1h-3c-0.55 0-1-0.45-1-1v-4c0-0.55 0.45-1 1-1h3c0.55 0 1 0.45 1 1v1zM8 10v5H6.5v-1.5h-2V15H3v-5c0-0.55 0.45-1 1-1h3c0.55 0 1 0.45 1 1zm-1.5 0.5h-2V12h2v-1.5zm7 1.5c0.55 0 1 0.45 1 1v1c0 0.55-0.45 1-1 1h-4V9h4c0.55 0 1 0.45 1 1v1c0 0.55-0.45 1-1 1zM11 10.5v0.75h2V10.5h-2zm2 2.25h-2v0.75h2v-0.75z" />
|
||||
</vector>
|
|
@ -11,18 +11,18 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/download_wizard_details_title"
|
||||
android:text="@string/download_wizard_details"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:textSize="20sp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:layout_marginStart="60dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginEnd="60dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@string/download_wizard_details"
|
||||
android:textSize="20sp"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
|
@ -32,10 +32,11 @@
|
|||
android:hint="@string/profile_download_server">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:maxLines="1"
|
||||
android:inputType="text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
android:layout_height="match_parent"
|
||||
android:inputType="textVisiblePassword"
|
||||
android:maxLines="1"
|
||||
android:typeface="monospace" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
|
@ -43,14 +44,14 @@
|
|||
android:id="@+id/profile_download_code"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/profile_download_code"
|
||||
app:passwordToggleEnabled="true">
|
||||
android:hint="@string/profile_download_code">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:maxLines="1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:inputType="textPassword" />
|
||||
android:inputType="textVisiblePassword"
|
||||
android:maxLines="1"
|
||||
android:typeface="monospace" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
|
@ -59,13 +60,16 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/profile_download_confirmation_code"
|
||||
app:passwordToggleEnabled="true">
|
||||
app:endIconCheckable="true"
|
||||
app:endIconDrawable="@drawable/ic_format_number"
|
||||
app:endIconMode="custom">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:maxLines="1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:inputType="textPassword" />
|
||||
android:inputType="number"
|
||||
android:maxLines="1"
|
||||
android:typeface="monospace" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
|
@ -75,29 +79,29 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="15dp"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:hint="@string/profile_download_imei"
|
||||
app:passwordToggleEnabled="true">
|
||||
android:hint="@string/profile_download_imei">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:maxLines="1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:inputType="numberPassword" />
|
||||
android:inputType="number"
|
||||
android:maxLines="1"
|
||||
android:typeface="monospace" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<androidx.constraintlayout.helper.widget.Flow
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginHorizontal="20dp"
|
||||
android:orientation="vertical"
|
||||
app:constraint_referenced_ids="profile_download_server,profile_download_code,profile_download_confirmation_code,profile_download_imei"
|
||||
app:flow_verticalGap="16dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/download_wizard_details_title"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constrainedWidth="true" />
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/download_wizard_details_title" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
|
|
@ -81,6 +81,12 @@
|
|||
<string name="download_wizard_method_clipboard">Load from Clipboard</string>
|
||||
<string name="download_wizard_method_manual">Enter manually</string>
|
||||
<string name="download_wizard_details">Input or confirm details for downloading your eSIM:</string>
|
||||
<string name="download_wizard_details_error_address_required">Server address is required</string>
|
||||
<string name="download_wizard_details_error_cannot_url">Server address not is URL</string>
|
||||
<string name="download_wizard_details_error_address_incorrect_format">Incorrect Server address</string>
|
||||
<string name="download_wizard_details_error_matching_id_incorrect_format">Incorrect Matching ID format</string>
|
||||
<string name="download_wizard_details_error_confirmation_code_incorrect_format">Incorrect Confirmation Code format</string>
|
||||
<string name="download_wizard_details_error_imei_incorrect_format">Incorrect IMEI format</string>
|
||||
<string name="download_wizard_progress">Downloading your eSIM…</string>
|
||||
<string name="download_wizard_progress_step_preparing">Preparing</string>
|
||||
<string name="download_wizard_progress_step_connecting">Establishing connection to server</string>
|
||||
|
|
Loading…
Add table
Reference in a new issue