diff --git a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannel.kt b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannel.kt index c3036a1..a16073f 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannel.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannel.kt @@ -12,6 +12,7 @@ abstract class EuiccChannel( val cardId = port.card.cardId val name = "SLOT $logicalSlotId" val removable = port.card.isRemovable + val isMEP = port.card.isMultipleEnabledProfilesSupported abstract val lpa: LocalProfileAssistant val valid: Boolean diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt index e44d495..01e82ce 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt @@ -8,6 +8,7 @@ import android.view.LayoutInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup +import android.widget.FrameLayout import android.widget.ImageButton import android.widget.PopupMenu import android.widget.TextView @@ -26,7 +27,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.lang.Exception -class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesChangedListener { +open class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesChangedListener { companion object { const val TAG = "EuiccManagementFragment" @@ -38,7 +39,7 @@ class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesCh private lateinit var fab: FloatingActionButton private lateinit var profileList: RecyclerView - private val adapter = EuiccProfileAdapter(listOf()) + private val adapter = EuiccProfileAdapter() override fun onCreateView( inflater: LayoutInflater, @@ -76,6 +77,8 @@ class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesCh refresh() } + protected open suspend fun onCreateFooterViews(parent: ViewGroup): List = listOf() + @SuppressLint("NotifyDataSetChanged") private fun refresh() { swipeRefresh.isRefreshing = true @@ -88,6 +91,7 @@ class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesCh withContext(Dispatchers.Main) { adapter.profiles = profiles.operational + adapter.footerViews = onCreateFooterViews(profileList) adapter.notifyDataSetChanged() swipeRefresh.isRefreshing = false } @@ -130,7 +134,30 @@ class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesCh channel.lpa.disableProfile(iccid) } - inner class ViewHolder(private val root: View) : RecyclerView.ViewHolder(root) { + sealed class ViewHolder(root: View) : RecyclerView.ViewHolder(root) { + enum class Type(val value: Int) { + PROFILE(0), + FOOTER(1); + + companion object { + fun fromInt(value: Int) = + Type.values().first { it.value == value } + } + } + } + + inner class FooterViewHolder: ViewHolder(FrameLayout(requireContext())) { + fun attach(view: View) { + view.parent?.let { (it as ViewGroup).removeView(view) } + (itemView as FrameLayout).addView(view) + } + + fun detach() { + (itemView as FrameLayout).removeAllViews() + } + } + + inner class ProfileViewHolder(private val root: View) : ViewHolder(root) { private val iccid: TextView = root.findViewById(R.id.iccid) private val name: TextView = root.findViewById(R.id.name) private val state: TextView = root.findViewById(R.id.state) @@ -208,16 +235,49 @@ class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesCh } } - inner class EuiccProfileAdapter(var profiles: List) : RecyclerView.Adapter() { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - val view = LayoutInflater.from(parent.context).inflate(R.layout.euicc_profile, parent, false) - return ViewHolder(view) - } + inner class EuiccProfileAdapter : RecyclerView.Adapter() { + var profiles: List = listOf() + var footerViews: List = listOf() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = + when (ViewHolder.Type.fromInt(viewType)) { + ViewHolder.Type.PROFILE -> { + val view = LayoutInflater.from(parent.context).inflate(R.layout.euicc_profile, parent, false) + ProfileViewHolder(view) + } + ViewHolder.Type.FOOTER -> { + FooterViewHolder() + } + } + + override fun getItemViewType(position: Int): Int = + when { + position < profiles.size -> { + ViewHolder.Type.PROFILE.value + } + position >= profiles.size && position < profiles.size + footerViews.size -> { + ViewHolder.Type.FOOTER.value + } + else -> -1 + } override fun onBindViewHolder(holder: ViewHolder, position: Int) { - holder.setProfile(profiles[position]) + when (holder) { + is ProfileViewHolder -> { + holder.setProfile(profiles[position]) + } + is FooterViewHolder -> { + holder.attach(footerViews[position - profiles.size]) + } + } } - override fun getItemCount(): Int = profiles.size + override fun onViewRecycled(holder: ViewHolder) { + if (holder is FooterViewHolder) { + holder.detach() + } + } + + override fun getItemCount(): Int = profiles.size + footerViews.size } } \ No newline at end of file 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 4bd45e4..961676d 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 @@ -11,6 +11,7 @@ import android.widget.Spinner import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import im.angry.openeuicc.common.R +import im.angry.openeuicc.core.EuiccChannel import im.angry.openeuicc.core.EuiccChannelManager import im.angry.openeuicc.util.* import kotlinx.coroutines.Dispatchers @@ -72,6 +73,9 @@ open class MainActivity : AppCompatActivity() { return true } + protected open fun createEuiccManagementFragment(channel: EuiccChannel): EuiccManagementFragment = + EuiccManagementFragment.newInstance(channel.slotId, channel.portId) + private suspend fun init() { withContext(Dispatchers.IO) { manager.enumerateEuiccChannels() @@ -88,7 +92,7 @@ open class MainActivity : AppCompatActivity() { withContext(Dispatchers.Main) { manager.knownChannels.forEach { channel -> spinnerAdapter.add(channel.name) - fragments.add(EuiccManagementFragment.newInstance(channel.slotId, channel.portId)) + fragments.add(createEuiccManagementFragment(channel)) } if (fragments.isNotEmpty()) { diff --git a/app/src/main/java/im/angry/openeuicc/ui/PrivilegedEuiccManagementFragment.kt b/app/src/main/java/im/angry/openeuicc/ui/PrivilegedEuiccManagementFragment.kt new file mode 100644 index 0000000..730fc49 --- /dev/null +++ b/app/src/main/java/im/angry/openeuicc/ui/PrivilegedEuiccManagementFragment.kt @@ -0,0 +1,24 @@ +package im.angry.openeuicc.ui + +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import im.angry.openeuicc.R + +class PrivilegedEuiccManagementFragment: EuiccManagementFragment() { + companion object { + fun newInstance(slotId: Int, portId: Int): EuiccManagementFragment = + newInstanceEuicc(PrivilegedEuiccManagementFragment::class.java, slotId, portId) + } + + override suspend fun onCreateFooterViews(parent: ViewGroup): List = + if (channel.isMEP) { + val view = layoutInflater.inflate(R.layout.footer_mep, parent, false) + view.findViewById