refactor: download wizard details form #163

Closed
septs wants to merge 20 commits from septs:stricted-imei into master
5 changed files with 147 additions and 30 deletions

View file

@ -1,10 +1,13 @@
package im.angry.openeuicc.ui.wizard package im.angry.openeuicc.ui.wizard
import android.os.Bundle import android.os.Bundle
import android.util.Patterns import android.text.InputType
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.view.inputmethod.EditorInfo
import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat
import androidx.core.widget.addTextChangedListener import androidx.core.widget.addTextChangedListener
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
@ -48,8 +51,13 @@ class DownloadWizardDetailsFragment : DownloadWizardActivity.DownloadWizardStepF
matchingId = view.requireViewById(R.id.profile_download_code) matchingId = view.requireViewById(R.id.profile_download_code)
confirmationCode = view.requireViewById(R.id.profile_download_confirmation_code) confirmationCode = view.requireViewById(R.id.profile_download_confirmation_code)
imei = view.requireViewById(R.id.profile_download_imei) imei = view.requireViewById(R.id.profile_download_imei)
smdp.editText!!.addTextChangedListener { smdp.editText!!.addTextChangedListener { updateInputCompleteness() }
updateInputCompleteness() matchingId.editText!!.addTextChangedListener { updateInputCompleteness() }
confirmationCode.editText!!.addTextChangedListener { updateInputCompleteness() }
imei.editText!!.addTextChangedListener { updateInputCompleteness() }
confirmationCode.setEndIconOnClickListener {
onConfirmationCodeEndIconClick(confirmationCode)
validate()
} }
return view return view
} }
@ -69,7 +77,84 @@ class DownloadWizardDetailsFragment : DownloadWizardActivity.DownloadWizardStepF
} }
private fun updateInputCompleteness() { 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() 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

View 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>

View 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>

View file

@ -11,18 +11,18 @@
<TextView <TextView
android:id="@+id/download_wizard_details_title" android:id="@+id/download_wizard_details_title"
android:text="@string/download_wizard_details"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="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_marginStart="60dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="60dp" android:layout_marginEnd="60dp"
app:layout_constraintStart_toStartOf="parent" android:layout_marginBottom="20dp"
app:layout_constraintEnd_toEndOf="parent" android:gravity="center_horizontal"
android:text="@string/download_wizard_details"
android:textSize="20sp"
app:layout_constrainedWidth="true" app:layout_constrainedWidth="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
@ -32,10 +32,11 @@
android:hint="@string/profile_download_server"> android:hint="@string/profile_download_server">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:maxLines="1"
android:inputType="text"
android:layout_width="match_parent" 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> </com.google.android.material.textfield.TextInputLayout>
@ -43,14 +44,14 @@
android:id="@+id/profile_download_code" android:id="@+id/profile_download_code"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/profile_download_code" android:hint="@string/profile_download_code">
app:passwordToggleEnabled="true">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:maxLines="1"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="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> </com.google.android.material.textfield.TextInputLayout>
@ -59,13 +60,16 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/profile_download_confirmation_code" 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 <com.google.android.material.textfield.TextInputEditText
android:maxLines="1"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="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> </com.google.android.material.textfield.TextInputLayout>
@ -75,29 +79,29 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="15dp" android:layout_marginTop="15dp"
android:layout_marginBottom="6dp" android:layout_marginBottom="6dp"
android:hint="@string/profile_download_imei" android:hint="@string/profile_download_imei">
app:passwordToggleEnabled="true">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:maxLines="1"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="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> </com.google.android.material.textfield.TextInputLayout>
<androidx.constraintlayout.helper.widget.Flow <androidx.constraintlayout.helper.widget.Flow
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginHorizontal="20dp" 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:constraint_referenced_ids="profile_download_server,profile_download_code,profile_download_confirmation_code,profile_download_imei"
app:flow_verticalGap="16dp" app:flow_verticalGap="16dp"
app:layout_constraintStart_toStartOf="parent" app:layout_constrainedWidth="true"
app:layout_constraintTop_toBottomOf="@id/download_wizard_details_title"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="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> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -81,6 +81,12 @@
<string name="download_wizard_method_clipboard">Load from Clipboard</string> <string name="download_wizard_method_clipboard">Load from Clipboard</string>
<string name="download_wizard_method_manual">Enter manually</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">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">Downloading your eSIM…</string>
<string name="download_wizard_progress_step_preparing">Preparing</string> <string name="download_wizard_progress_step_preparing">Preparing</string>
<string name="download_wizard_progress_step_connecting">Establishing connection to server</string> <string name="download_wizard_progress_step_connecting">Establishing connection to server</string>