浏览代码

ui: wizard: Implement input by scanning / gallery

Peter Cai 1 年之前
父节点
当前提交
8c73615fbb

+ 10 - 2
app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt

@@ -16,7 +16,11 @@ import im.angry.openeuicc.util.*
 
 class DownloadWizardActivity: BaseEuiccAccessActivity() {
     data class DownloadWizardState(
-        var selectedLogicalSlot: Int
+        var selectedLogicalSlot: Int,
+        var smdp: String,
+        var matchingId: String,
+        var confirmationCode: String,
+        var imei: String,
     )
 
     private lateinit var state: DownloadWizardState
@@ -39,7 +43,11 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() {
         })
 
         state = DownloadWizardState(
-            intent.getIntExtra("selectedLogicalSlot", 0)
+            intent.getIntExtra("selectedLogicalSlot", 0),
+            "",
+            "",
+            "",
+            ""
         )
 
         progressBar = requireViewById(R.id.progress)

+ 32 - 1
app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardDetailsFragment.kt

@@ -1,17 +1,27 @@
 package im.angry.openeuicc.ui.wizard
 
 import android.os.Bundle
+import android.util.Patterns
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
+import androidx.core.widget.addTextChangedListener
+import com.google.android.material.textfield.TextInputLayout
 import im.angry.openeuicc.common.R
 
 class DownloadWizardDetailsFragment : DownloadWizardActivity.DownloadWizardStepFragment() {
+    private var inputComplete = false
+
     override val hasNext: Boolean
-        get() = false
+        get() = inputComplete
     override val hasPrev: Boolean
         get() = true
 
+    private lateinit var smdp: TextInputLayout
+    private lateinit var matchingId: TextInputLayout
+    private lateinit var confirmationCode: TextInputLayout
+    private lateinit var imei: TextInputLayout
+
     override fun createNextFragment(): DownloadWizardActivity.DownloadWizardStepFragment? {
         TODO("Not yet implemented")
     }
@@ -25,6 +35,27 @@ class DownloadWizardDetailsFragment : DownloadWizardActivity.DownloadWizardStepF
         savedInstanceState: Bundle?
     ): View? {
         val view = inflater.inflate(R.layout.fragment_download_details, container, false)
+        smdp = view.requireViewById(R.id.profile_download_server)
+        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()
+        }
         return view
     }
+
+    override fun onStart() {
+        super.onStart()
+        smdp.editText!!.setText(state.smdp)
+        matchingId.editText!!.setText(state.matchingId)
+        confirmationCode.editText!!.setText(state.confirmationCode)
+        imei.editText!!.setText(state.imei)
+        updateInputCompleteness()
+    }
+
+    private fun updateInputCompleteness() {
+        inputComplete = Patterns.DOMAIN_NAME.matcher(smdp.editText!!.text).matches()
+        refreshButtons()
+    }
 }

+ 53 - 5
app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardMethodSelectFragment.kt

@@ -1,16 +1,25 @@
 package im.angry.openeuicc.ui.wizard
 
+import android.graphics.BitmapFactory
 import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import android.widget.ImageView
 import android.widget.TextView
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.lifecycle.lifecycleScope
 import androidx.recyclerview.widget.DividerItemDecoration
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
 import androidx.recyclerview.widget.RecyclerView.ViewHolder
+import com.journeyapps.barcodescanner.ScanContract
+import com.journeyapps.barcodescanner.ScanOptions
 import im.angry.openeuicc.common.R
+import im.angry.openeuicc.util.*
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 
 class DownloadWizardMethodSelectFragment : DownloadWizardActivity.DownloadWizardStepFragment() {
     data class DownloadMethod(
@@ -19,12 +28,44 @@ class DownloadWizardMethodSelectFragment : DownloadWizardActivity.DownloadWizard
         val onClick: () -> Unit
     )
 
+    // TODO: Maybe we should find a better barcode scanner (or an external one?)
+    private val barcodeScannerLauncher = registerForActivityResult(ScanContract()) { result ->
+        result.contents?.let { content ->
+            processLpaString(content)
+        }
+    }
+
+    private val gallerySelectorLauncher =
+        registerForActivityResult(ActivityResultContracts.GetContent()) { result ->
+            if (result == null) return@registerForActivityResult
+
+            lifecycleScope.launch(Dispatchers.IO) {
+                runCatching {
+                    requireContext().contentResolver.openInputStream(result)?.let { input ->
+                        val bmp = BitmapFactory.decodeStream(input)
+                        input.close()
+
+                        decodeQrFromBitmap(bmp)?.let {
+                            withContext(Dispatchers.Main) {
+                                processLpaString(it)
+                            }
+                        }
+
+                        bmp.recycle()
+                    }
+                }
+            }
+        }
+
     val downloadMethods = arrayOf(
         DownloadMethod(R.drawable.ic_scan_black, R.string.download_wizard_method_qr_code) {
-
+            barcodeScannerLauncher.launch(ScanOptions().apply {
+                setDesiredBarcodeFormats(ScanOptions.QR_CODE)
+                setOrientationLocked(false)
+            })
         },
         DownloadMethod(R.drawable.ic_gallery_black, R.string.download_wizard_method_gallery) {
-
+            gallerySelectorLauncher.launch("image/*")
         },
         DownloadMethod(R.drawable.ic_edit, R.string.download_wizard_method_manual) {
             gotoNextFragment(DownloadWizardDetailsFragment())
@@ -36,9 +77,8 @@ class DownloadWizardMethodSelectFragment : DownloadWizardActivity.DownloadWizard
     override val hasPrev: Boolean
         get() = true
 
-    override fun createNextFragment(): DownloadWizardActivity.DownloadWizardStepFragment? {
-        TODO("Not yet implemented")
-    }
+    override fun createNextFragment(): DownloadWizardActivity.DownloadWizardStepFragment? =
+        null
 
     override fun createPrevFragment(): DownloadWizardActivity.DownloadWizardStepFragment =
         DownloadWizardSlotSelectFragment()
@@ -62,6 +102,14 @@ class DownloadWizardMethodSelectFragment : DownloadWizardActivity.DownloadWizard
         return view
     }
 
+    private fun processLpaString(s: String) {
+        val components = s.split("$")
+        if (components.size < 3 || components[0] != "LPA:1") return
+        state.smdp = components[1]
+        state.matchingId = components[2]
+        gotoNextFragment(DownloadWizardDetailsFragment())
+    }
+
     private class DownloadMethodViewHolder(private val root: View) : ViewHolder(root) {
         private val icon = root.requireViewById<ImageView>(R.id.download_method_icon)
         private val title = root.requireViewById<TextView>(R.id.download_method_title)

+ 12 - 1
app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt

@@ -27,6 +27,7 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt
         val portId: Int,
         val eID: String,
         val freeSpace: Int,
+        val imei: String,
         val enabledProfileName: String?
     )
 
@@ -65,7 +66,7 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt
         }
     }
 
-    @SuppressLint("NotifyDataSetChanged")
+    @SuppressLint("NotifyDataSetChanged", "MissingPermission")
     private suspend fun init() {
         ensureEuiccChannelManager()
         showProgressBar(-1)
@@ -78,6 +79,11 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt
                     channel.portId,
                     channel.lpa.eID,
                     channel.lpa.euiccInfo2?.freeNvram ?: 0,
+                    try {
+                        telephonyManager.getImei(channel.logicalSlotId) ?: ""
+                    } catch (e: Exception) {
+                        ""
+                    },
                     channel.lpa.profiles.find { it.state == LocalProfileInfo.State.Enabled }?.displayName
                 )
             }
@@ -95,6 +101,10 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt
             0
         }
 
+        if (slots.isNotEmpty()) {
+            state.imei = slots[adapter.currentSelectedIdx].imei
+        }
+
         adapter.notifyDataSetChanged()
         hideProgressBar()
         loaded = true
@@ -126,6 +136,7 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt
             adapter.notifyItemChanged(curIdx)
             // Selected index isn't logical slot ID directly, needs a conversion
             state.selectedLogicalSlot = adapter.slots[adapter.currentSelectedIdx].logicalSlotId
+            state.imei = adapter.slots[adapter.currentSelectedIdx].imei
         }
 
         fun bind(item: SlotInfo, idx: Int) {