Skip to content

Commit

Permalink
apply watchface support
Browse files Browse the repository at this point in the history
  • Loading branch information
crc-32 committed Sep 19, 2024
1 parent 7f7dec6 commit b83d06e
Show file tree
Hide file tree
Showing 19 changed files with 82 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,7 @@ class ConnectionLooper @Inject constructor(
if (BluetoothAdapter.getDefaultAdapter()?.isEnabled != true) {
Timber.d("Bluetooth is off. Waiting until it is on Cancel connection attempt.")

_connectionState.value = ConnectionState.WaitingForTransport(
BluetoothAdapter.getDefaultAdapter()?.getRemoteDevice(macAddress)?.let { BluetoothPebbleDevice(it, it.address) }
)
_connectionState.value = ConnectionState.WaitingForTransport(macAddress)

getBluetoothStatus(context).first { bluetoothOn -> bluetoothOn }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ class DeviceTransport @Inject constructor(
lastMacAddress = macAddress

val bluetoothDevice = if (BuildConfig.DEBUG && !macAddress.contains(":")) {
EmulatedPebbleDevice(macAddress)
EmulatedPebbleDevice(macAddress, protocolHandler)
} else {
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
BluetoothPebbleDevice(bluetoothAdapter.getRemoteDevice(macAddress), macAddress)
BluetoothPebbleDevice(bluetoothAdapter.getRemoteDevice(macAddress), protocolHandler, macAddress)
}

val driver = getTargetTransport(bluetoothDevice)
Expand Down

This file was deleted.

12 changes: 0 additions & 12 deletions android/app/src/main/kotlin/io/rebble/cobble/di/LibPebbleModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,6 @@ abstract class LibPebbleModule {
protocolHandler: ProtocolHandler
) = AppMessageService(protocolHandler)

@Provides
@Singleton
fun provideAppRunStateService(
protocolHandler: ProtocolHandler
) = AppRunStateService(protocolHandler)

@Provides
@Singleton
fun provideSystemService(
Expand Down Expand Up @@ -123,12 +117,6 @@ abstract class LibPebbleModule {
@IntoSet
abstract fun bindAppMessageServiceIntoSet(appMessageService: AppMessageService): ProtocolService

@Binds
@IntoSet
abstract fun bindAppRunStateServiceIntoSet(
appRunStateService: AppRunStateService
): ProtocolService

@Binds
@IntoSet
abstract fun bindSystemServiceIntoSet(systemService: SystemService): ProtocolService
Expand Down
10 changes: 3 additions & 7 deletions android/app/src/main/kotlin/io/rebble/cobble/di/ServiceModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,15 @@ abstract class ServiceModule {
}
}

//TODO: Move to per-protocol handler services
/*
@Binds
@IntoSet
@Named("normal")
abstract fun bindAppMessageHandlerIntoSet(
appMessageHandler: AppMessageHandler
): CobbleHandler
*/

@Binds
@IntoSet
Expand Down Expand Up @@ -79,11 +82,4 @@ abstract class ServiceModule {
abstract fun bindAppInstallHandlerIntoSet(
appInstallHandler: AppInstallHandler
): CobbleHandler

@Binds
@IntoSet
@Named("normal")
abstract fun bindAppRunStateHandler(
appRunStateHandler: AppRunStateHandler
): CobbleHandler
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,6 @@ abstract class CommonBridgesModule {
appInstallFlutterBridge: AppInstallFlutterBridge
): FlutterBridge

@Binds
@IntoSet
@CommonBridge
abstract fun bindAppLifecycleBridge(
appLifecycleFlutterBridge: AppLifecycleFlutterBridge
): FlutterBridge

@Binds
@IntoSet
@CommonBridge
Expand Down
2 changes: 1 addition & 1 deletion android/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ kotlin = "2.0.20-Beta2"
kotlinxDatetime = "0.6.0"
kotlinxSerializationJson = "1.7.1"
ksp = "2.0.20-Beta2-1.0.23"
libpebblecommonVersion = "0.1.22"
libpebblecommonVersion = "0.1.23"
errorproneVersion = "2.26.1"
spotbugsVersion = "4.8.6"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.Manifest
import android.bluetooth.BluetoothDevice
import androidx.annotation.RequiresPermission
import io.rebble.cobble.shared.domain.common.PebbleDevice
import io.rebble.libpebblecommon.ProtocolHandler
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow

Expand All @@ -15,8 +16,9 @@ interface BlueIO {

class BluetoothPebbleDevice(
val bluetoothDevice: BluetoothDevice,
protocolHandler: ProtocolHandler,
address: String
) : PebbleDevice(null, address){
) : PebbleDevice(null, protocolHandler, address){

override fun toString(): String {
val start = "< BluetoothPebbleDevice, address=$address, bluetoothDevice=< BluetoothDevice address=${bluetoothDevice.address}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package io.rebble.cobble.bluetooth

import android.bluetooth.BluetoothDevice
import io.rebble.cobble.shared.domain.common.PebbleDevice
import io.rebble.libpebblecommon.ProtocolHandler

class EmulatedPebbleDevice(
address: String
) : PebbleDevice(null, address){
address: String,
protocolHandler: ProtocolHandler
) : PebbleDevice(null, protocolHandler, address){

override fun toString(): String {
return "< EmulatedPebbleDevice, address=$address >"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import io.rebble.libpebblecommon.ProtocolHandlerImpl
import io.rebble.libpebblecommon.ProtocolHandler
import io.rebble.libpebblecommon.services.AppFetchService
import io.rebble.libpebblecommon.services.PutBytesService
import io.rebble.libpebblecommon.services.app.AppRunStateService
import org.koin.dsl.module
import io.rebble.libpebblecommon.services.blobdb.TimelineService

val libpebbleModule = module {
//TODO: Move away from global protocol handler and singleton services
single<ProtocolHandler> {
ProtocolHandlerImpl()
}
Expand All @@ -25,4 +27,8 @@ val libpebbleModule = module {
single {
PutBytesController()
}

factory { params ->
AppRunStateService(params.get())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ val stateModule = module {
.filterNotNull()
.stateIn(CoroutineScope(Dispatchers.Default), SharingStarted.WhileSubscribed(), null)
}
factory(named("isConnected")) {
get<StateFlow<ConnectionState>>(named("connectionState"))
.map { it is ConnectionState.Connected }
.stateIn(CoroutineScope(Dispatchers.Default), SharingStarted.WhileSubscribed(), false)
}
single(named("connectionScope")) {
MutableStateFlow<CoroutineScope>(CoroutineScope(EmptyCoroutineContext))
} bind StateFlow::class
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
package io.rebble.cobble.shared.domain.common

import io.ktor.http.parametersOf
import io.rebble.libpebblecommon.ProtocolHandler
import io.rebble.libpebblecommon.packets.WatchVersion
import io.rebble.libpebblecommon.services.app.AppRunStateService
import kotlinx.coroutines.flow.MutableStateFlow
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.koin.core.parameter.parametersOf

open class PebbleDevice(
metadata: WatchVersion.WatchVersionResponse?,
private val protocolHandler: ProtocolHandler,
val address: String
) {
): KoinComponent {
val metadata: MutableStateFlow<WatchVersion.WatchVersionResponse?> = MutableStateFlow(metadata)

override fun toString(): String = "< PebbleDevice address=$address >"

//TODO: Move to per-protocol handler services, so we can have multiple PebbleDevices, this is the first of many
val appRunStateService: AppRunStateService by inject {parametersOf(protocolHandler)}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import org.koin.core.qualifier.named

open class ConnectionState {
object Disconnected : ConnectionState()
data class WaitingForTransport(val watch: PebbleDevice?) : ConnectionState()
data class WaitingForTransport(val address: String) : ConnectionState()
data class WaitingForReconnect(val watch: PebbleDevice?) : ConnectionState()
data class Connecting(val watch: PebbleDevice?) : ConnectionState()
data class Negotiating(val watch: PebbleDevice?) : ConnectionState()
Expand All @@ -20,7 +20,6 @@ open class ConnectionState {

val ConnectionState.watchOrNull: PebbleDevice?
get() = when (this) {
is ConnectionState.WaitingForTransport -> watch
is ConnectionState.WaitingForReconnect -> watch
is ConnectionState.Connecting -> watch
is ConnectionState.Negotiating -> watch
Expand All @@ -37,4 +36,6 @@ object ConnectionStateManager: KoinComponent {
* Flow of the currently connected watch's metadata. This flow only emits when a watch is connected and will not emit if negotiation never completes.
*/
val connectedWatchMetadata: StateFlow<WatchVersion.WatchVersionResponse?> by inject(named("connectedWatchMetadata"))

val isConnected: StateFlow<Boolean> by inject(named("isConnected"))
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ enum class LockerTabs(val label: String, val navRoute: String) {
fun Locker(page: LockerTabs, lockerDao: LockerDao = getKoin().get(), viewModel: LockerViewModel = viewModel { LockerViewModel(lockerDao) }, onTabChanged: (LockerTabs) -> Unit) {
val entriesState: LockerViewModel.LockerEntriesState by viewModel.entriesState.collectAsState()
val modalSheetState by viewModel.modalSheetState.collectAsState()
val watchIsConnected by viewModel.watchIsConnected.collectAsState()

Column {
Surface {
Expand Down Expand Up @@ -56,6 +57,6 @@ fun Locker(page: LockerTabs, lockerDao: LockerDao = getKoin().get(), viewModel:
}
if (modalSheetState is LockerViewModel.ModalSheetState.Open) {
val sheetViewModel = (modalSheetState as LockerViewModel.ModalSheetState.Open).viewModel
LockerItemSheet(onDismissRequest = { viewModel.closeModalSheet() }, viewModel = sheetViewModel)
LockerItemSheet(onDismissRequest = { viewModel.closeModalSheet() }, watchIsConnected = watchIsConnected, viewModel = sheetViewModel)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.rebble.cobble.shared.ui.view.home.locker

import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
Expand All @@ -24,14 +25,15 @@ import io.rebble.cobble.shared.ui.viewmodel.LockerItemViewModel

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LockerItemSheet(onDismissRequest: () -> Unit, viewModel: LockerItemViewModel) {
fun LockerItemSheet(onDismissRequest: () -> Unit, watchIsConnected: Boolean, viewModel: LockerItemViewModel) {
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
ModalBottomSheet(
onDismissRequest = onDismissRequest,
sheetState = sheetState,
containerColor = AppTheme.materialColors.surface,
) {
val imageState: LockerItemViewModel.ImageState by viewModel.imageState.collectAsState()
val supportedState: Boolean by viewModel.supportedState.collectAsState()
Column(
modifier = Modifier
.fillMaxWidth()
Expand Down Expand Up @@ -127,9 +129,18 @@ fun LockerItemSheet(onDismissRequest: () -> Unit, viewModel: LockerItemViewModel
}
HorizontalDivider(thickness = 2.dp)
if (viewModel.entry.entry.type == "watchface") {
val color = if (watchIsConnected && supportedState) {ListItemDefaults.contentColor} else {ListItemDefaults.contentColor.copy(alpha = 0.38f)}
ListItem(
leadingContent = { RebbleIcons.sendToWatchUnchecked() },
headlineContent = { Text("Apply on watch") },
colors = ListItemDefaults.colors(
leadingIconColor = color,
headlineColor = color,
trailingIconColor = color,
),
modifier = Modifier.clickable(enabled = watchIsConnected && supportedState) {
viewModel.applyWatchface()
},
leadingContent = { RebbleIcons.sendToWatchUnchecked() },
headlineContent = { Text("Apply on watch") },
)
HorizontalDivider(thickness = 2.dp)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ import io.rebble.cobble.shared.ui.viewmodel.LockerItemViewModel
import org.koin.compose.getKoin

@Composable
fun LockerWatchfaceItem(entry: SyncedLockerEntryWithPlatforms, onOpenModalSheet: (LockerItemViewModel) -> Unit) {
fun LockerWatchfaceItem(entry: SyncedLockerEntryWithPlatforms, watchConnected: Boolean, onOpenModalSheet: (LockerItemViewModel) -> Unit) {
val koin = getKoin()
val viewModel: LockerItemViewModel = viewModel(key = "locker-watchface-${entry.entry.id}") { LockerItemViewModel(koin.get(), entry) }
val imageState: LockerItemViewModel.ImageState by viewModel.imageState.collectAsState()
val supportedState: Boolean by viewModel.supportedState.collectAsState()
Surface(tonalElevation = 1.dp, modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(16.dp)) {
Row(Modifier.padding(8.dp)) {
Column(modifier = Modifier.fillMaxSize().padding(8.dp).weight(1f), verticalArrangement = Arrangement.spacedBy(8.dp)) {
Expand Down Expand Up @@ -67,7 +68,7 @@ fun LockerWatchfaceItem(entry: SyncedLockerEntryWithPlatforms, onOpenModalSheet:
modifier = Modifier.fillMaxHeight(),
verticalArrangement = Arrangement.SpaceBetween
) {
IconButton(onClick = {}, colors = IconButtonDefaults.iconButtonColors(contentColor = AppTheme.materialColors.primary)) {
IconButton(onClick = {viewModel.applyWatchface()}, enabled = watchConnected && supportedState, colors = IconButtonDefaults.iconButtonColors(contentColor = AppTheme.materialColors.primary)) {
RebbleIcons.sendToWatchUnchecked()
}
IconButton(onClick = {}, colors = IconButtonDefaults.iconButtonColors(contentColor = AppTheme.materialColors.primary)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.rebble.cobble.shared.ui.view.home.locker

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.rememberScrollState
Expand All @@ -20,14 +21,16 @@ import io.rebble.cobble.shared.ui.viewmodel.LockerViewModel
fun LockerWatchfaceList(viewModel: LockerViewModel, onOpenModalSheet: (LockerItemViewModel) -> Unit) {
val entriesState: LockerViewModel.LockerEntriesState by viewModel.entriesState.collectAsState()
val entries = ((entriesState as? LockerViewModel.LockerEntriesState.Loaded)?.entries ?: emptyList()).filter { it.entry.type == "watchface" }
val connectedState: Boolean by viewModel.watchIsConnected.collectAsState()

LazyVerticalGrid(
modifier = Modifier.padding(8.dp),
columns = GridCells.Fixed(2),
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
) {
items(entries.size) { i ->
LockerWatchfaceItem(entries[i], onOpenModalSheet = onOpenModalSheet)
LockerWatchfaceItem(entries[i], watchConnected = connectedState, onOpenModalSheet = onOpenModalSheet)
}
}
}
Loading

0 comments on commit b83d06e

Please sign in to comment.