From 9c376d0a3c0c0d587ac21b03e4bd8ff31a479cf6 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sat, 30 Apr 2022 11:32:45 -0400 Subject: [PATCH 1/5] lay out the profile download dialog --- .idea/misc.xml | 1 + .../openeuicc/ui/EuiccManagementFragment.kt | 4 ++ .../openeuicc/ui/ProfileDownloadFragment.kt | 53 +++++++++++++++++ .../java/im/angry/openeuicc/util/UiUtils.kt | 28 +++++++++ .../res/layout/fragment_profile_download.xml | 58 +++++++++++++++++++ app/src/main/res/values/strings.xml | 4 ++ app/src/main/res/values/themes.xml | 13 +++++ 7 files changed, 161 insertions(+) create mode 100644 app/src/main/java/im/angry/openeuicc/ui/ProfileDownloadFragment.kt create mode 100644 app/src/main/java/im/angry/openeuicc/util/UiUtils.kt create mode 100644 app/src/main/res/layout/fragment_profile_download.xml diff --git a/.idea/misc.xml b/.idea/misc.xml index 9c6b75e..21b4ba2 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -6,6 +6,7 @@ + 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..380b4b8 100644 --- a/app/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt +++ b/app/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt @@ -39,6 +39,10 @@ class EuiccManagementFragment(private val channel: EuiccChannel) : Fragment() { binding.profileList.adapter = adapter binding.profileList.layoutManager = LinearLayoutManager(view.context, LinearLayoutManager.VERTICAL, false) + + binding.fab.setOnClickListener { + ProfileDownloadFragment(channel).show(childFragmentManager, ProfileDownloadFragment.TAG) + } } override fun onStart() { 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..f3cc663 --- /dev/null +++ b/app/src/main/java/im/angry/openeuicc/ui/ProfileDownloadFragment.kt @@ -0,0 +1,53 @@ +package im.angry.openeuicc.ui + +import android.app.Dialog +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.Window +import androidx.fragment.app.DialogFragment +import im.angry.openeuicc.R +import im.angry.openeuicc.core.EuiccChannel +import im.angry.openeuicc.databinding.FragmentProfileDownloadBinding +import im.angry.openeuicc.util.setWidthPercent + +class ProfileDownloadFragment(val channel: EuiccChannel) : DialogFragment() { + companion object { + const val TAG = "ProfileDownloadFragment" + } + + private var _binding: FragmentProfileDownloadBinding? = null + private val binding get() = _binding!! + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentProfileDownloadBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding.toolbar.apply { + setTitle(R.string.profile_download) + setNavigationOnClickListener { + dismiss() + } + } + } + + 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/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/values/strings.xml b/app/src/main/res/values/strings.xml index dcc7169..3d9e417 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -7,4 +7,8 @@ Disabled Provider: ICCID: + + New eSIM + Server (RSP / SM-DP+) + Activation 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 From 3219497cb04ac40014fc278a3e0adc166bdfd11c Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sat, 30 Apr 2022 14:36:12 -0400 Subject: [PATCH 2/5] implement eSIM QR code scanning --- .idea/misc.xml | 2 ++ app/build.gradle | 1 + app/src/main/AndroidManifest.xml | 5 +++ .../openeuicc/ui/ProfileDownloadFragment.kt | 33 ++++++++++++++++--- app/src/main/res/drawable/ic_scan_black.xml | 10 ++++++ .../res/menu/fragment_profile_download.xml | 9 +++++ app/src/main/res/values/strings.xml | 1 + 7 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 app/src/main/res/drawable/ic_scan_black.xml create mode 100644 app/src/main/res/menu/fragment_profile_download.xml diff --git a/.idea/misc.xml b/.idea/misc.xml index 21b4ba2..67135cd 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,11 +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..41a7fe6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,6 +26,11 @@ + + \ No newline at end of file diff --git a/app/src/main/java/im/angry/openeuicc/ui/ProfileDownloadFragment.kt b/app/src/main/java/im/angry/openeuicc/ui/ProfileDownloadFragment.kt index f3cc663..5db931f 100644 --- a/app/src/main/java/im/angry/openeuicc/ui/ProfileDownloadFragment.kt +++ b/app/src/main/java/im/angry/openeuicc/ui/ProfileDownloadFragment.kt @@ -2,17 +2,17 @@ package im.angry.openeuicc.ui import android.app.Dialog import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.Window +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.core.EuiccChannel import im.angry.openeuicc.databinding.FragmentProfileDownloadBinding import im.angry.openeuicc.util.setWidthPercent -class ProfileDownloadFragment(val channel: EuiccChannel) : DialogFragment() { +class ProfileDownloadFragment(val channel: EuiccChannel) : DialogFragment(), Toolbar.OnMenuItemClickListener { companion object { const val TAG = "ProfileDownloadFragment" } @@ -20,12 +20,22 @@ class ProfileDownloadFragment(val channel: EuiccChannel) : DialogFragment() { 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 } @@ -36,9 +46,22 @@ class ProfileDownloadFragment(val channel: EuiccChannel) : DialogFragment() { 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) 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/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 3d9e417..0c6c866 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -11,4 +11,5 @@ New eSIM Server (RSP / SM-DP+) Activation Code + Scan QR Code \ No newline at end of file From 27ca07cde069d4f0a3361e552733644cecef548f Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sat, 30 Apr 2022 14:40:09 -0400 Subject: [PATCH 3/5] Lift the ownership of EuiccChannelRepository to application Should probably use Dagger but there's just this one thing and I'm too lazy --- app/src/main/AndroidManifest.xml | 1 + .../main/java/im/angry/openeuicc/OpenEUICCApplication.kt | 8 ++++++++ app/src/main/java/im/angry/openeuicc/ui/MainActivity.kt | 7 +++++-- 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/im/angry/openeuicc/OpenEUICCApplication.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 41a7fe6..cc80baf 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,6 +11,7 @@ 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 { From e407ab0e79283cc85c846c217d96672038fa8628 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sat, 30 Apr 2022 14:49:32 -0400 Subject: [PATCH 4/5] Decouple the channel object from the fragments themselves ...to allow the framework to recreate the fragment objects as needed --- .../openeuicc/ui/EuiccManagementFragment.kt | 17 ++++++++++++++++- .../java/im/angry/openeuicc/ui/MainActivity.kt | 6 +++--- 2 files changed, 19 insertions(+), 4 deletions(-) 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 380b4b8..3446ccf 100644 --- a/app/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt +++ b/app/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt @@ -10,6 +10,7 @@ import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import im.angry.openeuicc.OpenEUICCApplication import im.angry.openeuicc.R import im.angry.openeuicc.core.EuiccChannel import im.angry.openeuicc.databinding.EuiccProfileBinding @@ -18,7 +19,19 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -class EuiccManagementFragment(private val channel: EuiccChannel) : Fragment() { +class EuiccManagementFragment : Fragment() { + companion object { + fun newInstance(slotId: Int): EuiccManagementFragment { + val instance = EuiccManagementFragment() + instance.arguments = Bundle().apply { + putInt("slotId", slotId) + } + return instance + } + } + + private lateinit var channel: EuiccChannel + private var _binding: FragmentEuiccBinding? = null private val binding get() = _binding!! @@ -47,6 +60,8 @@ class EuiccManagementFragment(private val channel: EuiccChannel) : Fragment() { override fun onStart() { super.onStart() + val slotId = requireArguments().getInt("slotId") + channel = (requireActivity().application as OpenEUICCApplication).euiccChannelRepo.availableChannels[slotId] refresh() } 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 ecccb10..5551219 100644 --- a/app/src/main/java/im/angry/openeuicc/ui/MainActivity.kt +++ b/app/src/main/java/im/angry/openeuicc/ui/MainActivity.kt @@ -78,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()) { From ca637da5ee1476858469eb686d51fdaacbd2a3ec Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sat, 30 Apr 2022 15:15:23 -0400 Subject: [PATCH 5/5] Make the download fragment also recreatable --- .../openeuicc/ui/EuiccChannelFragmentUtils.kt | 23 +++++++++++++++++++ .../openeuicc/ui/EuiccManagementFragment.kt | 20 ++++------------ .../openeuicc/ui/ProfileDownloadFragment.kt | 6 +++-- 3 files changed, 32 insertions(+), 17 deletions(-) create mode 100644 app/src/main/java/im/angry/openeuicc/ui/EuiccChannelFragmentUtils.kt 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 3446ccf..7b0a6b9 100644 --- a/app/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt +++ b/app/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt @@ -10,28 +10,19 @@ import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import im.angry.openeuicc.OpenEUICCApplication 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 : Fragment() { +class EuiccManagementFragment : Fragment(), EuiccFragmentMarker { companion object { - fun newInstance(slotId: Int): EuiccManagementFragment { - val instance = EuiccManagementFragment() - instance.arguments = Bundle().apply { - putInt("slotId", slotId) - } - return instance - } + fun newInstance(slotId: Int): EuiccManagementFragment = + newInstanceEuicc(EuiccManagementFragment::class.java, slotId) } - private lateinit var channel: EuiccChannel - private var _binding: FragmentEuiccBinding? = null private val binding get() = _binding!! @@ -54,14 +45,13 @@ class EuiccManagementFragment : Fragment() { LinearLayoutManager(view.context, LinearLayoutManager.VERTICAL, false) binding.fab.setOnClickListener { - ProfileDownloadFragment(channel).show(childFragmentManager, ProfileDownloadFragment.TAG) + ProfileDownloadFragment.newInstance(slotId) + .show(childFragmentManager, ProfileDownloadFragment.TAG) } } override fun onStart() { super.onStart() - val slotId = requireArguments().getInt("slotId") - channel = (requireActivity().application as OpenEUICCApplication).euiccChannelRepo.availableChannels[slotId] refresh() } diff --git a/app/src/main/java/im/angry/openeuicc/ui/ProfileDownloadFragment.kt b/app/src/main/java/im/angry/openeuicc/ui/ProfileDownloadFragment.kt index 5db931f..bdd5fd2 100644 --- a/app/src/main/java/im/angry/openeuicc/ui/ProfileDownloadFragment.kt +++ b/app/src/main/java/im/angry/openeuicc/ui/ProfileDownloadFragment.kt @@ -8,13 +8,15 @@ import androidx.fragment.app.DialogFragment import com.journeyapps.barcodescanner.ScanContract import com.journeyapps.barcodescanner.ScanOptions import im.angry.openeuicc.R -import im.angry.openeuicc.core.EuiccChannel import im.angry.openeuicc.databinding.FragmentProfileDownloadBinding import im.angry.openeuicc.util.setWidthPercent -class ProfileDownloadFragment(val channel: EuiccChannel) : DialogFragment(), Toolbar.OnMenuItemClickListener { +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