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.OpenEuiccApplication
  22. import im.angry.openeuicc.common.R
  23. import im.angry.openeuicc.core.EuiccChannel
  24. import im.angry.openeuicc.util.displayName
  25. import kotlinx.coroutines.Dispatchers
  26. import kotlinx.coroutines.launch
  27. import kotlinx.coroutines.withContext
  28. import net.typeblog.lpac_jni.LocalProfileNotification
  29. class NotificationsActivity: AppCompatActivity() {
  30. private lateinit var swipeRefresh: SwipeRefreshLayout
  31. private lateinit var notificationList: RecyclerView
  32. private val notificationAdapter = NotificationAdapter()
  33. private lateinit var euiccChannel: EuiccChannel
  34. override fun onCreate(savedInstanceState: Bundle?) {
  35. super.onCreate(savedInstanceState)
  36. setContentView(R.layout.activity_notifications)
  37. supportActionBar!!.setDisplayHomeAsUpEnabled(true)
  38. euiccChannel = (application as OpenEuiccApplication).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. }