diff --git a/.idea/misc.xml b/.idea/misc.xml
index 9c6b75e..67135cd 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -3,10 +3,13 @@
diff --git a/app/build.gradle b/app/build.gradle
index e6264f9..996e927 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -44,6 +44,7 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1'
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "androidx.cardview:cardview:1.0.0"
+ implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 7e0fdf2..cc80baf 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -11,6 +11,7 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/java/im/angry/openeuicc/OpenEUICCApplication.kt b/app/src/main/java/im/angry/openeuicc/OpenEUICCApplication.kt
new file mode 100644
index 0000000..bdd6520
--- /dev/null
+++ b/app/src/main/java/im/angry/openeuicc/OpenEUICCApplication.kt
@@ -0,0 +1,8 @@
+package im.angry.openeuicc
+
+import android.app.Application
+import im.angry.openeuicc.core.EuiccChannelRepositoryProxy
+
+class OpenEUICCApplication : Application() {
+ val euiccChannelRepo = EuiccChannelRepositoryProxy(this)
+}
\ No newline at end of file
diff --git a/app/src/main/java/im/angry/openeuicc/ui/EuiccChannelFragmentUtils.kt b/app/src/main/java/im/angry/openeuicc/ui/EuiccChannelFragmentUtils.kt
new file mode 100644
index 0000000..4aaa49a
--- /dev/null
+++ b/app/src/main/java/im/angry/openeuicc/ui/EuiccChannelFragmentUtils.kt
@@ -0,0 +1,23 @@
+package im.angry.openeuicc.ui
+
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import im.angry.openeuicc.OpenEUICCApplication
+import im.angry.openeuicc.core.EuiccChannel
+
+interface EuiccFragmentMarker
+
+fun newInstanceEuicc(clazz: Class, slotId: Int): T where T: Fragment, T: EuiccFragmentMarker {
+ val instance = clazz.newInstance()
+ instance.arguments = Bundle().apply {
+ putInt("slotId", slotId)
+ }
+ return instance
+}
+
+val T.slotId: Int where T: Fragment, T: EuiccFragmentMarker
+ get() = requireArguments().getInt("slotId")
+
+val T.channel: EuiccChannel where T: Fragment, T: EuiccFragmentMarker
+ get() =
+ (requireActivity().application as OpenEUICCApplication).euiccChannelRepo.availableChannels[slotId]
\ No newline at end of file
diff --git a/app/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt b/app/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt
index 4b17565..7b0a6b9 100644
--- a/app/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt
+++ b/app/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt
@@ -11,14 +11,18 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import im.angry.openeuicc.R
-import im.angry.openeuicc.core.EuiccChannel
import im.angry.openeuicc.databinding.EuiccProfileBinding
import im.angry.openeuicc.databinding.FragmentEuiccBinding
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
-class EuiccManagementFragment(private val channel: EuiccChannel) : Fragment() {
+class EuiccManagementFragment : Fragment(), EuiccFragmentMarker {
+ companion object {
+ fun newInstance(slotId: Int): EuiccManagementFragment =
+ newInstanceEuicc(EuiccManagementFragment::class.java, slotId)
+ }
+
private var _binding: FragmentEuiccBinding? = null
private val binding get() = _binding!!
@@ -39,6 +43,11 @@ class EuiccManagementFragment(private val channel: EuiccChannel) : Fragment() {
binding.profileList.adapter = adapter
binding.profileList.layoutManager =
LinearLayoutManager(view.context, LinearLayoutManager.VERTICAL, false)
+
+ binding.fab.setOnClickListener {
+ ProfileDownloadFragment.newInstance(slotId)
+ .show(childFragmentManager, ProfileDownloadFragment.TAG)
+ }
}
override fun onStart() {
diff --git a/app/src/main/java/im/angry/openeuicc/ui/MainActivity.kt b/app/src/main/java/im/angry/openeuicc/ui/MainActivity.kt
index 8c9a305..5551219 100644
--- a/app/src/main/java/im/angry/openeuicc/ui/MainActivity.kt
+++ b/app/src/main/java/im/angry/openeuicc/ui/MainActivity.kt
@@ -9,8 +9,9 @@ import android.widget.ArrayAdapter
import android.widget.Spinner
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
+import im.angry.openeuicc.OpenEUICCApplication
import im.angry.openeuicc.R
-import im.angry.openeuicc.core.EuiccChannelRepositoryProxy
+import im.angry.openeuicc.core.EuiccChannelRepository
import im.angry.openeuicc.databinding.ActivityMainBinding
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -21,7 +22,7 @@ class MainActivity : AppCompatActivity() {
const val TAG = "MainActivity"
}
- private val repo = EuiccChannelRepositoryProxy(this)
+ private lateinit var repo: EuiccChannelRepository
private lateinit var spinnerAdapter: ArrayAdapter
private lateinit var spinner: Spinner
@@ -35,6 +36,8 @@ class MainActivity : AppCompatActivity() {
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
+ repo = (application as OpenEUICCApplication).euiccChannelRepo
+
spinnerAdapter = ArrayAdapter(this, android.R.layout.simple_spinner_item)
lifecycleScope.launch {
@@ -75,9 +78,9 @@ class MainActivity : AppCompatActivity() {
}
withContext(Dispatchers.Main) {
- repo.availableChannels.forEach {
- spinnerAdapter.add(it.name)
- fragments.add(EuiccManagementFragment(it))
+ repo.availableChannels.forEachIndexed { idx, channel ->
+ spinnerAdapter.add(channel.name)
+ fragments.add(EuiccManagementFragment.newInstance(idx))
}
if (fragments.isNotEmpty()) {
diff --git a/app/src/main/java/im/angry/openeuicc/ui/ProfileDownloadFragment.kt b/app/src/main/java/im/angry/openeuicc/ui/ProfileDownloadFragment.kt
new file mode 100644
index 0000000..bdd5fd2
--- /dev/null
+++ b/app/src/main/java/im/angry/openeuicc/ui/ProfileDownloadFragment.kt
@@ -0,0 +1,78 @@
+package im.angry.openeuicc.ui
+
+import android.app.Dialog
+import android.os.Bundle
+import android.view.*
+import androidx.appcompat.widget.Toolbar
+import androidx.fragment.app.DialogFragment
+import com.journeyapps.barcodescanner.ScanContract
+import com.journeyapps.barcodescanner.ScanOptions
+import im.angry.openeuicc.R
+import im.angry.openeuicc.databinding.FragmentProfileDownloadBinding
+import im.angry.openeuicc.util.setWidthPercent
+
+class ProfileDownloadFragment : DialogFragment(), EuiccFragmentMarker, Toolbar.OnMenuItemClickListener {
+ companion object {
+ const val TAG = "ProfileDownloadFragment"
+
+ fun newInstance(slotId: Int): ProfileDownloadFragment =
+ newInstanceEuicc(ProfileDownloadFragment::class.java, slotId)
+ }
+
+ private var _binding: FragmentProfileDownloadBinding? = null
+ private val binding get() = _binding!!
+
+ private val barcodeScannerLauncher = registerForActivityResult(ScanContract()) { result ->
+ result.contents?.let { content ->
+ val components = content.split("$")
+ if (components.size != 3 || components[0] != "LPA:1") return@registerForActivityResult
+ binding.profileDownloadServer.editText?.setText(components[1])
+ binding.profileDownloadCode.editText?.setText(components[2])
+ }
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentProfileDownloadBinding.inflate(inflater, container, false)
+ binding.toolbar.inflateMenu(R.menu.fragment_profile_download)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.toolbar.apply {
+ setTitle(R.string.profile_download)
+ setNavigationOnClickListener {
+ dismiss()
+ }
+ setOnMenuItemClickListener(this@ProfileDownloadFragment)
+ }
+ }
+
+ override fun onMenuItemClick(item: MenuItem): Boolean =
+ when (item.itemId) {
+ R.id.scan -> {
+ barcodeScannerLauncher.launch(ScanOptions().apply {
+ setDesiredBarcodeFormats(ScanOptions.QR_CODE)
+ setOrientationLocked(false)
+ })
+ true
+ }
+ else -> false
+ }
+
+ override fun onResume() {
+ super.onResume()
+ setWidthPercent(95)
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ return super.onCreateDialog(savedInstanceState).also {
+ it.window?.requestFeature(Window.FEATURE_NO_TITLE)
+ it.setCanceledOnTouchOutside(false)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/im/angry/openeuicc/util/UiUtils.kt b/app/src/main/java/im/angry/openeuicc/util/UiUtils.kt
new file mode 100644
index 0000000..24c1ad8
--- /dev/null
+++ b/app/src/main/java/im/angry/openeuicc/util/UiUtils.kt
@@ -0,0 +1,28 @@
+package im.angry.openeuicc.util
+
+import android.content.res.Resources
+import android.graphics.Rect
+import android.view.ViewGroup
+import androidx.fragment.app.DialogFragment
+
+// Source:
+/**
+ * Call this method (in onActivityCreated or later) to set
+ * the width of the dialog to a percentage of the current
+ * screen width.
+ */
+fun DialogFragment.setWidthPercent(percentage: Int) {
+ val percent = percentage.toFloat() / 100
+ val dm = Resources.getSystem().displayMetrics
+ val rect = dm.run { Rect(0, 0, widthPixels, heightPixels) }
+ val percentWidth = rect.width() * percent
+ dialog?.window?.setLayout(percentWidth.toInt(), ViewGroup.LayoutParams.WRAP_CONTENT)
+}
+
+/**
+ * Call this method (in onActivityCreated or later)
+ * to make the dialog near-full screen.
+ */
+fun DialogFragment.setFullScreen() {
+ dialog?.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+}
diff --git a/app/src/main/res/drawable/ic_scan_black.xml b/app/src/main/res/drawable/ic_scan_black.xml
new file mode 100644
index 0000000..597e8d7
--- /dev/null
+++ b/app/src/main/res/drawable/ic_scan_black.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/layout/fragment_profile_download.xml b/app/src/main/res/layout/fragment_profile_download.xml
new file mode 100644
index 0000000..dab6655
--- /dev/null
+++ b/app/src/main/res/layout/fragment_profile_download.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/fragment_profile_download.xml b/app/src/main/res/menu/fragment_profile_download.xml
new file mode 100644
index 0000000..6f52963
--- /dev/null
+++ b/app/src/main/res/menu/fragment_profile_download.xml
@@ -0,0 +1,9 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index dcc7169..0c6c866 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -7,4 +7,9 @@
Disabled
Provider:
ICCID:
+
+ New eSIM
+ Server (RSP / SM-DP+)
+ Activation Code
+ Scan QR Code
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 7e1d6b0..747a861 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -9,8 +9,21 @@
- @color/pink_600
- @color/pink_800
- @color/white
+ - ?attr/colorSecondary
- ?attr/colorPrimaryVariant
+
+
+
+
+
+
\ No newline at end of file