NotificationsActivity.kt 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. package im.angry.openeuicc.ui
  2. import android.annotation.SuppressLint
  3. import android.os.Bundle
  4. import android.text.Html
  5. import android.view.ContextMenu
  6. import android.view.LayoutInflater
  7. import android.view.Menu
  8. import android.view.MenuItem
  9. import android.view.MenuItem.OnMenuItemClickListener
  10. import android.view.View
  11. import android.view.ViewGroup
  12. import android.widget.TextView
  13. import androidx.appcompat.app.AlertDialog
  14. import androidx.appcompat.app.AppCompatActivity
  15. import androidx.core.view.forEach
  16. import androidx.lifecycle.lifecycleScope
  17. import androidx.recyclerview.widget.DividerItemDecoration
  18. import androidx.recyclerview.widget.LinearLayoutManager
  19. import androidx.recyclerview.widget.RecyclerView
  20. import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
  21. import im.angry.openeuicc.common.R
  22. import im.angry.openeuicc.core.EuiccChannel
  23. import im.angry.openeuicc.util.*
  24. import kotlinx.coroutines.Dispatchers
  25. import kotlinx.coroutines.launch
  26. import kotlinx.coroutines.withContext
  27. import net.typeblog.lpac_jni.LocalProfileNotification
  28. class NotificationsActivity: AppCompatActivity(), OpenEuiccUIContextMarker {
  29. private lateinit var swipeRefresh: SwipeRefreshLayout
  30. private lateinit var notificationList: RecyclerView
  31. private val notificationAdapter = NotificationAdapter()
  32. private lateinit var euiccChannel: EuiccChannel
  33. override fun onCreate(savedInstanceState: Bundle?) {
  34. super.onCreate(savedInstanceState)
  35. setContentView(R.layout.activity_notifications)
  36. setSupportActionBar(findViewById(R.id.toolbar))
  37. supportActionBar!!.setDisplayHomeAsUpEnabled(true)
  38. euiccChannel = euiccChannelManager
  39. .findEuiccChannelBySlotBlocking(intent.getIntExtra("logicalSlotId", 0))!!
  40. swipeRefresh = findViewById(R.id.swipe_refresh)
  41. notificationList = findViewById(R.id.recycler_view)
  42. notificationList.layoutManager =
  43. LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
  44. notificationList.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL))
  45. notificationList.adapter = notificationAdapter
  46. registerForContextMenu(notificationList)
  47. swipeRefresh.setOnRefreshListener {
  48. refresh()
  49. }
  50. refresh()
  51. }
  52. override fun onCreateOptionsMenu(menu: Menu?): Boolean {
  53. super.onCreateOptionsMenu(menu)
  54. menuInflater.inflate(R.menu.activity_notifications, menu)
  55. return true
  56. }
  57. override fun onOptionsItemSelected(item: MenuItem): Boolean =
  58. when (item.itemId) {
  59. android.R.id.home -> {
  60. finish()
  61. true
  62. }
  63. R.id.help -> {
  64. AlertDialog.Builder(this, R.style.AlertDialogTheme).apply {
  65. setMessage(R.string.profile_notifications_help)
  66. setPositiveButton(android.R.string.ok) { dialog, _ ->
  67. dialog.dismiss()
  68. }
  69. show()
  70. }
  71. true
  72. }
  73. else -> super.onOptionsItemSelected(item)
  74. }
  75. private fun launchTask(task: suspend () -> Unit) {
  76. swipeRefresh.isRefreshing = true
  77. lifecycleScope.launch {
  78. task()
  79. swipeRefresh.isRefreshing = false
  80. }
  81. }
  82. private fun refresh() {
  83. launchTask {
  84. val profiles = withContext(Dispatchers.IO) {
  85. euiccChannel.lpa.profiles
  86. }
  87. notificationAdapter.notifications =
  88. withContext(Dispatchers.IO) {
  89. euiccChannel.lpa.notifications.map {
  90. val profile = profiles.find { p -> p.iccid == it.iccid }
  91. LocalProfileNotificationWrapper(it, profile?.displayName ?: "???")
  92. }
  93. }
  94. }
  95. }
  96. data class LocalProfileNotificationWrapper(
  97. val inner: LocalProfileNotification,
  98. val profileName: String
  99. )
  100. @SuppressLint("ClickableViewAccessibility")
  101. inner class NotificationViewHolder(private val root: View):
  102. RecyclerView.ViewHolder(root), View.OnCreateContextMenuListener, OnMenuItemClickListener {
  103. private val address: TextView = root.findViewById(R.id.notification_address)
  104. private val profileName: TextView = root.findViewById(R.id.notification_profile_name)
  105. private lateinit var notification: LocalProfileNotificationWrapper
  106. private var lastTouchX = 0f
  107. private var lastTouchY = 0f
  108. init {
  109. root.isClickable = true
  110. root.setOnCreateContextMenuListener(this)
  111. root.setOnTouchListener { _, event ->
  112. lastTouchX = event.x
  113. lastTouchY = event.y
  114. false
  115. }
  116. root.setOnLongClickListener {
  117. root.showContextMenu(lastTouchX, lastTouchY)
  118. true
  119. }
  120. }
  121. private fun operationToLocalizedText(operation: LocalProfileNotification.Operation) =
  122. root.context.getText(
  123. when (operation) {
  124. LocalProfileNotification.Operation.Install -> R.string.profile_notification_operation_download
  125. LocalProfileNotification.Operation.Delete -> R.string.profile_notification_operation_delete
  126. LocalProfileNotification.Operation.Enable -> R.string.profile_notification_operation_enable
  127. LocalProfileNotification.Operation.Disable -> R.string.profile_notification_operation_disable
  128. })
  129. fun updateNotification(value: LocalProfileNotificationWrapper) {
  130. notification = value
  131. address.text = value.inner.notificationAddress
  132. profileName.text = Html.fromHtml(
  133. root.context.getString(R.string.profile_notification_name_format,
  134. operationToLocalizedText(value.inner.profileManagementOperation),
  135. value.profileName, value.inner.iccid),
  136. Html.FROM_HTML_MODE_COMPACT)
  137. }
  138. override fun onCreateContextMenu(
  139. menu: ContextMenu?,
  140. v: View?,
  141. menuInfo: ContextMenu.ContextMenuInfo?
  142. ) {
  143. menuInflater.inflate(R.menu.notification_options, menu)
  144. menu!!.forEach {
  145. it.setOnMenuItemClickListener(this)
  146. }
  147. }
  148. override fun onMenuItemClick(item: MenuItem): Boolean =
  149. when (item.itemId) {
  150. R.id.notification_process -> {
  151. launchTask {
  152. withContext(Dispatchers.IO) {
  153. euiccChannel.lpa.handleNotification(notification.inner.seqNumber)
  154. }
  155. }
  156. refresh()
  157. true
  158. }
  159. R.id.notification_delete -> {
  160. launchTask {
  161. withContext(Dispatchers.IO) {
  162. euiccChannel.lpa.deleteNotification(notification.inner.seqNumber)
  163. }
  164. }
  165. refresh()
  166. true
  167. }
  168. else -> false
  169. }
  170. }
  171. inner class NotificationAdapter: RecyclerView.Adapter<NotificationViewHolder>() {
  172. var notifications: List<LocalProfileNotificationWrapper> = listOf()
  173. @SuppressLint("NotifyDataSetChanged")
  174. set(value) {
  175. field = value
  176. notifyDataSetChanged()
  177. }
  178. override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NotificationViewHolder {
  179. val root = LayoutInflater.from(parent.context)
  180. .inflate(R.layout.notification_item, parent, false)
  181. return NotificationViewHolder(root)
  182. }
  183. override fun getItemCount(): Int = notifications.size
  184. override fun onBindViewHolder(holder: NotificationViewHolder, position: Int) =
  185. holder.updateNotification(notifications[position])
  186. }
  187. }