ui: refactor: Use ViewPager2 instead of an ad-hoc spinner for slot selection
All checks were successful
/ build-debug (push) Successful in 4m32s
All checks were successful
/ build-debug (push) Successful in 4m32s
This commit is contained in:
parent
d78985bd72
commit
3ffd847af7
|
@ -12,28 +12,43 @@ import android.util.Log
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.AdapterView
|
|
||||||
import android.widget.ArrayAdapter
|
|
||||||
import android.widget.ProgressBar
|
import android.widget.ProgressBar
|
||||||
import android.widget.Spinner
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.commitNow
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
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.common.R
|
||||||
import im.angry.openeuicc.util.*
|
import im.angry.openeuicc.util.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "MainActivity"
|
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 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
|
var loading: Boolean
|
||||||
get() = loadingProgress.visibility == View.VISIBLE
|
get() = loadingProgress.visibility == View.VISIBLE
|
||||||
|
@ -45,8 +60,6 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val fragments = arrayListOf<Fragment>()
|
|
||||||
|
|
||||||
protected lateinit var tm: TelephonyManager
|
protected lateinit var tm: TelephonyManager
|
||||||
|
|
||||||
private val usbReceiver = object : BroadcastReceiver() {
|
private val usbReceiver = object : BroadcastReceiver() {
|
||||||
|
@ -63,11 +76,16 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
||||||
setContentView(R.layout.activity_main)
|
setContentView(R.layout.activity_main)
|
||||||
setSupportActionBar(requireViewById(R.id.toolbar))
|
setSupportActionBar(requireViewById(R.id.toolbar))
|
||||||
loadingProgress = requireViewById(R.id.loading)
|
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
|
tm = telephonyManager
|
||||||
|
|
||||||
spinnerAdapter = ArrayAdapter<String>(this, R.layout.spinner_item)
|
|
||||||
|
|
||||||
registerReceiver(usbReceiver, IntentFilter().apply {
|
registerReceiver(usbReceiver, IntentFilter().apply {
|
||||||
addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED)
|
addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED)
|
||||||
addAction(UsbManager.ACTION_USB_DEVICE_DETACHED)
|
addAction(UsbManager.ACTION_USB_DEVICE_DETACHED)
|
||||||
|
@ -81,37 +99,6 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
menuInflater.inflate(R.menu.activity_main, menu)
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,6 +123,8 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
||||||
|
|
||||||
private suspend fun init() {
|
private suspend fun init() {
|
||||||
loading = true
|
loading = true
|
||||||
|
viewPager.visibility = View.GONE
|
||||||
|
tabs.visibility = View.GONE
|
||||||
|
|
||||||
val knownChannels = withContext(Dispatchers.IO) {
|
val knownChannels = withContext(Dispatchers.IO) {
|
||||||
euiccChannelManager.enumerateEuiccChannels().onEach {
|
euiccChannelManager.enumerateEuiccChannels().onEach {
|
||||||
|
@ -156,28 +145,25 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
||||||
loading = false
|
loading = false
|
||||||
|
|
||||||
knownChannels.sortedBy { it.logicalSlotId }.forEach { channel ->
|
knownChannels.sortedBy { it.logicalSlotId }.forEach { channel ->
|
||||||
spinnerAdapter.add(getString(R.string.channel_name_format, channel.logicalSlotId))
|
pages.add(Page(
|
||||||
fragments.add(appContainer.uiComponentFactory.createEuiccManagementFragment(channel))
|
getString(R.string.channel_name_format, channel.logicalSlotId)
|
||||||
|
) { appContainer.uiComponentFactory.createEuiccManagementFragment(channel) })
|
||||||
}
|
}
|
||||||
|
|
||||||
// If USB readers exist, add them at the very last
|
// If USB readers exist, add them at the very last
|
||||||
// We use a wrapper fragment to handle logic specific to USB readers
|
// We use a wrapper fragment to handle logic specific to USB readers
|
||||||
usbDevice?.let {
|
usbDevice?.let {
|
||||||
spinnerAdapter.add(it.productName)
|
//spinnerAdapter.add(it.productName)
|
||||||
fragments.add(UsbCcidReaderFragment())
|
pages.add(Page(it.productName ?: "USB") { UsbCcidReaderFragment() })
|
||||||
}
|
}
|
||||||
|
pagerAdapter.notifyDataSetChanged()
|
||||||
|
viewPager.visibility = View.VISIBLE
|
||||||
|
|
||||||
if (fragments.isNotEmpty()) {
|
if (pages.size > 1) {
|
||||||
if (this@MainActivity::spinner.isInitialized) {
|
tabs.visibility = View.VISIBLE
|
||||||
spinnerItem.isVisible = true
|
} else if (pages.isEmpty()) {
|
||||||
}
|
pages.add(Page("") { appContainer.uiComponentFactory.createNoEuiccPlaceholderFragment() })
|
||||||
supportFragmentManager.beginTransaction()
|
pagerAdapter.notifyDataSetChanged()
|
||||||
.replace(R.id.fragment_root, fragments.first()).commit()
|
|
||||||
} else {
|
|
||||||
supportFragmentManager.beginTransaction().replace(
|
|
||||||
R.id.fragment_root,
|
|
||||||
appContainer.uiComponentFactory.createNoEuiccPlaceholderFragment()
|
|
||||||
).commit()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -185,14 +171,11 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
||||||
private fun refresh() {
|
private fun refresh() {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
loading = true
|
loading = true
|
||||||
|
viewPager.visibility = View.GONE
|
||||||
|
tabs.visibility = View.GONE
|
||||||
|
|
||||||
supportFragmentManager.commitNow {
|
pages.clear()
|
||||||
fragments.forEach {
|
pagerAdapter.notifyDataSetChanged()
|
||||||
remove(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fragments.clear()
|
|
||||||
spinnerAdapter.clear()
|
|
||||||
|
|
||||||
init()
|
init()
|
||||||
}
|
}
|
||||||
|
|
5
app-common/src/main/res/drawable/ic_refresh_black.xml
Normal file
5
app-common/src/main/res/drawable/ic_refresh_black.xml
Normal 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>
|
|
@ -14,6 +14,17 @@
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintWidth_percent="1" />
|
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
|
<ProgressBar
|
||||||
android:id="@+id/loading"
|
android:id="@+id/loading"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
@ -21,16 +32,17 @@
|
||||||
android:indeterminate="true"
|
android:indeterminate="true"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/toolbar"
|
app:layout_constraintTop_toBottomOf="@id/main_tabs"
|
||||||
app:layout_constraintBottom_toBottomOf="parent" />
|
app:layout_constraintBottom_toBottomOf="parent" />
|
||||||
|
|
||||||
<FrameLayout
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
android:id="@+id/fragment_root"
|
android:id="@+id/view_pager"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
|
android:visibility="gone"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/toolbar"/>
|
app:layout_constraintTop_toBottomOf="@id/main_tabs"/>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -1,16 +1,11 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
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
|
<item
|
||||||
android:id="@+id/reload"
|
android:id="@+id/reload"
|
||||||
android:title="@string/reload"
|
android:title="@string/reload"
|
||||||
app:showAsAction="never" />
|
android:icon="@drawable/ic_refresh_black"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/settings"
|
android:id="@+id/settings"
|
||||||
|
|
|
@ -50,6 +50,7 @@ dependencies {
|
||||||
api("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
|
api("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
|
||||||
api("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
api("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
||||||
api("androidx.cardview:cardview:1.0.0")
|
api("androidx.cardview:cardview:1.0.0")
|
||||||
|
api("androidx.viewpager2:viewpager2:1.1.0")
|
||||||
api("androidx.datastore:datastore-preferences:1.0.0")
|
api("androidx.datastore:datastore-preferences:1.0.0")
|
||||||
api("com.journeyapps:zxing-android-embedded:4.3.0")
|
api("com.journeyapps:zxing-android-embedded:4.3.0")
|
||||||
testImplementation("junit:junit:4.13.2")
|
testImplementation("junit:junit:4.13.2")
|
||||||
|
|
Loading…
Reference in a new issue