ui: refactor: Use ViewPager2 instead of an ad-hoc spinner for slot selection
All checks were successful
/ build-debug (push) Successful in 4m32s

This commit is contained in:
Peter Cai 2024-07-06 18:12:33 -04:00
parent d78985bd72
commit 3ffd847af7
5 changed files with 71 additions and 75 deletions

View file

@ -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<String>
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<Page> = 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<Fragment>()
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<String>(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()
}

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="?attr/colorControlNormal"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/>
</vector>

View file

@ -14,6 +14,17 @@
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintWidth_percent="1" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/main_tabs"
android:background="?attr/colorSurfaceVariant"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
app:tabTextColor="?attr/colorOnSurfaceVariant"
app:tabSelectedTextColor="?attr/colorOnSurfaceVariant"
app:layout_constraintTop_toBottomOf="@id/toolbar"
app:layout_constraintStart_toStartOf="parent" />
<ProgressBar
android:id="@+id/loading"
android:layout_width="wrap_content"
@ -21,16 +32,17 @@
android:indeterminate="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar"
app:layout_constraintTop_toBottomOf="@id/main_tabs"
app:layout_constraintBottom_toBottomOf="parent" />
<FrameLayout
android:id="@+id/fragment_root"
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/view_pager"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar"/>
app:layout_constraintTop_toBottomOf="@id/main_tabs"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,16 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/spinner"
android:title=""
app:actionViewClass="android.widget.Spinner"
app:showAsAction="always" />
<item
android:id="@+id/reload"
android:title="@string/reload"
app:showAsAction="never" />
android:icon="@drawable/ic_refresh_black"
app:showAsAction="ifRoom" />
<item
android:id="@+id/settings"

View file

@ -50,6 +50,7 @@ dependencies {
api("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
api("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
api("androidx.cardview:cardview:1.0.0")
api("androidx.viewpager2:viewpager2:1.1.0")
api("androidx.datastore:datastore-preferences:1.0.0")
api("com.journeyapps:zxing-android-embedded:4.3.0")
testImplementation("junit:junit:4.13.2")