Skip to content

Commit

Permalink
calendar kmp initial feature, kmp shared prefs
Browse files Browse the repository at this point in the history
  • Loading branch information
crc-32 committed Jul 1, 2024
1 parent 37c5c23 commit 6c019d5
Show file tree
Hide file tree
Showing 63 changed files with 6,079 additions and 6,065 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import io.rebble.cobble.di.AppComponent
import io.rebble.cobble.di.DaggerAppComponent
import io.rebble.cobble.log.AppTaggedDebugTree
import io.rebble.cobble.log.FileLoggingTree
import io.rebble.cobble.shared.database.closeDatabase
import io.rebble.cobble.shared.di.initKoin
import timber.log.Timber
import kotlin.system.exitProcess

Expand All @@ -22,6 +24,7 @@ class CobbleApplication : FlutterApplication() {
super.onCreate()

initLogging()
initKoin(applicationContext)

component.initNotificationChannels()
component.initLibPebbleCommonServices()
Expand Down
6 changes: 6 additions & 0 deletions android/app/src/main/kotlin/io/rebble/cobble/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import io.rebble.cobble.datasources.PermissionChangeBus
import io.rebble.cobble.notifications.NotificationListener
import io.rebble.cobble.service.CompanionDeviceService
import io.rebble.cobble.service.InCallService
import io.rebble.cobble.shared.database.closeDatabase
import io.rebble.cobble.util.hasNotificationAccessPermission
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.plus
Expand Down Expand Up @@ -124,6 +125,11 @@ class MainActivity : FlutterActivity() {
handleIntent(intent)
}

override fun onDestroy() {
closeDatabase()
super.onDestroy()
}

/**
* Start the CompanionDeviceService and InCallService
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ class CalendarSyncWorker(appContext: Context, params: WorkerParameters) : Corout
return Result.success()
}

return if (component.createCalendarFlutterBridge().syncCalendar()) {
return if (component.createKMPCalendarSync().doFullCalendarSync()) {
Timber.d("Success")
Result.success()
} else {
Timber.d("Flutter failure")
Timber.d("KMP failure")
Result.retry()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.annotation.RequiresPermission
import io.rebble.cobble.bluetooth.classic.ReconnectionSocketServer
import io.rebble.cobble.shared.domain.common.PebbleDevice
import io.rebble.cobble.shared.domain.state.ConnectionState
import io.rebble.cobble.shared.domain.state.ConnectionStateManager
import io.rebble.cobble.shared.domain.state.watchOrNull
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
Expand All @@ -26,9 +27,9 @@ class ConnectionLooper @Inject constructor(
private val errorHandler: CoroutineExceptionHandler
) {
val connectionState: StateFlow<ConnectionState> get() = _connectionState
private val _connectionState: MutableStateFlow<ConnectionState> = MutableStateFlow(
private val _connectionState: MutableStateFlow<ConnectionState> = /*MutableStateFlow(
ConnectionState.Disconnected
)
)*/ ConnectionStateManager.connectionState
private val _watchPresenceState = MutableStateFlow<String?>(null)
val watchPresenceState: StateFlow<String?> get() = _watchPresenceState

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package io.rebble.cobble.bluetooth
import io.rebble.cobble.pigeons.Pigeons

@Throws(SecurityException::class)
fun BluePebbleDevice.toPigeon(): Pigeons.PebbleScanDevicePigeon {
fun ScannedPebbleDevice.toPigeon(): Pigeons.PebbleScanDevicePigeon {
return Pigeons.PebbleScanDevicePigeon().also {
it.name = bluetoothDevice.name
it.address = bluetoothDevice.address
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import android.bluetooth.BluetoothDevice
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanResult
import androidx.annotation.RequiresPermission
import io.rebble.cobble.bluetooth.BluePebbleDevice
import io.rebble.cobble.bluetooth.ScannedPebbleDevice
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.Channel
Expand All @@ -25,15 +25,15 @@ class BleScanner @Inject constructor() {
private var stopTrigger: CompletableDeferred<Unit>? = null

@RequiresPermission(allOf = [android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.BLUETOOTH_CONNECT])
fun getScanFlow(): Flow<List<BluePebbleDevice>> = flow {
fun getScanFlow(): Flow<List<ScannedPebbleDevice>> = flow {
coroutineScope {
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
?: throw BluetoothNotSupportedException("Device does not have a bluetooth adapter")

val leScanner = bluetoothAdapter.bluetoothLeScanner
val callback = ScanCallbackChannel()

var foundDevices = emptyList<BluePebbleDevice>()
var foundDevices = emptyList<ScannedPebbleDevice>()

val stopTrigger = CompletableDeferred<Unit>()
this@BleScanner.stopTrigger = stopTrigger
Expand All @@ -54,12 +54,12 @@ class BleScanner @Inject constructor() {
device.name.startsWith("Pebble-LE"))) {
val i = foundDevices.indexOfFirst { it.bluetoothDevice.address == device.address }
if (i < 0) {
val bluePebbleDevice = BluePebbleDevice(result)
foundDevices = foundDevices + bluePebbleDevice
val scannedPebbleDevice = ScannedPebbleDevice(result)
foundDevices = foundDevices + scannedPebbleDevice
emit(foundDevices)
} else if (foundDevices[i].leMeta?.color == null) {
val fd = foundDevices as MutableList
fd[i] = BluePebbleDevice(result)
fd[i] = ScannedPebbleDevice(result)
foundDevices = fd
emit(foundDevices)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import android.bluetooth.BluetoothDevice
import android.content.Context
import android.content.IntentFilter
import androidx.annotation.RequiresPermission
import io.rebble.cobble.bluetooth.BluePebbleDevice
import io.rebble.cobble.bluetooth.ScannedPebbleDevice
import io.rebble.cobble.util.coroutines.asFlow
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.ExperimentalCoroutinesApi
Expand All @@ -25,12 +25,12 @@ class ClassicScanner @Inject constructor(private val context: Context) {
private var stopTrigger: CompletableDeferred<Unit>? = null

@RequiresPermission(allOf = [android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.BLUETOOTH_CONNECT])
fun getScanFlow(): Flow<List<BluePebbleDevice>> = flow {
fun getScanFlow(): Flow<List<ScannedPebbleDevice>> = flow {
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
?: throw BluetoothNotSupportedException("Device does not have a bluetooth adapter")

coroutineScope {
var deviceList = emptyList<BluePebbleDevice>()
var deviceList = emptyList<ScannedPebbleDevice>()
val stopTrigger = CompletableDeferred<Unit>()
this@ClassicScanner.stopTrigger = stopTrigger

Expand Down Expand Up @@ -62,7 +62,7 @@ class ClassicScanner @Inject constructor(private val context: Context) {
it.bluetoothDevice.address == device.address
}
) {
deviceList = deviceList + BluePebbleDevice(device)
deviceList = deviceList + ScannedPebbleDevice(device)
emit(deviceList)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,10 @@ class BackgroundTimelineFlutterBridge @Inject constructor(
}

fun syncTimelineToWatchNow() {
GlobalScope.launch(Dispatchers.Main.immediate) {
getTimelineSyncCallbacks()?.syncTimelineToWatch { }
}
//TODO: Reimplement as KMP
//GlobalScope.launch(Dispatchers.Main.immediate) {
// getTimelineSyncCallbacks()?.syncTimelineToWatch { }
//}
}

suspend fun handleTimelineAction(
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,10 @@ class TimelineSyncFlutterBridge @Inject constructor(
}

fun syncTimelineToWatchNow() {
GlobalScope.launch(Dispatchers.Main.immediate) {
getTimelineSyncCallbacks()?.syncTimelineToWatch { }
}
//TODO: Reimplement as KMP
//GlobalScope.launch(Dispatchers.Main.immediate) {
// getTimelineSyncCallbacks()?.syncTimelineToWatch { }
//}
}

private suspend fun getTimelineSyncCallbacks(): Pigeons.TimelineCallbacks? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,22 @@ package io.rebble.cobble.bridges.ui

import io.rebble.cobble.bluetooth.ConnectionLooper
import io.rebble.cobble.bridges.FlutterBridge
import io.rebble.cobble.bridges.background.CalendarFlutterBridge
import io.rebble.cobble.pigeons.Pigeons
import io.rebble.cobble.shared.datastore.KMPPrefs
import io.rebble.cobble.shared.domain.calendar.CalendarSync
import io.rebble.cobble.shared.domain.state.ConnectionState
import io.rebble.cobble.util.Debouncer
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject

class CalendarControlFlutterBridge @Inject constructor(
private val connectionLooper: ConnectionLooper,
private val calendarFlutterBridge: CalendarFlutterBridge,
private val calendarSync: CalendarSync,
private val coroutineScope: CoroutineScope,
private val kmpPrefs: KMPPrefs,
bridgeLifecycleController: BridgeLifecycleController
) : Pigeons.CalendarControl, FlutterBridge {
private val debouncer = Debouncer(debouncingTimeMs = 5_000L, scope = coroutineScope)
Expand All @@ -34,7 +38,31 @@ class CalendarControlFlutterBridge @Inject constructor(
// many sync requests
debouncer.executeDebouncing {
Timber.d("Sync calendar on request after debounce")
calendarFlutterBridge.syncCalendar()
calendarSync.doFullCalendarSync()
}
}

override fun getCalendarSyncEnabled(result: Pigeons.Result<Boolean>) {
coroutineScope.launch {
kmpPrefs.calendarSyncEnabled.first().let {
result.success(it)
}
}
}

override fun deleteAllCalendarPins(result: Pigeons.Result<Void>) {
coroutineScope.launch {
calendarSync.deleteCalendarPinsFromWatch()
result.success(null)
}
}

override fun setCalendarSyncEnabled(enabled: Boolean, result: Pigeons.Result<Void>) {
coroutineScope.launch {
kmpPrefs.setCalendarSyncEnabled(enabled)
result.success(null)
}
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@ fun WatchVersion.WatchVersionResponse?.toPigeon(
// Pigeon does not appear to allow null values. We have to set some dummy values instead

return Pigeons.PebbleDevicePigeon().also {
it.name = if (btDevice is EmulatedPebbleDevice) "[Emulator]" else if (btDevice is BluetoothPebbleDevice) btDevice.bluetoothDevice.name.orEmpty() else error("Unknown PebbleDevice type")
it.address = btDevice.address
it.name = btDevice?.let {
if (btDevice is EmulatedPebbleDevice) "[Emulator]"
else if (btDevice is BluetoothPebbleDevice) btDevice.bluetoothDevice.name.orEmpty()
else error("Unknown PebbleDevice type")
} ?: ""
it.address = btDevice?.address ?: ""
it.runningFirmware = this?.running?.toPigeon() ?: blankWatchFirwmareVersion()
it.recoveryFirmware = this?.recovery?.toPigeon() ?: blankWatchFirwmareVersion()
it.model = model?.toLong() ?: 0L
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.rebble.cobble.datasources

import android.content.SharedPreferences
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.first

@OptIn(ExperimentalCoroutinesApi::class)
inline fun <T> SharedPreferences.flow(
key: String,
crossinline mapper: (preferences: SharedPreferences, key: String) -> T): Flow<T> {

return callbackFlow {
trySend(mapper(this@flow, key)).isSuccess

val listener = SharedPreferences
.OnSharedPreferenceChangeListener { sharedPreferences: SharedPreferences,
changedKey: String? ->

if (changedKey == key) {
trySend(mapper(sharedPreferences, key)).isSuccess
}
}

registerOnSharedPreferenceChangeListener(listener)

awaitClose {
unregisterOnSharedPreferenceChangeListener(listener)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,6 @@ class FlutterPreferences @Inject constructor(private val context: Context) {
Context.MODE_PRIVATE
)

val calendarSyncEnabled = preferences.flow(KEY_CALENDAR_SYNC_ENABLED) { prefs: SharedPreferences,
key: String ->
prefs.getBoolean(key, false)
}

val mutePhoneNotificationSounds = preferences.flow(
KEY_MUTE_PHONE_NOTIFICATION_SOUNDS
) { prefs: SharedPreferences,
Expand Down Expand Up @@ -79,32 +74,6 @@ private fun decodeList(encodedList: String): List<String>? {
}
}

@OptIn(ExperimentalCoroutinesApi::class)
private inline fun <T> SharedPreferences.flow(
key: String,
crossinline mapper: (preferences: SharedPreferences, key: String) -> T): Flow<T> {

return callbackFlow {
trySend(mapper(this@flow, key)).isSuccess

val listener = SharedPreferences
.OnSharedPreferenceChangeListener { sharedPreferences: SharedPreferences,
changedKey: String? ->

if (changedKey == key) {
trySend(mapper(sharedPreferences, key)).isSuccess
}
}

registerOnSharedPreferenceChangeListener(listener)

awaitClose {
unregisterOnSharedPreferenceChangeListener(listener)
}
}
}

private const val KEY_CALENDAR_SYNC_ENABLED = "flutter.ENABLE_CALENDAR_SYNC"
private const val KEY_MUTE_PHONE_NOTIFICATION_SOUNDS = "flutter.MUTE_PHONE_NOTIFICATIONS"
private const val KEY_MUTE_PHONE_CALL_SOUNDS = "flutter.MUTE_PHONE_CALLS"
private const val KEY_MASTER_NOTIFICATION_TOGGLE = "flutter.MASTER_NOTIFICATION_TOGGLE"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import java.util.UUID
import javax.inject.Inject
import javax.inject.Singleton

//TODO: Consolidate this with the shared ConnectionStateManager
@Singleton
class WatchMetadataStore @Inject constructor() {
val lastConnectedWatchMetadata = MutableStateFlow<WatchVersion.WatchVersionResponse?>(null)
Expand Down
Loading

0 comments on commit 6c019d5

Please sign in to comment.