Skip to content

Commit 17f1541

Browse files
authored
Merge pull request #162 from thehale/wearos
feat: Add WearOS Support
2 parents 9f30683 + 0710933 commit 17f1541

File tree

68 files changed

+2597
-2
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+2597
-2
lines changed

app/build.gradle.kts

+3
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,10 @@ dependencies {
9797
implementation(project(":feature_data_edit"))
9898
implementation(project(":feature_records_filter"))
9999
implementation(project(":feature_goals"))
100+
implementation(project(":wearrpc"))
100101

102+
implementation("com.google.android.gms:play-services-wearable:18.0.0")
103+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
101104
implementation(Deps.Androidx.room)
102105
implementation(Deps.Ktx.navigationFragment)
103106
implementation(Deps.Ktx.navigationUi)

app/src/main/AndroidManifest.xml

+9
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,15 @@
4545
android:value="true" />
4646
</service>
4747

48+
<service
49+
android:name=".wear.WearService"
50+
android:exported="true">
51+
<intent-filter>
52+
<action android:name="com.google.android.gms.wearable.REQUEST_RECEIVED" />
53+
<data android:scheme="wear" android:host="*"
54+
android:pathPrefix="/stt" />
55+
</intent-filter>
56+
</service>
4857
</application>
4958

5059
<queries>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
5+
*/
6+
package com.example.util.simpletimetracker.wear
7+
8+
import com.example.util.simpletimetracker.domain.interactor.PrefsInteractor
9+
import com.example.util.simpletimetracker.domain.interactor.RecordTagInteractor
10+
import com.example.util.simpletimetracker.domain.interactor.RecordTypeInteractor
11+
import com.example.util.simpletimetracker.domain.interactor.RemoveRunningRecordMediator
12+
import com.example.util.simpletimetracker.domain.interactor.RunningRecordInteractor
13+
import com.example.util.simpletimetracker.domain.mapper.AppColorMapper
14+
import com.example.util.simpletimetracker.domain.model.AppColor
15+
import com.example.util.simpletimetracker.domain.model.RecordTag
16+
import com.example.util.simpletimetracker.domain.model.RunningRecord
17+
import com.example.util.simpletimetracker.wearrpc.Activity
18+
import com.example.util.simpletimetracker.wearrpc.CurrentActivity
19+
import com.example.util.simpletimetracker.wearrpc.Settings
20+
import com.example.util.simpletimetracker.wearrpc.SimpleTimeTrackerAPI
21+
import com.example.util.simpletimetracker.wearrpc.Tag
22+
23+
class DomainAPI(
24+
private val prefsInteractor: PrefsInteractor,
25+
private val recordTypeInteractor: RecordTypeInteractor,
26+
private val recordTagInteractor: RecordTagInteractor,
27+
private val runningRecordInteractor: RunningRecordInteractor,
28+
private val removeRunningRecordMediator: RemoveRunningRecordMediator,
29+
private val appColorMapper: AppColorMapper,
30+
) : SimpleTimeTrackerAPI {
31+
32+
override suspend fun queryActivities(): Array<Activity> {
33+
return recordTypeInteractor.getAll().filter { recordType -> !recordType.hidden }
34+
.map { recordType ->
35+
Activity(
36+
id = recordType.id,
37+
name = recordType.name,
38+
icon = recordType.icon,
39+
color = asColor(recordType.color),
40+
)
41+
}.toTypedArray()
42+
}
43+
44+
override suspend fun queryCurrentActivities(): Array<CurrentActivity> {
45+
return runningRecordInteractor.getAll().map { record ->
46+
CurrentActivity(
47+
record.id,
48+
record.timeStarted,
49+
record.tagIds.map { tagId ->
50+
asTag(recordTagInteractor.get(tagId))
51+
}.filter { it.id > 0 }.toTypedArray(),
52+
)
53+
}.toTypedArray()
54+
}
55+
56+
override suspend fun setCurrentActivities(activities: Array<CurrentActivity>) {
57+
val currents = queryCurrentActivities()
58+
val unchanged = currents.filter { c -> activities.any { a -> a == c } }
59+
val stopped = currents.filter { c -> unchanged.none { u -> u == c } }
60+
val started = activities.filter { a -> currents.none { c -> a == c } }
61+
stopped.forEach { removeRunningRecordMediator.removeWithRecordAdd(asRunningRecord(it)) }
62+
started.forEach { runningRecordInteractor.add(asRunningRecord(it)) }
63+
}
64+
65+
private fun asRunningRecord(currentActivity: CurrentActivity): RunningRecord {
66+
return RunningRecord(
67+
id = currentActivity.id,
68+
timeStarted = currentActivity.startedAt,
69+
comment = "",
70+
tagIds = currentActivity.tags.map { t -> t.id },
71+
)
72+
}
73+
74+
override suspend fun queryTagsForActivity(activityId: Long): Array<Tag> {
75+
val activityColor = recordTypeInteractor.get(activityId)?.color
76+
return recordTagInteractor.getByTypeOrUntyped(activityId).filter { !it.archived }
77+
.map { asTag(it, asColor(activityColor)) }.sortedBy { it.name }
78+
.sortedBy { it.isGeneral }.toTypedArray()
79+
}
80+
81+
private fun asTag(recordTag: RecordTag?, activityColor: Long = 0x00000000): Tag {
82+
return if (recordTag != null) {
83+
val isGeneral = recordTag.typeId == 0L
84+
val tagColor = if (isGeneral) {
85+
asColor(recordTag.color)
86+
} else {
87+
activityColor
88+
}
89+
Tag(
90+
id = recordTag.id,
91+
name = recordTag.name,
92+
isGeneral = isGeneral,
93+
color = tagColor,
94+
)
95+
} else {
96+
Tag(id = -1, name = "", isGeneral = true, color = 0xFF555555)
97+
}
98+
}
99+
100+
private fun asColor(appColor: AppColor?): Long {
101+
return if (appColor == null) {
102+
0x00000000
103+
} else {
104+
appColorMapper.mapToColorInt(appColor).toLong()
105+
}
106+
}
107+
108+
override suspend fun querySettings(): Settings {
109+
return Settings(
110+
allowMultitasking = prefsInteractor.getAllowMultitasking(),
111+
showRecordTagSelection = prefsInteractor.getShowRecordTagSelection(),
112+
recordTagSelectionCloseAfterOne = prefsInteractor.getRecordTagSelectionCloseAfterOne(),
113+
recordTagSelectionEvenForGeneralTags = prefsInteractor.getRecordTagSelectionEvenForGeneralTags(),
114+
)
115+
}
116+
117+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
5+
*/
6+
package com.example.util.simpletimetracker.wear
7+
8+
import com.example.util.simpletimetracker.domain.interactor.PrefsInteractor
9+
import com.example.util.simpletimetracker.domain.interactor.RecordTagInteractor
10+
import com.example.util.simpletimetracker.domain.interactor.RecordTypeInteractor
11+
import com.example.util.simpletimetracker.domain.interactor.RemoveRunningRecordMediator
12+
import com.example.util.simpletimetracker.domain.interactor.RunningRecordInteractor
13+
import com.example.util.simpletimetracker.domain.mapper.AppColorMapper
14+
import com.example.util.simpletimetracker.wearrpc.WearRPCServer
15+
import com.google.android.gms.tasks.Task
16+
import com.google.android.gms.tasks.Tasks
17+
import com.google.android.gms.wearable.WearableListenerService
18+
import dagger.hilt.android.AndroidEntryPoint
19+
import kotlinx.coroutines.runBlocking
20+
import javax.inject.Inject
21+
22+
/**
23+
* Landing point for messages coming to Mobile from Wear.
24+
*
25+
* Implemented as a Service so that it can receive messages even when the Mobile app is stopped or
26+
* in the background.
27+
*/
28+
@AndroidEntryPoint
29+
class WearService : WearableListenerService() {
30+
31+
@Inject
32+
lateinit var prefsInteractor: PrefsInteractor
33+
34+
@Inject
35+
lateinit var recordTypeInteractor: RecordTypeInteractor
36+
37+
@Inject
38+
lateinit var recordTagInteractor: RecordTagInteractor
39+
40+
@Inject
41+
lateinit var runningRecordInteractor: RunningRecordInteractor
42+
43+
@Inject
44+
lateinit var removeRunningRecordMediator: RemoveRunningRecordMediator
45+
46+
@Inject
47+
lateinit var appColorMapper: AppColorMapper
48+
49+
override fun onRequest(nodeId: String, path: String, request: ByteArray): Task<ByteArray>? {
50+
val rpc = WearRPCServer(
51+
DomainAPI(
52+
prefsInteractor,
53+
recordTypeInteractor,
54+
recordTagInteractor,
55+
runningRecordInteractor,
56+
removeRunningRecordMediator,
57+
appColorMapper,
58+
),
59+
)
60+
return runBlocking { Tasks.forResult(rpc.onRequest(path, request)) }
61+
}
62+
}

app/src/main/res/values/wear.xml

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!-- This Source Code Form is subject to the terms of the Mozilla Public
3+
- License, v. 2.0. If a copy of the MPL was not distributed with this
4+
- file, You can obtain one at https://mozilla.org/MPL/2.0/. -->
5+
<resources xmlns:tools="http://schemas.android.com/tools"
6+
tools:keep="@array/android_wear_capabilities">
7+
<string-array name="android_wear_capabilities">
8+
<item>/stt//GET/ping</item>
9+
<item>/stt//GET/activities</item>
10+
<item>/stt//GET/activities/current</item>
11+
<item>/stt//PUT/activities/current</item>
12+
<item>/stt//GET/activities/:ID/tags</item>
13+
<item>/stt//GET/settings</item>
14+
</string-array>
15+
</resources>

buildSrc/src/main/kotlin/com/example/util/simpletimetracker/Base.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ object Base {
66

77
const val versionCode = 38
88
const val versionName = "1.37"
9-
const val minSDK = 21
9+
const val minSDK = 26
1010
const val currentSDK = 34
1111
}
47.9 KB
Loading
32.5 KB
Loading

domain/src/test/java/com/example/util/simpletimetracker/domain/mapper/UnCoveredRangesMapperTest.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ class UnCoveredRangesMapperTest(
6161
),
6262
arrayOf(
6363
listOf(0L, 10L, listOf(Range(0L, 0L), Range(5L, 5L), Range(10L, 10L))),
64-
listOf(0L to 5L, Range(5L, 10L)),
64+
listOf(Range(0L, 5L), Range(5L, 10L)),
6565
),
6666

6767
// Segments on range points

settings.gradle.kts

+2
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,5 @@ file("features").walkTopDown().maxDepth(1).forEach { dir ->
1414
project(":${dir.name}").projectDir = dir
1515
}
1616
}
17+
include(":wear")
18+
include(":wearrpc")

wear/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build

wear/build.gradle.kts

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
5+
*/
6+
7+
plugins {
8+
id("com.android.application")
9+
id("kotlin-android")
10+
}
11+
12+
android {
13+
namespace = "com.example.util.simpletimetracker"
14+
compileSdk = 34
15+
16+
defaultConfig {
17+
applicationId = "com.razeeman.util.simpletimetracker"
18+
minSdk = 26
19+
targetSdk = 34
20+
versionCode = 1
21+
versionName = "1.0"
22+
vectorDrawables {
23+
useSupportLibrary = true
24+
}
25+
26+
}
27+
28+
buildTypes {
29+
getByName("debug") {
30+
applicationIdSuffix = ".debug"
31+
}
32+
release {
33+
isMinifyEnabled = false
34+
proguardFiles(
35+
getDefaultProguardFile("proguard-android-optimize.txt"),
36+
"proguard-rules.pro"
37+
)
38+
}
39+
}
40+
compileOptions {
41+
sourceCompatibility = JavaVersion.VERSION_1_8
42+
targetCompatibility = JavaVersion.VERSION_1_8
43+
}
44+
kotlinOptions {
45+
jvmTarget = "1.8"
46+
}
47+
buildFeatures {
48+
compose = true
49+
}
50+
composeOptions {
51+
kotlinCompilerExtensionVersion = "1.4.0-alpha02"
52+
}
53+
packagingOptions {
54+
resources {
55+
excludes += "/META-INF/{AL2.0,LGPL2.1}"
56+
}
57+
}
58+
}
59+
60+
dependencies {
61+
var compose_version = "1.3.1"
62+
var wear_compose_version = "1.1.0"
63+
// Runtime Dependencies
64+
implementation("androidx.wear.compose:compose-navigation:$wear_compose_version")
65+
implementation("com.google.android.horologist:horologist-compose-layout:0.2.7")
66+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
67+
implementation("androidx.compose.material:material-icons-core:1.6.1")
68+
implementation(project(":wearrpc"))
69+
implementation(project(":resources"))
70+
71+
// Dev Dependencies
72+
implementation("androidx.wear:wear-tooling-preview:1.0.0")
73+
74+
// Default Dependencies
75+
implementation("androidx.core:core-ktx:1.7.0")
76+
implementation("com.google.android.gms:play-services-wearable:17.1.0")
77+
implementation("androidx.percentlayout:percentlayout:1.0.0")
78+
implementation("androidx.legacy:legacy-support-v4:1.0.0")
79+
implementation("androidx.recyclerview:recyclerview:1.2.1")
80+
implementation("androidx.compose.ui:ui:$compose_version")
81+
implementation("androidx.wear.compose:compose-material:$wear_compose_version")
82+
implementation("androidx.wear.compose:compose-foundation:$wear_compose_version")
83+
implementation("androidx.compose.ui:ui-tooling-preview:$compose_version")
84+
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.3.1")
85+
implementation("androidx.activity:activity-compose:1.3.1")
86+
implementation("androidx.appcompat:appcompat:1.6.0")
87+
androidTestImplementation("androidx.compose.ui:ui-test-junit4:$compose_version")
88+
debugImplementation("androidx.compose.ui:ui-tooling:$compose_version")
89+
debugImplementation("androidx.compose.ui:ui-test-manifest:$compose_version")
90+
}

wear/proguard-rules.pro

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Add project specific ProGuard rules here.
2+
# You can control the set of applied configuration files using the
3+
# proguardFiles setting in build.gradle.kts.
4+
#
5+
# For more details, see
6+
# http://developer.android.com/guide/developing/tools/proguard.html
7+
8+
# If your project uses WebView with JS, uncomment the following
9+
# and specify the fully qualified class name to the JavaScript interface
10+
# class:
11+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12+
# public *;
13+
#}
14+
15+
# Uncomment this to preserve the line number information for
16+
# debugging stack traces.
17+
#-keepattributes SourceFile,LineNumberTable
18+
19+
# If you keep the line number information, uncomment this to
20+
# hide the original source file name.
21+
#-renamesourcefileattribute SourceFile

0 commit comments

Comments
 (0)