From 1bd90d4a685679cd0232d41b62f5a0fd09ba5872 Mon Sep 17 00:00:00 2001 From: Joseph Hale Date: Sat, 10 Feb 2024 00:37:09 -0700 Subject: [PATCH] feat: Create WearRPCClient in WearOS Creates an MVP driver for querying live data from the Mobile phone. The previous UI has been hidden, though not deleted since much of the code will likely be reused in the new implementation. --- wear/build.gradle | 71 ---------- wear/build.gradle.kts | 77 +++++++++++ wear/proguard-rules.pro | 2 +- .../util/simpletimetracker/data/Messaging.kt | 81 ----------- .../presentation/MainActivity.kt | 126 ++++++++++++++---- 5 files changed, 179 insertions(+), 178 deletions(-) delete mode 100644 wear/build.gradle create mode 100644 wear/build.gradle.kts delete mode 100644 wear/src/main/java/com/example/util/simpletimetracker/data/Messaging.kt diff --git a/wear/build.gradle b/wear/build.gradle deleted file mode 100644 index 0a2064e1..00000000 --- a/wear/build.gradle +++ /dev/null @@ -1,71 +0,0 @@ -plugins { - id 'com.android.application' - id 'org.jetbrains.kotlin.android' -} - -android { - namespace 'com.example.util.simpletimetracker' - compileSdk 34 - - defaultConfig { - applicationId "com.razeeman.util.simpletimetracker" - minSdk 26 - targetSdk 34 - versionCode 1 - versionName "1.0" - vectorDrawables { - useSupportLibrary true - } - - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = '1.8' - } - buildFeatures { - compose true - } - composeOptions { - kotlinCompilerExtensionVersion '1.4.0-alpha02' - } - packagingOptions { - resources { - excludes += '/META-INF/{AL2.0,LGPL2.1}' - } - } -} - -dependencies { - var compose_version = "1.3.1" - var wear_compose_version = "1.1.0" - // Custom Dependencies - implementation 'com.google.android.gms:play-services-wearable:18.0.0' - implementation 'com.google.android.horologist:horologist-compose-layout:0.2.7' - - // Default Dependencies - implementation 'androidx.core:core-ktx:1.7.0' - implementation 'com.google.android.gms:play-services-wearable:17.1.0' - implementation 'androidx.percentlayout:percentlayout:1.0.0' - implementation 'androidx.legacy:legacy-support-v4:1.0.0' - implementation 'androidx.recyclerview:recyclerview:1.2.1' - implementation "androidx.compose.ui:ui:$compose_version" - implementation "androidx.wear.compose:compose-material:$wear_compose_version" - implementation "androidx.wear.compose:compose-foundation:$wear_compose_version" - implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1' - implementation 'androidx.activity:activity-compose:1.3.1' - androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" - debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" - debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" - wearApp project(":wear") -} \ No newline at end of file diff --git a/wear/build.gradle.kts b/wear/build.gradle.kts new file mode 100644 index 00000000..6838683d --- /dev/null +++ b/wear/build.gradle.kts @@ -0,0 +1,77 @@ +plugins { + id("com.android.application") + id("kotlin-android") +} + +android { + namespace = "com.example.util.simpletimetracker" + compileSdk = 34 + + defaultConfig { + applicationId = "com.razeeman.util.simpletimetracker" + minSdk = 26 + targetSdk = 34 + versionCode = 1 + versionName = "1.0" + vectorDrawables { + useSupportLibrary = true + } + + } + + buildTypes { + getByName("debug") { + applicationIdSuffix = ".debug" + } + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = "1.4.0-alpha02" + } + packagingOptions { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } +} + +dependencies { + var compose_version = "1.3.1" + var wear_compose_version = "1.1.0" + // Custom Dependencies + implementation("com.google.android.horologist:horologist-compose-layout:0.2.7") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1") + implementation(project(":wearrpc")) + + // Default Dependencies + implementation("androidx.core:core-ktx:1.7.0") + implementation("com.google.android.gms:play-services-wearable:17.1.0") + implementation("androidx.percentlayout:percentlayout:1.0.0") + implementation("androidx.legacy:legacy-support-v4:1.0.0") + implementation("androidx.recyclerview:recyclerview:1.2.1") + implementation("androidx.compose.ui:ui:$compose_version") + implementation("androidx.wear.compose:compose-material:$wear_compose_version") + implementation("androidx.wear.compose:compose-foundation:$wear_compose_version") + implementation("androidx.compose.ui:ui-tooling-preview:$compose_version") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.3.1") + implementation("androidx.activity:activity-compose:1.3.1") + androidTestImplementation("androidx.compose.ui:ui-test-junit4:$compose_version") + debugImplementation("androidx.compose.ui:ui-tooling:$compose_version") + debugImplementation("androidx.compose.ui:ui-test-manifest:$compose_version") +} \ No newline at end of file diff --git a/wear/proguard-rules.pro b/wear/proguard-rules.pro index 481bb434..ff59496d 100644 --- a/wear/proguard-rules.pro +++ b/wear/proguard-rules.pro @@ -1,6 +1,6 @@ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. +# proguardFiles setting in build.gradle.kts. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html diff --git a/wear/src/main/java/com/example/util/simpletimetracker/data/Messaging.kt b/wear/src/main/java/com/example/util/simpletimetracker/data/Messaging.kt deleted file mode 100644 index 9d4a4b72..00000000 --- a/wear/src/main/java/com/example/util/simpletimetracker/data/Messaging.kt +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ -package com.example.util.simpletimetracker.data - -import android.content.Context -import android.util.Log -import com.google.android.gms.tasks.Task -import com.google.android.gms.tasks.Tasks -import com.google.android.gms.wearable.CapabilityClient -import com.google.android.gms.wearable.CapabilityInfo -import com.google.android.gms.wearable.Wearable -import com.example.util.simpletimetracker.presentation.LOG_TAG - -const val START_TIME_TRACKING_ACTIVITY_CAPABILITY_NAME = "start_time_tracking_activity" - -class Messaging { - fun startTimeTracking( - context: Context, - activity: String, - tag: String, - ) { - Thread( - Runnable { - startTimeTrackingTask(context, activity, tag) - }, - ).start() - } - - private fun startTimeTrackingTask( - context: Context, - activity: String, - tag: String, - ) { - // Find all nodes which support the time tracking message - val capabilityInfo: CapabilityInfo = - Tasks.await( - Wearable.getCapabilityClient(context) - .getCapability( - START_TIME_TRACKING_ACTIVITY_CAPABILITY_NAME, - CapabilityClient.FILTER_REACHABLE, - ), - ) - - // Choose the best node (the closest one connected to the watch - val nodes = capabilityInfo.nodes - val bestNode = nodes.firstOrNull { it.isNearby }?.id ?: nodes.firstOrNull()?.id - - // Send the message - val message = "$activity|$tag" - bestNode?.also { nodeId -> - Log.i(LOG_TAG, "Sending message to $bestNode") - val sendTask: Task<*> = - Wearable.getMessageClient(context).sendMessage( - nodeId, - "/$START_TIME_TRACKING_ACTIVITY_CAPABILITY_NAME", - message.toByteArray(), - ).apply { - addOnSuccessListener { - Log.i( - LOG_TAG, - "Sent $START_TIME_TRACKING_ACTIVITY_CAPABILITY_NAME message: $message", - ) - } - addOnFailureListener { - Log.e( - LOG_TAG, - "Failed to send $START_TIME_TRACKING_ACTIVITY_CAPABILITY_NAME message: $message", - ) - } - } - } ?: run { - Log.e( - LOG_TAG, - "No nodes found with the capability $START_TIME_TRACKING_ACTIVITY_CAPABILITY_NAME", - ) - } - } -} diff --git a/wear/src/main/java/com/example/util/simpletimetracker/presentation/MainActivity.kt b/wear/src/main/java/com/example/util/simpletimetracker/presentation/MainActivity.kt index 31944722..320632ba 100644 --- a/wear/src/main/java/com/example/util/simpletimetracker/presentation/MainActivity.kt +++ b/wear/src/main/java/com/example/util/simpletimetracker/presentation/MainActivity.kt @@ -7,6 +7,7 @@ package com.example.util.simpletimetracker.presentation import android.content.Context import android.os.Bundle +import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.background @@ -25,9 +26,13 @@ import com.google.android.horologist.compose.focus.rememberActiveFocusRequester import com.google.android.horologist.compose.navscaffold.ExperimentalHorologistComposeLayoutApi import com.google.android.horologist.compose.rotaryinput.* import com.example.util.simpletimetracker.R -import com.example.util.simpletimetracker.data.Messaging import com.example.util.simpletimetracker.data.getTimeTrackingActivities import com.example.util.simpletimetracker.presentation.theme.SimpleTimeTrackerForWearOSTheme +import com.example.util.simpletimetracker.wearrpc.Messenger +import com.example.util.simpletimetracker.wearrpc.WearRPCClient +//import com.example.util.simpletimetracker.wearrpc.CurrentActivity +import kotlinx.coroutines.* +//import java.util.Date const val LOG_TAG = "com.razeeman.util.simpletimetracker" @@ -42,6 +47,8 @@ class MainActivity : ComponentActivity() { @Composable fun WearApp() { + val context = LocalContext.current + val scope = CoroutineScope(Dispatchers.Default + SupervisorJob()) SimpleTimeTrackerForWearOSTheme { val scrollState = rememberScalingLazyListState() Scaffold( @@ -55,11 +62,72 @@ fun WearApp() { PositionIndicator(scalingLazyListState = scrollState) }, ) { - ActivityList(scrollState) + ScalingLazyColumn ( + modifier = Modifier.fillMaxSize(), + autoCentering = AutoCenteringParams(itemIndex = 0) + ){ + item { + DebugButton( + label = "Ping", + onClick = { + Log.i(LOG_TAG, "Pinging mobile ...") + scope.async { + val response = WearRPCClient(Messenger(context)).ping("Ping button") + Log.i(LOG_TAG, response) + } + } + ) + } + item { + DebugButton(label="Query Activities", onClick = { + Log.i(LOG_TAG, "Querying activities ...") + scope.async { + val response = WearRPCClient(Messenger(context)).queryActivities() + Log.i(LOG_TAG, "Received ${response.size} activities") + for (activity in response) { + Log.i(LOG_TAG, " - ${activity.id}: ${activity.name}") + } + } + }) + } + } + +// ActivityList(scrollState) } } } +@Composable +fun DebugButton(label: String, onClick: () -> Unit) { + Chip( + modifier = Modifier + .fillMaxWidth(0.9f) + .padding(top = 10.dp), + label = { Text(label) }, + onClick = onClick, + ) +} + +@OptIn(ExperimentalHorologistComposeLayoutApi::class) +@Composable +fun ScrollingColumn(scrollState: ScalingLazyListState = rememberScalingLazyListState(), content: @Composable () -> Unit) { + val focusRequester = rememberActiveFocusRequester() + val rotaryHapticFeedback = rememberRotaryHapticFeedback() + ScalingLazyColumn( + modifier = + Modifier + .rotaryWithScroll(focusRequester, scrollState, rotaryHapticFeedback) + .fillMaxSize() + .background(MaterialTheme.colors.background) + .selectableGroup(), + contentPadding = PaddingValues(10.dp), + verticalArrangement = Arrangement.Center, + state = scrollState, + ) { + content + } +} + @OptIn(ExperimentalHorologistComposeLayoutApi::class) @Composable fun ActivityList(scrollState: ScalingLazyListState = rememberScalingLazyListState()) { @@ -68,11 +136,11 @@ fun ActivityList(scrollState: ScalingLazyListState = rememberScalingLazyListStat val rotaryHapticFeedback = rememberRotaryHapticFeedback() ScalingLazyColumn( modifier = - Modifier - .rotaryWithScroll(focusRequester, scrollState, rotaryHapticFeedback) - .fillMaxSize() - .background(MaterialTheme.colors.background) - .selectableGroup(), + Modifier + .rotaryWithScroll(focusRequester, scrollState, rotaryHapticFeedback) + .fillMaxSize() + .background(MaterialTheme.colors.background) + .selectableGroup(), contentPadding = PaddingValues(10.dp), verticalArrangement = Arrangement.Center, state = scrollState, @@ -125,11 +193,12 @@ fun ActivityWithTag( icon: Int = R.drawable.baseline_question_mark_24, ) { val context = LocalContext.current + val scope = CoroutineScope(Dispatchers.Default + SupervisorJob()) Chip( modifier = - Modifier - .fillMaxWidth(0.9f) - .padding(top = 10.dp), + Modifier + .fillMaxWidth(0.9f) + .padding(top = 10.dp), icon = { ActivityIcon(iconId = icon) }, @@ -151,7 +220,7 @@ fun ActivityWithTag( ChipDefaults.chipColors( backgroundColor = color, ), - onClick = { startTimeTracking(context, name, tag) }, + onClick = { startTimeTracking(scope, context) }, ) } @@ -162,11 +231,12 @@ fun ActivityWithoutTag( icon: Int = R.drawable.baseline_question_mark_24, ) { val context = LocalContext.current + val scope = CoroutineScope(Dispatchers.Default + SupervisorJob()) Chip( modifier = - Modifier - .fillMaxWidth(0.9f) - .padding(top = 10.dp), + Modifier + .fillMaxWidth(0.9f) + .padding(top = 10.dp), icon = { ActivityIcon(iconId = icon) }, @@ -181,7 +251,7 @@ fun ActivityWithoutTag( ChipDefaults.chipColors( backgroundColor = color, ), - onClick = { startTimeTracking(context, name, "") }, + onClick = { startTimeTracking(scope, context) }, ) } @@ -191,16 +261,22 @@ fun ActivityIcon(iconId: Int) { painter = painterResource(id = iconId), contentDescription = "activity icon", modifier = - Modifier - .size(ChipDefaults.IconSize) - .wrapContentSize(align = Alignment.Center), + Modifier + .size(ChipDefaults.IconSize) + .wrapContentSize(align = Alignment.Center), ) } -fun startTimeTracking( - context: Context, - activity: String, - tag: String, -) { - Messaging().startTimeTracking(context, activity, tag) -} +fun startTimeTracking(scope: CoroutineScope, context: Context) { +// val activities = arrayOf( +// CurrentActivity( +// id, +// name, +// startedAt = Date(), +// arrayOf(/* tags */), +// ) +// ) +// scope.async { +// WearRPCClient(context).setCurrentActivities(activities) +// } +} \ No newline at end of file -- 2.51.2