Skip to content

Commit f5f3d83

Browse files
committed
WearOS improvements & some unit tests
1 parent 9378af4 commit f5f3d83

13 files changed

Lines changed: 322 additions & 56 deletions

File tree

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,7 @@ ios/Runner/GoogleService-Info.plist
9494
firebase.json
9595

9696
android/app/google-services.json
97+
98+
HiveTest/
99+
100+
.HiveTest/

.run/main.dart.run.xml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
<configuration default="false" name="main.dart" type="FlutterRunConfigurationType" factoryName="Flutter">
33
<option name="additionalArgs" value="--dart-define=cronetHttpNoPlay=true" />
44
<option name="filePath" value="$PROJECT_DIR$/lib/main.dart" />
5-
<method v="2">
6-
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Run Dart Generator" run_configuration_type="ShConfigurationType" />
7-
</method>
5+
<method v="2" />
86
</configuration>
97
</component>

.vscode/settings.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44
"editor.formatOnSave": true,
55
"editor.formatOnType": true,
66
"editor.wordBasedSuggestions": "off",
7-
"dart.lineLength": 200,
87
"[dart]": {
98
"editor.rulers": [
10-
200
9+
80
1110
],
1211
"editor.selectionHighlight": false,
1312
"editor.tabCompletion": "onlySnippets",
@@ -17,6 +16,4 @@
1716
"dart.buildRunnerAdditionalArgs": [
1817
"--delete-conflicting-outputs"
1918
],
20-
"avdmanager.executable": "c:\\Users\\Floof\\AppData\\Local\\Android\\Sdk\\cmdline-tools\\latest\\bin\\avdmanager.bat",
21-
"java.configuration.updateBuildConfiguration": "automatic"
2219
}

android/wear/src/main/java/com/codel1417/tail_App/presentation/MainActivity.kt

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,25 +71,23 @@ import java.io.ObjectOutputStream
7171

7272
/** TODO:
7373
* Show spinner when no data available / loading from app
74-
* Refresh UI when data updates
7574
* Move actions / triggers / gear to their own page with horizontal swipe
7675
* show all actions, not just favorites
7776
* Theme based on main app colors
78-
* Watch to App communication
7977
*/
8078
class MainActivity : ComponentActivity(), DataClient.OnDataChangedListener,
8179
CapabilityClient.OnCapabilityChangedListener {
8280
private var wearData: MutableLiveData<WearData> = MutableLiveData<WearData>(WearData())
8381

8482
override fun onResume() {
8583
super.onResume()
86-
println("onResume()")
84+
//println("onResume()")
8785
Wearable.getDataClient(this).addListener(this)
8886
}
8987

9088
override fun onPause() {
9189
super.onPause()
92-
println("onPause()")
90+
//println("onPause()")
9391
Wearable.getDataClient(this).removeListener(this)
9492
}
9593

@@ -127,9 +125,9 @@ class MainActivity : ComponentActivity(), DataClient.OnDataChangedListener,
127125
}
128126

129127
override fun onDataChanged(dataEvents: DataEventBuffer) {
130-
println("onDataChanged()")
128+
//println("onDataChanged()")
131129
dataEvents.forEach { event ->
132-
println("onDataChanged() ${event.type}")
130+
//println("onDataChanged() ${event.type}")
133131
// DataItem changed
134132
if (event.type == DataEvent.TYPE_CHANGED) {
135133
event.dataItem.also { item ->
@@ -141,7 +139,7 @@ class MainActivity : ComponentActivity(), DataClient.OnDataChangedListener,
141139

142140
private fun getWearDataItem(item: DataItem) {
143141
try {
144-
println("Loading Actions")
142+
//println("Loading Actions")
145143
val gson = Gson()
146144
// asMap converts the bytes to the java object
147145
// The flutter library watch_connectivity was built for flutter to flutter, not flutter to compose
@@ -170,6 +168,7 @@ class MainActivity : ComponentActivity(), DataClient.OnDataChangedListener,
170168
val capabilityId = // Find a nearby node or pick one arbitrarily.
171169
result.nodes.firstOrNull { it.isNearby }?.id
172170
?: result.nodes.firstOrNull()?.id
171+
//println("Capability ID: ${capabilityId}")
173172
if (capabilityId == null) {
174173
return@addOnSuccessListener
175174
}
@@ -189,7 +188,7 @@ class MainActivity : ComponentActivity(), DataClient.OnDataChangedListener,
189188

190189

191190
override fun onCreate(savedInstanceState: Bundle?) {
192-
println("onCreate()")
191+
//println("onCreate()")
193192
installSplashScreen()
194193

195194
super.onCreate(savedInstanceState)
@@ -204,7 +203,7 @@ class MainActivity : ComponentActivity(), DataClient.OnDataChangedListener,
204203
//TODO: When app is visible, send a message to update application context
205204
@Composable
206205
fun WearApp() {
207-
println("WearApp()")
206+
//println("WearApp()")
208207
val context = LocalContext.current
209208
val state: State<WearData?> = wearData.observeAsState()
210209

@@ -370,7 +369,7 @@ class MainActivity : ComponentActivity(), DataClient.OnDataChangedListener,
370369
GearButton(
371370
contentModifier,
372371
it.name,
373-
it.batteryLevel.toInt(),
372+
it.batteryLevel,
374373
it.color
375374
)
376375
}
@@ -464,7 +463,7 @@ class MainActivity : ComponentActivity(), DataClient.OnDataChangedListener,
464463
}
465464

466465
override fun onCapabilityChanged(p0: CapabilityInfo) {
467-
println("onCapabilityChanged() ${p0.name} ${p0.nodes}")
466+
//println("onCapabilityChanged() ${p0.name} ${p0.nodes}")
468467
sendMessageToPhone(
469468
data = WearSendData(
470469
capability = "refresh",

lib/Backend/dynamic_config.dart

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ abstract class DynamicConfigInfo with _$DynamicConfigInfo {
3030
"MiTail": "https://thetailcompany.com/fw/mitail.json",
3131
"minitail": "https://thetailcompany.com/fw/mini.json",
3232
"EG2": "https://thetailcompany.com/fw/eg",
33-
"flutter": "https://thetailcompany.com/fw/flutter"
33+
"flutter": "https://thetailcompany.com/fw/flutter",
3434
})
3535
Map<String, String> updateURLs,
3636
@Default(URLs()) URLs urls,
@@ -41,23 +41,15 @@ abstract class DynamicConfigInfo with _$DynamicConfigInfo {
4141

4242
@freezed
4343
abstract class AppVersion with _$AppVersion {
44-
const factory AppVersion({
45-
@Default(Version(major: 1, minor: 0, patch: 0)) Version version,
46-
@Default("") String changelog,
47-
@Default("") String url,
48-
}) = _AppVersion;
44+
const factory AppVersion({@Default(Version(major: 1, minor: 0, patch: 0)) Version version, @Default("") String changelog, @Default("") String url}) = _AppVersion;
4945

5046
factory AppVersion.fromJson(Map<String, dynamic> json) => _$AppVersionFromJson(json);
5147
}
5248

5349
@freezed
5450
abstract class SentryConfig with _$SentryConfig {
55-
const factory SentryConfig({
56-
@Default(0.5) double tracesSampleRate,
57-
@Default(0.5) double profilesSampleRate,
58-
@Default(0) double replaySessionSampleRate,
59-
@Default(0) double replayOnErrorSampleRate,
60-
}) = _SentryConfig;
51+
const factory SentryConfig({@Default(0.5) double tracesSampleRate, @Default(0.5) double profilesSampleRate, @Default(0) double replaySessionSampleRate, @Default(0) double replayOnErrorSampleRate}) =
52+
_SentryConfig;
6153

6254
factory SentryConfig.fromJson(Map<String, dynamic> json) => _$SentryConfigFromJson(json);
6355
}
@@ -90,6 +82,12 @@ abstract class URLs with _$URLs {
9082

9183
DynamicConfigInfo? _dynamicConfigInfo;
9284

85+
@visibleForTesting
86+
void clearDynamicConfigCache() {
87+
_dynamicConfigInfo = null;
88+
HiveProxy.deleteKey(settings, dynamicConfigJsonString);
89+
}
90+
9391
Future<DynamicConfigInfo> getDynamicConfigInfo() async {
9492
if (_dynamicConfigInfo != null) {
9593
return _dynamicConfigInfo!;
@@ -119,7 +117,10 @@ Future<void> _getRemoteDynamicConfigInfo() async {
119117
try {
120118
_dynamicConfigLogger.info("Downloading latest config file");
121119
// TODO: move to own domain
122-
Response<String> response = await dio.get(_dynamicConfigInfo!.urls.dynamicConfigFileUrl, options: Options(contentType: ContentType.json.mimeType, responseType: ResponseType.json));
120+
Response<String> response = await dio.get(
121+
_dynamicConfigInfo!.urls.dynamicConfigFileUrl,
122+
options: Options(contentType: ContentType.json.mimeType, responseType: ResponseType.json),
123+
);
123124
if (response.statusCode! < 400) {
124125
String jsonData = response.data!;
125126
// ignore: unused_local_variable

lib/Backend/move_lists_backend.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,11 @@ class MoveLists with ChangeNotifier {
143143
static final MoveLists instance = MoveLists._internal();
144144

145145
MoveLists._internal() {
146+
reload();
147+
}
148+
149+
@visibleForTesting
150+
void reload() {
146151
List<MoveList> results = [];
147152
try {
148153
results = HiveProxy.getAll<MoveList>(sequencesBox).toList(growable: true);

lib/Backend/wear_bridge.dart

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'dart:async';
33
import 'package:built_collection/built_collection.dart';
44
import 'package:freezed_annotation/freezed_annotation.dart';
55
import 'package:logging/logging.dart';
6+
import 'package:stream_transform/stream_transform.dart';
67
import 'package:synchronized/synchronized.dart';
78
import 'package:tail_app/Backend/Bluetooth/known_devices.dart';
89
import 'package:tail_app/Backend/Definitions/Device/device_definition.dart';
@@ -22,9 +23,7 @@ part 'wear_bridge.g.dart';
2223

2324
final Logger _wearLogger = Logger('Wear');
2425
final _watch = WatchConnectivity();
25-
bool _didInitWear = false;
2626
WearThemeData? wearThemeData;
27-
Lock _initLock = Lock();
2827

2928
void _watchIncomingMessageListener(Map<String, dynamic> event) {
3029
_wearLogger.info("Watch Message: $event");
@@ -52,14 +51,12 @@ void _watchIncomingMessageListener(Map<String, dynamic> event) {
5251
}
5352
}
5453

55-
Future<void> initWear() async {
56-
if (_didInitWear) {
57-
return;
58-
}
59-
_initLock.synchronized(() async {
60-
await Future.delayed(const Duration(seconds: 5));
54+
void initWear() {
55+
6156
try {
62-
_watch.messageStream.listen(_watchIncomingMessageListener);
57+
_wearLogger.info("Setting up listeners");
58+
59+
_watch.messageStream.asBroadcastStream().debounce(Duration(milliseconds: 250)).listen(_watchIncomingMessageListener);
6360
KnownDevices.instance.addListener(() {
6461
KnownDevices.instance.state.values.map((e) => e).forEach((element) {
6562
element.batteryLevel
@@ -73,13 +70,13 @@ Future<void> initWear() async {
7370
..removeListener(updateWearData)
7471
..addListener(updateWearData);
7572
});
73+
7674
//react to device pairing
7775
updateWearData();
7876
});
7977
} catch (e, s) {
8078
_wearLogger.severe("exception setting up Wear $e", e, s);
8179
}
82-
});
8380
}
8481

8582
Future<bool> isReachable() {
@@ -99,12 +96,12 @@ Future<Map<String, dynamic>> applicationContext() {
9996
}
10097

10198
Future<void> updateWearData() async {
102-
_initLock.synchronized(() async {
10399
try {
104-
await initWear();
105100
if (!await isPaired()) {
106101
return; // Don't update wear actions if wear is not supported / no watch is paired
107102
}
103+
_wearLogger.info("Updating watch data");
104+
108105
Iterable<BaseAction> allActions = FavoriteActions.instance.state.map((e) => ActionRegistry.getActionFromUUID(e.actionUUID)).nonNulls;
109106
BuiltList<Trigger> triggers = TriggerList.instance.state;
110107
final List<WearActionData> favoriteMap = allActions.map((e) => WearActionData(uuid: e.uuid, name: e.name)).toList();
@@ -138,7 +135,6 @@ Future<void> updateWearData() async {
138135
} catch (e, s) {
139136
_wearLogger.severe("Unable to send favorite actions to watch", e, s);
140137
}
141-
});
142138
}
143139

144140
@freezed

lib/Frontend/pages/move_list.dart

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,19 @@ import 'dart:math';
33
import 'package:built_collection/built_collection.dart';
44
import 'package:flutter/material.dart';
55
import 'package:flutter/services.dart';
6-
import 'package:flutter_joystick/flutter_joystick.dart';
76
import 'package:flutter_riverpod/flutter_riverpod.dart';
87
import 'package:go_router/go_router.dart';
98
import 'package:tail_app/Backend/command_runner.dart';
109
import 'package:tail_app/Frontend/Widgets/uwu_text.dart';
11-
import 'package:tail_app/Frontend/pages/direct_gear_control.dart';
1210
import 'package:uuid/uuid.dart';
1311

14-
import '../../Backend/Bluetooth/known_devices.dart';
15-
import '../../Backend/Definitions/Action/base_action.dart';
16-
import '../../Backend/Definitions/Device/device_definition.dart';
17-
import '../../Backend/logging_wrappers.dart';
18-
import '../../Backend/move_lists_backend.dart';
12+
import 'package:tail_app/Backend/Bluetooth/known_devices.dart';
13+
import 'package:tail_app/Backend/Definitions/Action/base_action.dart';
14+
import 'package:tail_app/Backend/Definitions/Device/device_definition.dart';
15+
import 'package:tail_app/Backend/logging_wrappers.dart';
16+
import 'package:tail_app/Backend/move_lists_backend.dart';
1917
import '../../Backend/analytics.dart';
20-
import '../../constants.dart';
18+
import 'package:tail_app/constants.dart';
2119
import '../Widgets/device_type_widget.dart';
2220
import '../Widgets/speed_widget.dart';
2321
import '../Widgets/tutorial_card.dart';

lib/main.dart

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,11 +157,18 @@ void initFlutter() {
157157
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); // keeps the splash screen visible
158158
}
159159

160+
bool _didInitHive = false;
160161
Future<void> initHive() async {
162+
if (_didInitHive) {
163+
return;
164+
}
161165
mainLogger.fine("Init Hive");
162-
163-
final Directory appDir = await getApplicationSupportDirectory();
164-
Hive.init(appDir.path);
166+
if (Platform.isAndroid || Platform.isIOS) {
167+
final Directory appDir = await getApplicationSupportDirectory();
168+
Hive.init(appDir.path);
169+
} else {
170+
Hive.init(Directory(".HiveTest").path);
171+
}
165172

166173
//Hive Type ID 1
167174
if (!Hive.isAdapterRegistered(BaseStoredDeviceAdapter().typeId)) {
@@ -229,6 +236,8 @@ Future<void> initHive() async {
229236
await Hive.openBox<AudioAction>(audioActionsBox);
230237
await Hive.openBox<MoveList>(sequencesBox);
231238
await Hive.openBox<BaseStoredDevice>(devicesBox);
239+
240+
_didInitHive = true;
232241
}
233242

234243
class TailApp extends ConsumerWidget {

pubspec.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1809,7 +1809,7 @@ packages:
18091809
source: hosted
18101810
version: "2.1.4"
18111811
stream_transform:
1812-
dependency: transitive
1812+
dependency: "direct main"
18131813
description:
18141814
name: stream_transform
18151815
sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871

0 commit comments

Comments
 (0)