| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- package im.angry.openeuicc.ui
- import android.annotation.SuppressLint
- import android.os.Bundle
- import android.text.Html
- import android.view.ContextMenu
- import android.view.LayoutInflater
- import android.view.Menu
- import android.view.MenuItem
- import android.view.MenuItem.OnMenuItemClickListener
- import android.view.View
- import android.view.ViewGroup
- import android.widget.TextView
- import androidx.activity.enableEdgeToEdge
- import androidx.appcompat.app.AlertDialog
- import androidx.core.view.forEach
- import androidx.lifecycle.lifecycleScope
- import androidx.recyclerview.widget.DividerItemDecoration
- import androidx.recyclerview.widget.LinearLayoutManager
- import androidx.recyclerview.widget.RecyclerView
- import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
- import im.angry.openeuicc.common.R
- import im.angry.openeuicc.core.EuiccChannelManager
- import im.angry.openeuicc.util.*
- import kotlinx.coroutines.Dispatchers
- import kotlinx.coroutines.launch
- import kotlinx.coroutines.withContext
- import net.typeblog.lpac_jni.LocalProfileNotification
- class NotificationsActivity: BaseEuiccAccessActivity(), OpenEuiccContextMarker {
- private lateinit var swipeRefresh: SwipeRefreshLayout
- private lateinit var notificationList: RecyclerView
- private val notificationAdapter = NotificationAdapter()
- private var logicalSlotId = -1
- override fun onCreate(savedInstanceState: Bundle?) {
- enableEdgeToEdge()
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_notifications)
- setSupportActionBar(requireViewById(R.id.toolbar))
- setupToolbarInsets()
- supportActionBar!!.setDisplayHomeAsUpEnabled(true)
- swipeRefresh = requireViewById(R.id.swipe_refresh)
- notificationList = requireViewById(R.id.recycler_view)
- setupRootViewInsets(notificationList)
- }
- override fun onInit() {
- notificationList.layoutManager =
- LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
- notificationList.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL))
- notificationList.adapter = notificationAdapter
- registerForContextMenu(notificationList)
- logicalSlotId = intent.getIntExtra("logicalSlotId", 0)
- // This is slightly different from the MainActivity logic
- // due to the length (we don't want to display the full USB product name)
- val channelTitle = if (logicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) {
- getString(R.string.usb)
- } else {
- getString(R.string.channel_name_format, logicalSlotId)
- }
- title = getString(R.string.profile_notifications_detailed_format, channelTitle)
- swipeRefresh.setOnRefreshListener {
- refresh()
- }
- refresh()
- }
- override fun onCreateOptionsMenu(menu: Menu?): Boolean {
- super.onCreateOptionsMenu(menu)
- menuInflater.inflate(R.menu.activity_notifications, menu)
- return true
- }
- override fun onOptionsItemSelected(item: MenuItem): Boolean =
- when (item.itemId) {
- android.R.id.home -> {
- finish()
- true
- }
- R.id.help -> {
- AlertDialog.Builder(this, R.style.AlertDialogTheme).apply {
- setMessage(R.string.profile_notifications_help)
- setPositiveButton(android.R.string.ok) { dialog, _ ->
- dialog.dismiss()
- }
- show()
- }
- true
- }
- else -> super.onOptionsItemSelected(item)
- }
- private fun launchTask(task: suspend () -> Unit) {
- swipeRefresh.isRefreshing = true
- lifecycleScope.launch {
- withContext(Dispatchers.IO) {
- euiccChannelManagerLoaded.await()
- }
- task()
- swipeRefresh.isRefreshing = false
- }
- }
- private fun refresh() {
- launchTask {
- notificationAdapter.notifications =
- euiccChannelManager.withEuiccChannel(logicalSlotId) { channel ->
- val profiles = channel.lpa.profiles
- channel.lpa.notifications.map {
- val profile = profiles.find { p -> p.iccid == it.iccid }
- LocalProfileNotificationWrapper(it, profile?.displayName ?: "???")
- }
- }
- }
- }
- data class LocalProfileNotificationWrapper(
- val inner: LocalProfileNotification,
- val profileName: String
- )
- @SuppressLint("ClickableViewAccessibility")
- inner class NotificationViewHolder(private val root: View):
- RecyclerView.ViewHolder(root), View.OnCreateContextMenuListener, OnMenuItemClickListener {
- private val address: TextView = root.requireViewById(R.id.notification_address)
- private val profileName: TextView = root.requireViewById(R.id.notification_profile_name)
- private lateinit var notification: LocalProfileNotificationWrapper
- private var lastTouchX = 0f
- private var lastTouchY = 0f
- init {
- root.isClickable = true
- root.setOnCreateContextMenuListener(this)
- root.setOnTouchListener { _, event ->
- lastTouchX = event.x
- lastTouchY = event.y
- false
- }
- root.setOnLongClickListener {
- root.showContextMenu(lastTouchX, lastTouchY)
- true
- }
- }
- private fun operationToLocalizedText(operation: LocalProfileNotification.Operation) =
- root.context.getText(
- when (operation) {
- LocalProfileNotification.Operation.Install -> R.string.profile_notification_operation_download
- LocalProfileNotification.Operation.Delete -> R.string.profile_notification_operation_delete
- LocalProfileNotification.Operation.Enable -> R.string.profile_notification_operation_enable
- LocalProfileNotification.Operation.Disable -> R.string.profile_notification_operation_disable
- })
- fun updateNotification(value: LocalProfileNotificationWrapper) {
- notification = value
- address.text = value.inner.notificationAddress
- profileName.text = Html.fromHtml(
- root.context.getString(R.string.profile_notification_name_format,
- operationToLocalizedText(value.inner.profileManagementOperation),
- value.profileName, value.inner.iccid),
- Html.FROM_HTML_MODE_COMPACT)
- }
- override fun onCreateContextMenu(
- menu: ContextMenu?,
- v: View?,
- menuInfo: ContextMenu.ContextMenuInfo?
- ) {
- menuInflater.inflate(R.menu.notification_options, menu)
- menu!!.forEach {
- it.setOnMenuItemClickListener(this)
- }
- }
- override fun onMenuItemClick(item: MenuItem): Boolean =
- when (item.itemId) {
- R.id.notification_process -> {
- launchTask {
- withContext(Dispatchers.IO) {
- euiccChannelManager.withEuiccChannel(logicalSlotId) { channel ->
- channel.lpa.handleNotification(notification.inner.seqNumber)
- }
- }
- refresh()
- }
- true
- }
- R.id.notification_delete -> {
- launchTask {
- withContext(Dispatchers.IO) {
- euiccChannelManager.withEuiccChannel(logicalSlotId) { channel ->
- channel.lpa.deleteNotification(notification.inner.seqNumber)
- }
- }
- refresh()
- }
- true
- }
- else -> false
- }
- }
- inner class NotificationAdapter: RecyclerView.Adapter<NotificationViewHolder>() {
- var notifications: List<LocalProfileNotificationWrapper> = listOf()
- @SuppressLint("NotifyDataSetChanged")
- set(value) {
- field = value
- notifyDataSetChanged()
- }
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NotificationViewHolder {
- val root = LayoutInflater.from(parent.context)
- .inflate(R.layout.notification_item, parent, false)
- return NotificationViewHolder(root)
- }
- override fun getItemCount(): Int = notifications.size
- override fun onBindViewHolder(holder: NotificationViewHolder, position: Int) =
- holder.updateNotification(notifications[position])
- }
- }
|