summaryrefslogtreecommitdiffhomepage
path: root/android/src
diff options
context:
space:
mode:
Diffstat (limited to 'android/src')
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/service/AccountCache.kt19
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/service/ForegroundNotificationManager.kt263
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadTileService.kt4
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt3
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/NotificationChannel.kt61
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/TunnelStateNotification.kt115
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/TunnelStateNotificationAction.kt54
-rw-r--r--android/src/main/kotlin/net/mullvad/talpid/util/EventNotifier.kt16
8 files changed, 286 insertions, 249 deletions
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/AccountCache.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/AccountCache.kt
index f63ca84f58..36e09b1721 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/AccountCache.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/AccountCache.kt
@@ -20,25 +20,16 @@ class AccountCache(val daemon: MullvadDaemon, val settingsListener: SettingsList
public val EXPIRY_FORMAT = DateTimeFormat.forPattern("YYYY-MM-dd HH:mm:ss z")
}
+ val onAccountNumberChange = EventNotifier<String?>(null)
+ val onAccountExpiryChange = EventNotifier<DateTime?>(null)
+
private val jobTracker = JobTracker()
- private var accountNumber: String? = null
- set(value) {
- field = value
- onAccountNumberChange.notify(value)
- }
-
- private var accountExpiry: DateTime? = null
- set(value) {
- field = value
- onAccountExpiryChange.notify(value)
- }
+ private var accountNumber by onAccountNumberChange.notifiable()
+ private var accountExpiry by onAccountExpiryChange.notifiable()
private var oldAccountExpiry: DateTime? = null
- val onAccountNumberChange = EventNotifier<String?>(null)
- val onAccountExpiryChange = EventNotifier<DateTime?>(null)
-
init {
settingsListener.accountNumberNotifier.subscribe(this) { accountNumber ->
handleNewAccountNumber(accountNumber)
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/ForegroundNotificationManager.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/ForegroundNotificationManager.kt
index 28d32c1c89..5312af91f9 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/ForegroundNotificationManager.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/ForegroundNotificationManager.kt
@@ -1,37 +1,24 @@
package net.mullvad.mullvadvpn.service
import android.app.KeyguardManager
-import android.app.Notification
-import android.app.NotificationChannel
-import android.app.NotificationManager
-import android.app.PendingIntent
import android.app.Service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
-import android.support.v4.app.NotificationCompat
-import net.mullvad.mullvadvpn.R
+import kotlin.properties.Delegates.observable
import net.mullvad.mullvadvpn.model.TunnelState
-import net.mullvad.mullvadvpn.ui.MainActivity
-import net.mullvad.talpid.tunnel.ActionAfterDisconnect
+import net.mullvad.mullvadvpn.service.notifications.TunnelStateNotification
import net.mullvad.talpid.util.EventNotifier
-
-val CHANNEL_ID = "vpn_tunnel_status"
-val FOREGROUND_NOTIFICATION_ID: Int = 1
-val KEY_CONNECT_ACTION = "net.mullvad.mullvadvpn.connect_action"
-val KEY_DISCONNECT_ACTION = "net.mullvad.mullvadvpn.disconnect_action"
+import net.mullvad.talpid.util.autoSubscribable
class ForegroundNotificationManager(
val service: MullvadVpnService,
val serviceNotifier: EventNotifier<ServiceInstance?>,
val keyguardManager: KeyguardManager
) {
- private val notificationManager =
- service.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
-
- private val badgeColor = service.resources.getColor(R.color.colorPrimary)
+ private val tunnelStateNotification = TunnelStateNotification(service)
private val deviceLockListener = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
@@ -43,177 +30,39 @@ class ForegroundNotificationManager(
}
}
- private var connectionProxy: ConnectionProxy? = null
- set(value) {
- if (field != value) {
- field?.onStateChange?.unsubscribe(this)
-
- value?.onStateChange?.subscribe(this) { state ->
- tunnelState = state
- }
+ private var accountNumberEvents by autoSubscribable<String?>(this, null) { accountNumber ->
+ loggedIn = accountNumber != null
+ }
- field = value
- }
+ private var tunnelStateEvents
+ by autoSubscribable<TunnelState>(this, TunnelState.Disconnected()) { newState ->
+ tunnelStateNotification.tunnelState = newState
+ updateNotification()
}
- private var settingsListener: SettingsListener? = null
- set(value) {
- if (field != value) {
- field?.accountNumberNotifier?.unsubscribe(this)
+ private var deviceIsUnlocked by observable(!keyguardManager.isDeviceLocked) { _, _, _ ->
+ updateNotificationAction()
+ }
- value?.accountNumberNotifier?.subscribe(this) { accountNumber ->
- loggedIn = accountNumber != null
- }
-
- field = value
- }
- }
+ private var loggedIn by observable(false) { _, _, _ -> updateNotificationAction() }
private var onForeground = false
- private var reconnecting = false
- private var showingReconnecting = false
-
- private var tunnelState: TunnelState = TunnelState.Disconnected()
- set(value) {
- field = value
-
- reconnecting =
- (value is TunnelState.Disconnecting &&
- value.actionAfterDisconnect == ActionAfterDisconnect.Reconnect) ||
- (value is TunnelState.Connecting && reconnecting)
-
- updateNotification()
- }
-
- private var deviceIsUnlocked = true
- set(value) {
- if (field != value) {
- field = value
- updateNotification()
- }
- }
- private var loggedIn = false
- set(value) {
- field = value
- updateNotification()
- }
+ private val tunnelState
+ get() = tunnelStateEvents?.latestEvent ?: TunnelState.Disconnected()
private val shouldBeOnForeground
get() = lockedToForeground || !(tunnelState is TunnelState.Disconnected)
- private val notificationText: Int
- get() {
- val state = tunnelState
-
- return when (state) {
- is TunnelState.Disconnected -> R.string.unsecured
- is TunnelState.Connecting -> {
- if (reconnecting) {
- R.string.reconnecting
- } else {
- R.string.connecting
- }
- }
- is TunnelState.Connected -> R.string.secured
- is TunnelState.Disconnecting -> {
- when (state.actionAfterDisconnect) {
- ActionAfterDisconnect.Reconnect -> R.string.reconnecting
- else -> R.string.disconnecting
- }
- }
- is TunnelState.Error -> {
- if (state.errorState.isBlocking) {
- R.string.blocking_all_connections
- } else {
- R.string.critical_error
- }
- }
- }
- }
-
- private val tunnelActionText: Int
- get() {
- val state = tunnelState
-
- return when (state) {
- is TunnelState.Disconnected -> R.string.connect
- is TunnelState.Connecting -> R.string.cancel
- is TunnelState.Connected -> R.string.disconnect
- is TunnelState.Disconnecting -> {
- when (state.actionAfterDisconnect) {
- ActionAfterDisconnect.Reconnect -> R.string.cancel
- else -> R.string.connect
- }
- }
- is TunnelState.Error -> {
- if (state.errorState.isBlocking) {
- R.string.disconnect
- } else {
- R.string.dismiss
- }
- }
- }
- }
-
- private val tunnelActionKey: String
- get() {
- val state = tunnelState
-
- return when (state) {
- is TunnelState.Disconnected -> KEY_CONNECT_ACTION
- is TunnelState.Connecting -> KEY_DISCONNECT_ACTION
- is TunnelState.Connected -> KEY_DISCONNECT_ACTION
- is TunnelState.Disconnecting -> {
- when (state.actionAfterDisconnect) {
- ActionAfterDisconnect.Reconnect -> KEY_DISCONNECT_ACTION
- else -> KEY_CONNECT_ACTION
- }
- }
- is TunnelState.Error -> KEY_DISCONNECT_ACTION
- }
- }
-
- private val tunnelActionIcon: Int
- get() {
- if (tunnelActionKey == KEY_CONNECT_ACTION) {
- return R.drawable.icon_notification_connect
- } else {
- return R.drawable.icon_notification_disconnect
- }
- }
-
- private val connectReceiver = object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- connectionProxy?.connect()
- }
- }
-
- private val disconnectReceiver = object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- connectionProxy?.disconnect()
- }
- }
-
- var lockedToForeground = false
- set(value) {
- field = value
- updateNotificationForegroundStatus()
- }
+ var lockedToForeground by observable(false) { _, _, _ -> updateNotification() }
init {
- if (Build.VERSION.SDK_INT >= 26) {
- initChannel()
- }
-
serviceNotifier.subscribe(this) { newServiceInstance ->
- connectionProxy = newServiceInstance?.connectionProxy
- settingsListener = newServiceInstance?.settingsListener
+ accountNumberEvents = newServiceInstance?.settingsListener?.accountNumberNotifier
+ tunnelStateEvents = newServiceInstance?.connectionProxy?.onStateChange
}
service.apply {
- registerReceiver(connectReceiver, IntentFilter(KEY_CONNECT_ACTION))
- registerReceiver(disconnectReceiver, IntentFilter(KEY_DISCONNECT_ACTION))
registerReceiver(deviceLockListener, IntentFilter().apply {
addAction(Intent.ACTION_USER_PRESENT)
addAction(Intent.ACTION_SCREEN_OFF)
@@ -225,40 +74,23 @@ class ForegroundNotificationManager(
fun onDestroy() {
serviceNotifier.unsubscribe(this)
- connectionProxy = null
- settingsListener = null
- service.apply {
- unregisterReceiver(connectReceiver)
- unregisterReceiver(disconnectReceiver)
- }
+ accountNumberEvents = null
+ tunnelStateEvents = null
- notificationManager.cancel(FOREGROUND_NOTIFICATION_ID)
- }
+ service.unregisterReceiver(deviceLockListener)
- private fun initChannel() {
- val channelName = service.getString(R.string.foreground_notification_channel_name)
- val importance = NotificationManager.IMPORTANCE_MIN
- val channel = NotificationChannel(CHANNEL_ID, channelName, importance).apply {
- description = service.getString(R.string.foreground_notification_channel_description)
- setShowBadge(true)
- }
-
- notificationManager.createNotificationChannel(channel)
+ tunnelStateNotification.visible = false
}
private fun updateNotification() {
- if (!reconnecting || !showingReconnecting) {
- notificationManager.notify(FOREGROUND_NOTIFICATION_ID, buildNotification())
- }
-
- updateNotificationForegroundStatus()
- }
-
- private fun updateNotificationForegroundStatus() {
if (shouldBeOnForeground != onForeground) {
if (shouldBeOnForeground) {
- service.startForeground(FOREGROUND_NOTIFICATION_ID, buildNotification())
+ service.startForeground(
+ TunnelStateNotification.NOTIFICATION_ID,
+ tunnelStateNotification.build()
+ )
+
onForeground = true
} else if (!shouldBeOnForeground) {
if (Build.VERSION.SDK_INT >= 24) {
@@ -272,40 +104,7 @@ class ForegroundNotificationManager(
}
}
- private fun buildNotification(): Notification {
- val intent = Intent(service, MainActivity::class.java)
- .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
- .setAction(Intent.ACTION_MAIN)
-
- val pendingIntent =
- PendingIntent.getActivity(service, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT)
-
- val builder = NotificationCompat.Builder(service, CHANNEL_ID)
- .setSmallIcon(R.drawable.small_logo_black)
- .setColor(badgeColor)
- .setContentTitle(service.getString(notificationText))
- .setContentIntent(pendingIntent)
-
- if (loggedIn && deviceIsUnlocked) {
- builder.addAction(buildTunnelAction())
- }
-
- return builder.build()
- }
-
- private fun buildTunnelAction(): NotificationCompat.Action {
- val intent = Intent(tunnelActionKey).setPackage("net.mullvad.mullvadvpn")
- val flags = PendingIntent.FLAG_UPDATE_CURRENT
-
- val pendingIntent = if (Build.VERSION.SDK_INT >= 26) {
- PendingIntent.getForegroundService(service, 1, intent, flags)
- } else {
- PendingIntent.getService(service, 1, intent, flags)
- }
-
- val icon = tunnelActionIcon
- val label = service.getString(tunnelActionText)
-
- return NotificationCompat.Action(icon, label, pendingIntent)
+ private fun updateNotificationAction() {
+ tunnelStateNotification.showAction = loggedIn && deviceIsUnlocked
}
}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadTileService.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadTileService.kt
index 4a9562c6ec..f216e69726 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadTileService.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadTileService.kt
@@ -57,9 +57,9 @@ class MullvadTileService : TileService() {
val intent = Intent(this, MullvadVpnService::class.java)
if (secured) {
- intent.action = KEY_DISCONNECT_ACTION
+ intent.action = MullvadVpnService.KEY_DISCONNECT_ACTION
} else {
- intent.action = KEY_CONNECT_ACTION
+ intent.action = MullvadVpnService.KEY_CONNECT_ACTION
}
if (Build.VERSION.SDK_INT >= 26) {
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt
index 6952494f04..0998ddcc1c 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt
@@ -23,6 +23,9 @@ private const val RELAYS_FILE = "relays.json"
class MullvadVpnService : TalpidVpnService() {
companion object {
private val TAG = "mullvad"
+
+ val KEY_CONNECT_ACTION = "net.mullvad.mullvadvpn.connect_action"
+ val KEY_DISCONNECT_ACTION = "net.mullvad.mullvadvpn.disconnect_action"
}
private enum class PendingAction {
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/NotificationChannel.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/NotificationChannel.kt
new file mode 100644
index 0000000000..2546c8955f
--- /dev/null
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/NotificationChannel.kt
@@ -0,0 +1,61 @@
+package net.mullvad.mullvadvpn.service.notifications
+
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.content.Context
+import android.os.Build
+import android.support.v4.app.NotificationCompat
+import net.mullvad.mullvadvpn.R
+
+class NotificationChannel(
+ val context: Context,
+ val id: String,
+ val name: Int,
+ val description: Int,
+ val importance: Int
+) {
+ private val badgeColor by lazy {
+ context.resources.getColor(R.color.colorPrimary)
+ }
+
+ val notificationManager =
+ context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+
+ init {
+ if (Build.VERSION.SDK_INT >= 26) {
+ val channelName = context.getString(name)
+ val channelDescription = context.getString(description)
+
+ val channel = NotificationChannel(id, channelName, importance).apply {
+ description = channelDescription
+ setShowBadge(true)
+ }
+
+ notificationManager.createNotificationChannel(channel)
+ }
+ }
+
+ fun buildNotification(intent: PendingIntent, title: Int): Notification {
+ return buildNotification(intent, title, emptyList())
+ }
+
+ fun buildNotification(
+ pendingIntent: PendingIntent,
+ title: Int,
+ actions: List<NotificationCompat.Action>
+ ): Notification {
+ val builder = NotificationCompat.Builder(context, id)
+ .setSmallIcon(R.drawable.small_logo_black)
+ .setColor(badgeColor)
+ .setContentTitle(context.getString(title))
+ .setContentIntent(pendingIntent)
+
+ for (action in actions) {
+ builder.addAction(action)
+ }
+
+ return builder.build()
+ }
+}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/TunnelStateNotification.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/TunnelStateNotification.kt
new file mode 100644
index 0000000000..e91908f088
--- /dev/null
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/TunnelStateNotification.kt
@@ -0,0 +1,115 @@
+package net.mullvad.mullvadvpn.service.notifications
+
+import android.app.Notification
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import android.support.v4.app.NotificationCompat
+import kotlin.properties.Delegates.observable
+import net.mullvad.mullvadvpn.R
+import net.mullvad.mullvadvpn.model.TunnelState
+import net.mullvad.mullvadvpn.ui.MainActivity
+import net.mullvad.talpid.tunnel.ActionAfterDisconnect
+
+class TunnelStateNotification(val context: Context) {
+ companion object {
+ val NOTIFICATION_ID: Int = 1
+ }
+
+ private val channel = NotificationChannel(
+ context,
+ "vpn_tunnel_status",
+ R.string.foreground_notification_channel_name,
+ R.string.foreground_notification_channel_description,
+ NotificationManager.IMPORTANCE_MIN
+ )
+
+ private val notificationText: Int
+ get() = when (val state = tunnelState) {
+ is TunnelState.Disconnected -> R.string.unsecured
+ is TunnelState.Connecting -> {
+ if (reconnecting) {
+ R.string.reconnecting
+ } else {
+ R.string.connecting
+ }
+ }
+ is TunnelState.Connected -> R.string.secured
+ is TunnelState.Disconnecting -> {
+ when (state.actionAfterDisconnect) {
+ ActionAfterDisconnect.Reconnect -> R.string.reconnecting
+ else -> R.string.disconnecting
+ }
+ }
+ is TunnelState.Error -> {
+ if (state.errorState.isBlocking) {
+ R.string.blocking_all_connections
+ } else {
+ R.string.critical_error
+ }
+ }
+ }
+
+ private var reconnecting = false
+ private var showingReconnecting = false
+
+ var showAction by observable(false) { _, _, _ -> update() }
+
+ var tunnelState by observable<TunnelState>(TunnelState.Disconnected()) { _, _, newState ->
+ reconnecting =
+ (newState is TunnelState.Disconnecting &&
+ newState.actionAfterDisconnect == ActionAfterDisconnect.Reconnect) ||
+ (newState is TunnelState.Connecting && reconnecting)
+
+ update()
+ }
+
+ var visible by observable(true) { _, _, newValue ->
+ if (newValue == true) {
+ update()
+ } else {
+ channel.notificationManager.cancel(NOTIFICATION_ID)
+ }
+ }
+
+ private fun update() {
+ if (visible && (!reconnecting || !showingReconnecting)) {
+ channel.notificationManager.notify(NOTIFICATION_ID, build())
+ }
+ }
+
+ fun build(): Notification {
+ val intent = Intent(context, MainActivity::class.java)
+ .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
+ .setAction(Intent.ACTION_MAIN)
+
+ val pendingIntent =
+ PendingIntent.getActivity(context, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT)
+
+ val actions = if (showAction) {
+ listOf(buildAction())
+ } else {
+ emptyList()
+ }
+
+ return channel.buildNotification(pendingIntent, notificationText, actions)
+ }
+
+ private fun buildAction(): NotificationCompat.Action {
+ val action = TunnelStateNotificationAction.from(tunnelState)
+ val label = context.getString(action.text)
+
+ val intent = Intent(action.key).setPackage("net.mullvad.mullvadvpn")
+ val flags = PendingIntent.FLAG_UPDATE_CURRENT
+
+ val pendingIntent = if (Build.VERSION.SDK_INT >= 26) {
+ PendingIntent.getForegroundService(context, 1, intent, flags)
+ } else {
+ PendingIntent.getService(context, 1, intent, flags)
+ }
+
+ return NotificationCompat.Action(action.icon, label, pendingIntent)
+ }
+}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/TunnelStateNotificationAction.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/TunnelStateNotificationAction.kt
new file mode 100644
index 0000000000..714264efbf
--- /dev/null
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/TunnelStateNotificationAction.kt
@@ -0,0 +1,54 @@
+package net.mullvad.mullvadvpn.service.notifications
+
+import net.mullvad.mullvadvpn.R
+import net.mullvad.mullvadvpn.model.TunnelState
+import net.mullvad.mullvadvpn.service.MullvadVpnService
+import net.mullvad.talpid.tunnel.ActionAfterDisconnect
+
+enum class TunnelStateNotificationAction {
+ Connect,
+ Disconnect,
+ Cancel,
+ Dismiss;
+
+ companion object {
+ fun from(tunnelState: TunnelState) = when (tunnelState) {
+ is TunnelState.Disconnected -> Connect
+ is TunnelState.Connecting -> Cancel
+ is TunnelState.Connected -> Disconnect
+ is TunnelState.Disconnecting -> {
+ when (tunnelState.actionAfterDisconnect) {
+ ActionAfterDisconnect.Reconnect -> Cancel
+ else -> Connect
+ }
+ }
+ is TunnelState.Error -> {
+ if (tunnelState.errorState.isBlocking) {
+ Disconnect
+ } else {
+ Dismiss
+ }
+ }
+ }
+ }
+
+ val text
+ get() = when (this) {
+ Connect -> R.string.connect
+ Disconnect -> R.string.disconnect
+ Cancel -> R.string.cancel
+ Dismiss -> R.string.dismiss
+ }
+
+ val key
+ get() = when (this) {
+ Connect -> MullvadVpnService.KEY_CONNECT_ACTION
+ else -> MullvadVpnService.KEY_DISCONNECT_ACTION
+ }
+
+ val icon
+ get() = when (this) {
+ Connect -> R.drawable.icon_notification_connect
+ else -> R.drawable.icon_notification_disconnect
+ }
+}
diff --git a/android/src/main/kotlin/net/mullvad/talpid/util/EventNotifier.kt b/android/src/main/kotlin/net/mullvad/talpid/util/EventNotifier.kt
index 395146b168..405722f010 100644
--- a/android/src/main/kotlin/net/mullvad/talpid/util/EventNotifier.kt
+++ b/android/src/main/kotlin/net/mullvad/talpid/util/EventNotifier.kt
@@ -13,7 +13,8 @@ import kotlin.properties.Delegates.observable
class EventNotifier<T>(private val initialValue: T) {
private val listeners = HashMap<Any, (T) -> Unit>()
- private var latestEvent = initialValue
+ var latestEvent = initialValue
+ private set
fun notify(event: T) {
synchronized(this) {
@@ -54,3 +55,16 @@ class EventNotifier<T>(private val initialValue: T) {
notify(newValue)
}
}
+
+fun <T> autoSubscribable(id: Any, fallback: T, listener: (T) -> Unit) =
+ observable<EventNotifier<T>?>(null) { _, old, new ->
+ if (old != new) {
+ old?.unsubscribe(id)
+
+ if (new == null) {
+ listener.invoke(fallback)
+ } else {
+ new.subscribe(id, listener)
+ }
+ }
+ }