@@ -39,9 +39,10 @@ data class PermissionState(val notifications: Boolean = false, val sms: Boolean
3939class ConnectionViewModel (application : Application ) : AndroidViewModel(application) {
4040 private val graph = MessageGraph .get(application)
4141 val settings = graph.settings.state
42+ val settingsLoaded = graph.settings.loaded
4243 val listenerConnected = ListenerState .connected
4344 private val mutableState = MutableStateFlow (settings.value.let {
44- ConnectionState (it.url, it.deviceCode, it.requireAck, authToken = it.authToken)
45+ ConnectionState (it.url, it.deviceCode, it.requireAck, authToken = it.authToken, busy = true )
4546 })
4647 val state = mutableState.asStateFlow()
4748 private val mutableQueue = MutableStateFlow (QueueSnapshot ())
@@ -55,6 +56,13 @@ class ConnectionViewModel(application: Application) : AndroidViewModel(applicati
5556 init {
5657 refreshPermissions()
5758 viewModelScope.launch {
59+ try {
60+ val saved = withContext(Dispatchers .IO ) { graph.settings.load() }
61+ mutableState.value = ConnectionState (saved.url, saved.deviceCode, saved.requireAck, authToken = saved.authToken)
62+ } catch (error: Exception ) {
63+ if (error is kotlinx.coroutines.CancellationException ) throw error
64+ mutableState.value = state.value.copy(notice = R .string.local_error)
65+ }
5866 graph.outbox.revision.collect {
5967 try {
6068 mutableQueue.value = withContext(Dispatchers .IO ) {
@@ -101,13 +109,24 @@ class ConnectionViewModel(application: Application) : AndroidViewModel(applicati
101109 }
102110 }
103111
112+ private var permissionRefresh: kotlinx.coroutines.Job ? = null
104113 fun refreshPermissions () {
105114 refreshApps()
106- val app = getApplication<Application >()
107- mutablePermissions.value = PermissionState (
108- notifications = app.packageName in NotificationManagerCompat .getEnabledListenerPackages(app),
109- sms = ContextCompat .checkSelfPermission(app, Manifest .permission.RECEIVE_SMS ) == PackageManager .PERMISSION_GRANTED ,
110- )
115+ permissionRefresh?.cancel()
116+ permissionRefresh = viewModelScope.launch {
117+ try {
118+ mutablePermissions.value = withContext(Dispatchers .IO ) {
119+ val app = getApplication<Application >()
120+ PermissionState (
121+ notifications = app.packageName in NotificationManagerCompat .getEnabledListenerPackages(app),
122+ sms = ContextCompat .checkSelfPermission(app, Manifest .permission.RECEIVE_SMS ) == PackageManager .PERMISSION_GRANTED ,
123+ )
124+ }
125+ } catch (error: Exception ) {
126+ if (error is kotlinx.coroutines.CancellationException ) throw error
127+ mutableState.value = state.value.copy(notice = R .string.local_error)
128+ }
129+ }
111130 }
112131
113132 fun setAuthToken (token : String ) { mutableState.value = state.value.copy(authToken = token, invalidToken = false , notice = null ) }
@@ -118,6 +137,7 @@ class ConnectionViewModel(application: Application) : AndroidViewModel(applicati
118137 fun setTestOptions (options : TestMessageOptions ) { mutableState.value = state.value.copy(testOptions = options) }
119138
120139 fun save (sendTest : Boolean ) {
140+ if (state.value.busy) return
121141 val draft = state.value
122142 val url = draft.url.trim()
123143 val code = draft.deviceCode.trim()
@@ -132,18 +152,18 @@ class ConnectionViewModel(application: Application) : AndroidViewModel(applicati
132152
133153 fun sendTest () = action(R .string.test_queued) { graph.enqueueTest() }
134154 fun notifications (enabled : Boolean ) = action { graph.settings.update { it.copy(notifications = enabled) } }
135- fun deduplicationWindow (seconds : Int ) = action {
155+ fun deduplicationWindow (seconds : Int ) = numberSetting( " deduplicationWindow " ) {
136156 graph.settings.update { it.copy(deduplicationWindowSeconds = seconds) }
137157 }
138158 fun smsDeduplication (enabled : Boolean ) = action { graph.settings.update { it.copy(smsDeduplication = enabled) } }
139159 fun notificationDeduplication (enabled : Boolean ) = action { graph.settings.update { it.copy(notificationDeduplication = enabled) } }
140160 fun forwardAvatars (enabled : Boolean ) = action { graph.settings.update { it.copy(forwardAvatars = enabled) } }
141161 fun forwardAppIcons (enabled : Boolean ) = action { graph.settings.update { it.copy(forwardAppIcons = enabled) } }
142162 fun imageLimits (enabled : Boolean ) = action { graph.settings.update { it.copy(imageLimitsEnabled = enabled) } }
143- fun imageMaxCount (value : Int ) = action { graph.settings.update { it.copy(imageMaxCount = value) } }
144- fun imageMaxEdge (value : Int ) = action { graph.settings.update { it.copy(imageMaxEdge = value) } }
145- fun imageMaxKiB (value : Int ) = action { graph.settings.update { it.copy(imageMaxKiB = value) } }
146- fun imageTotalKiB (value : Int ) = action { graph.settings.update { it.copy(imageTotalKiB = value) } }
163+ fun imageMaxCount (value : Int ) = numberSetting( " imageMaxCount " ) { graph.settings.update { it.copy(imageMaxCount = value) } }
164+ fun imageMaxEdge (value : Int ) = numberSetting( " imageMaxEdge " ) { graph.settings.update { it.copy(imageMaxEdge = value) } }
165+ fun imageMaxKiB (value : Int ) = numberSetting( " imageMaxKiB " ) { graph.settings.update { it.copy(imageMaxKiB = value) } }
166+ fun imageTotalKiB (value : Int ) = numberSetting( " imageTotalKiB " ) { graph.settings.update { it.copy(imageTotalKiB = value) } }
147167 fun imageFormat (format : NotificationImageFormat ) = action { graph.settings.update { it.copy(imageFormat = format) } }
148168 fun imageQuality (quality : Int ) = action { graph.settings.update { it.copy(imageQuality = quality) } }
149169 fun sms (enabled : Boolean ) = action { graph.settings.update { it.copy(sms = enabled) } }
@@ -175,13 +195,27 @@ class ConnectionViewModel(application: Application) : AndroidViewModel(applicati
175195 }
176196 fun delete (id : String ) = action { graph.outbox.delete(id); graph.diagnostics.record(DiagnosticEvent .EVENT_DELETED ) }
177197 fun clearError () = action { graph.settings.update { it.copy(captureFailed = false ) } }
178- fun rebind () {
198+ fun rebind () = action {
179199 NotificationListenerService .requestRebind(ComponentName (getApplication(), NotificationCaptureService ::class .java))
180200 }
201+
202+ private val numberSaves = mutableMapOf<String , kotlinx.coroutines.Job >()
203+ private fun numberSetting (key : String , block : () -> Unit ) {
204+ if (! graph.settings.loaded.value) return
205+ numberSaves.remove(key)?.cancel()
206+ numberSaves[key] = viewModelScope.launch {
207+ kotlinx.coroutines.delay(300 )
208+ try { withContext(Dispatchers .IO ) { block() } }
209+ catch (error: Exception ) {
210+ if (error is kotlinx.coroutines.CancellationException ) throw error
211+ mutableState.value = state.value.copy(notice = R .string.local_error)
212+ }
213+ }
214+ }
181215 fun showSettingsError () { mutableState.value = state.value.copy(notice = R .string.settings_unavailable) }
182216
183217 private fun action (notice : Int? = null, block : () -> Unit ) {
184- if (state.value.busy) return
218+ if (state.value.busy || ! graph.settings.loaded.value ) return
185219 mutableState.value = state.value.copy(busy = true , notice = null )
186220 viewModelScope.launch {
187221 val result = try {
0 commit comments