From 3ffd847af76d0f7c3bebaec642fe51447f3d7672 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sat, 6 Jul 2024 18:12:33 -0400 Subject: [PATCH] ui: refactor: Use ViewPager2 instead of an ad-hoc spinner for slot selection --- .../im/angry/openeuicc/ui/MainActivity.kt | 111 ++++++++---------- .../main/res/drawable/ic_refresh_black.xml | 5 + .../src/main/res/layout/activity_main.xml | 20 +++- .../src/main/res/menu/activity_main.xml | 9 +- app-deps/build.gradle.kts | 1 + 5 files changed, 71 insertions(+), 75 deletions(-) create mode 100644 app-common/src/main/res/drawable/ic_refresh_black.xml diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt b/app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt index 161392c..85af388 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt @@ -12,28 +12,43 @@ import android.util.Log import android.view.Menu import android.view.MenuItem import android.view.View -import android.widget.AdapterView -import android.widget.ArrayAdapter import android.widget.ProgressBar -import android.widget.Spinner import androidx.fragment.app.Fragment -import androidx.fragment.app.commitNow import androidx.lifecycle.lifecycleScope +import androidx.viewpager2.adapter.FragmentStateAdapter +import androidx.viewpager2.widget.ViewPager2 +import com.google.android.material.tabs.TabLayout +import com.google.android.material.tabs.TabLayoutMediator import im.angry.openeuicc.common.R import im.angry.openeuicc.util.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +@SuppressLint("NotifyDataSetChanged") open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker { companion object { const val TAG = "MainActivity" } - private lateinit var spinnerAdapter: ArrayAdapter - private lateinit var spinnerItem: MenuItem - private lateinit var spinner: Spinner private lateinit var loadingProgress: ProgressBar + private lateinit var tabs: TabLayout + private lateinit var viewPager: ViewPager2 + + private data class Page( + val title: String, + val createFragment: () -> Fragment + ) + + private val pages: MutableList = mutableListOf() + + private val pagerAdapter by lazy { + object : FragmentStateAdapter(this) { + override fun getItemCount() = pages.size + + override fun createFragment(position: Int): Fragment = pages[position].createFragment() + } + } var loading: Boolean get() = loadingProgress.visibility == View.VISIBLE @@ -45,8 +60,6 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker { } } - private val fragments = arrayListOf() - protected lateinit var tm: TelephonyManager private val usbReceiver = object : BroadcastReceiver() { @@ -63,11 +76,16 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker { setContentView(R.layout.activity_main) setSupportActionBar(requireViewById(R.id.toolbar)) loadingProgress = requireViewById(R.id.loading) + tabs = requireViewById(R.id.main_tabs) + viewPager = requireViewById(R.id.view_pager) + + viewPager.adapter = pagerAdapter + TabLayoutMediator(tabs, viewPager) { tab, pos -> + tab.text = pages[pos].title + }.attach() tm = telephonyManager - spinnerAdapter = ArrayAdapter(this, R.layout.spinner_item) - registerReceiver(usbReceiver, IntentFilter().apply { addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED) addAction(UsbManager.ACTION_USB_DEVICE_DETACHED) @@ -81,37 +99,6 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker { override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.activity_main, menu) - - if (!this::spinner.isInitialized) { - spinnerItem = menu.findItem(R.id.spinner) - spinner = spinnerItem.actionView as Spinner - if (spinnerAdapter.isEmpty) { - spinnerItem.isVisible = false - } - spinner.adapter = spinnerAdapter - spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { - override fun onItemSelected( - parent: AdapterView<*>?, - view: View?, - position: Int, - id: Long - ) { - if (position < fragments.size) { - supportFragmentManager.beginTransaction() - .replace(R.id.fragment_root, fragments[position]).commit() - } - } - - override fun onNothingSelected(parent: AdapterView<*>?) { - } - - } - } else { - // Fragments may cause this menu to be inflated multiple times. - // Simply reuse the action view in that case - menu.findItem(R.id.spinner).actionView = spinner - } - return true } @@ -136,6 +123,8 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker { private suspend fun init() { loading = true + viewPager.visibility = View.GONE + tabs.visibility = View.GONE val knownChannels = withContext(Dispatchers.IO) { euiccChannelManager.enumerateEuiccChannels().onEach { @@ -156,28 +145,25 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker { loading = false knownChannels.sortedBy { it.logicalSlotId }.forEach { channel -> - spinnerAdapter.add(getString(R.string.channel_name_format, channel.logicalSlotId)) - fragments.add(appContainer.uiComponentFactory.createEuiccManagementFragment(channel)) + pages.add(Page( + getString(R.string.channel_name_format, channel.logicalSlotId) + ) { appContainer.uiComponentFactory.createEuiccManagementFragment(channel) }) } // If USB readers exist, add them at the very last // We use a wrapper fragment to handle logic specific to USB readers usbDevice?.let { - spinnerAdapter.add(it.productName) - fragments.add(UsbCcidReaderFragment()) + //spinnerAdapter.add(it.productName) + pages.add(Page(it.productName ?: "USB") { UsbCcidReaderFragment() }) } + pagerAdapter.notifyDataSetChanged() + viewPager.visibility = View.VISIBLE - if (fragments.isNotEmpty()) { - if (this@MainActivity::spinner.isInitialized) { - spinnerItem.isVisible = true - } - supportFragmentManager.beginTransaction() - .replace(R.id.fragment_root, fragments.first()).commit() - } else { - supportFragmentManager.beginTransaction().replace( - R.id.fragment_root, - appContainer.uiComponentFactory.createNoEuiccPlaceholderFragment() - ).commit() + if (pages.size > 1) { + tabs.visibility = View.VISIBLE + } else if (pages.isEmpty()) { + pages.add(Page("") { appContainer.uiComponentFactory.createNoEuiccPlaceholderFragment() }) + pagerAdapter.notifyDataSetChanged() } } } @@ -185,14 +171,11 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker { private fun refresh() { lifecycleScope.launch { loading = true + viewPager.visibility = View.GONE + tabs.visibility = View.GONE - supportFragmentManager.commitNow { - fragments.forEach { - remove(it) - } - } - fragments.clear() - spinnerAdapter.clear() + pages.clear() + pagerAdapter.notifyDataSetChanged() init() } diff --git a/app-common/src/main/res/drawable/ic_refresh_black.xml b/app-common/src/main/res/drawable/ic_refresh_black.xml new file mode 100644 index 0000000..ccaf34b --- /dev/null +++ b/app-common/src/main/res/drawable/ic_refresh_black.xml @@ -0,0 +1,5 @@ + + + diff --git a/app-common/src/main/res/layout/activity_main.xml b/app-common/src/main/res/layout/activity_main.xml index 89b9d7e..cb5f224 100644 --- a/app-common/src/main/res/layout/activity_main.xml +++ b/app-common/src/main/res/layout/activity_main.xml @@ -14,6 +14,17 @@ app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintWidth_percent="1" /> + + - + app:layout_constraintTop_toBottomOf="@id/main_tabs"/> \ No newline at end of file diff --git a/app-common/src/main/res/menu/activity_main.xml b/app-common/src/main/res/menu/activity_main.xml index 509ad79..0e00292 100644 --- a/app-common/src/main/res/menu/activity_main.xml +++ b/app-common/src/main/res/menu/activity_main.xml @@ -1,16 +1,11 @@ - - + android:icon="@drawable/ic_refresh_black" + app:showAsAction="ifRoom" />