diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 7968d249..05bb8158 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -78,7 +78,6 @@
@@ -162,4 +161,4 @@
android:exported="true"
tools:ignore="ExportedContentProvider" />
-
\ No newline at end of file
+
diff --git a/lang/en.json b/lang/en.json
index f78b8cf3..6048007f 100644
--- a/lang/en.json
+++ b/lang/en.json
@@ -243,5 +243,10 @@
"get_apps": "Get watch apps",
"incompatible_faces": "Incompatible Watch Faces",
"incompatible_apps": "Incompatible Watch Apps"
+ },
+ "store_page": {
+ "faces": "Watchfaces",
+ "apps": "Apps",
+ "search_bar": "Search for something..."
}
}
diff --git a/lib/background/actions/calendar_action_handler.dart b/lib/background/actions/calendar_action_handler.dart
index f4f17618..e7e1473d 100644
--- a/lib/background/actions/calendar_action_handler.dart
+++ b/lib/background/actions/calendar_action_handler.dart
@@ -80,7 +80,7 @@ class CalendarActionHandler implements ActionHandler {
final calendarList =
await (_calendarList.streamWithExistingValue.firstSuccessOrError() as FutureOr>>);
- final calendars = calendarList.data?.value;
+ final calendars = calendarList.value;
if (calendars == null) {
return TimelineActionResponse(false);
}
diff --git a/lib/background/main_background.dart b/lib/background/main_background.dart
index e1a96254..565932fb 100644
--- a/lib/background/main_background.dart
+++ b/lib/background/main_background.dart
@@ -65,7 +65,7 @@ class BackgroundReceiver implements TimelineCallbacks {
final asyncValue =
await container.readUntilFirstSuccessOrError(preferencesProvider);
- return asyncValue.data!.value;
+ return asyncValue.value!;
});
TimelineCallbacks.setup(this);
diff --git a/lib/domain/api/appstore/appstore.dart b/lib/domain/api/appstore/appstore.dart
index 5bcbc0a4..16c37841 100644
--- a/lib/domain/api/appstore/appstore.dart
+++ b/lib/domain/api/appstore/appstore.dart
@@ -8,11 +8,11 @@ import 'package:cobble/infrastructure/datasources/web_services/appstore.dart';
final appstoreServiceProvider = FutureProvider((ref) async {
final boot = await (await ref.watch(bootServiceProvider.future)).config;
- final token = await (await ref.watch(tokenProvider.last));
+ final token = await (await ref.watch(tokenProvider.future));
final oauth = await ref.watch(oauthClientProvider.future);
final prefs = await ref.watch(preferencesProvider.future);
if (token == null) {
throw NoTokenException("Service requires a token but none was found in storage");
}
return AppstoreService(boot.appstore.base, prefs, oauth, token);
-});
\ No newline at end of file
+});
diff --git a/lib/domain/api/auth/auth.dart b/lib/domain/api/auth/auth.dart
index f3cdc23a..b23a9c49 100644
--- a/lib/domain/api/auth/auth.dart
+++ b/lib/domain/api/auth/auth.dart
@@ -9,7 +9,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
final authServiceProvider = FutureProvider((ref) async {
final boot = await (await ref.watch(bootServiceProvider.future)).config;
- final token = await (await ref.watch(tokenProvider.last));
+ final token = await (await ref.watch(tokenProvider.future));
final oauth = await ref.watch(oauthClientProvider.future);
final prefs = await ref.watch(preferencesProvider.future);
if (token == null) {
diff --git a/lib/domain/api/boot/boot.dart b/lib/domain/api/boot/boot.dart
index d7907c2a..c5cf453f 100644
--- a/lib/domain/api/boot/boot.dart
+++ b/lib/domain/api/boot/boot.dart
@@ -3,5 +3,5 @@ import 'package:cobble/infrastructure/datasources/web_services/boot.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
final bootServiceProvider = FutureProvider(
- (ref) async => BootService(await ref.watch(bootUrlProvider.last) ?? ""),
-);
\ No newline at end of file
+ (ref) async => BootService(await ref.watch(bootUrlProvider.future) ?? ""),
+);
diff --git a/lib/domain/calendar/calendar_list.dart b/lib/domain/calendar/calendar_list.dart
index 12245faa..c51c8f1b 100644
--- a/lib/domain/calendar/calendar_list.dart
+++ b/lib/domain/calendar/calendar_list.dart
@@ -32,7 +32,7 @@ class CalendarList extends StateNotifier>> {
await _permissionCheck.hasCalendarPermission();
if (hasCalendarPermission.value == false) {
- return AsyncValue.error([ResultError(0, "No permission")]);
+ return AsyncValue.error([ResultError(0, "No permission")], StackTrace.current);
}
final preferences = await _preferencesFuture;
@@ -43,7 +43,7 @@ class CalendarList extends StateNotifier>> {
final calendars = await _deviceCalendarPlugin.retrieveCalendars();
if (!calendars.isSuccess) {
- return AsyncValue.error(calendars.errors);
+ return AsyncValue.error(calendars.errors, StackTrace.current);
} else {
return AsyncValue.data(calendars.data
?.map((c) => SelectableCalendar(
diff --git a/lib/domain/calendar/calendar_syncer.db.dart b/lib/domain/calendar/calendar_syncer.db.dart
index a4ab02a7..9447bdcc 100644
--- a/lib/domain/calendar/calendar_syncer.db.dart
+++ b/lib/domain/calendar/calendar_syncer.db.dart
@@ -33,7 +33,7 @@ class CalendarSyncer {
return false;
}
- final allCalendars = allCalendarsResult.data!.value;
+ final allCalendars = allCalendarsResult.value!;
final now = _dateTimeProvider();
// 1 day is added since we need to get the start of the next day
diff --git a/lib/domain/timeline/watch_timeline_syncer.dart b/lib/domain/timeline/watch_timeline_syncer.dart
index e1ab11d0..900a0efe 100644
--- a/lib/domain/timeline/watch_timeline_syncer.dart
+++ b/lib/domain/timeline/watch_timeline_syncer.dart
@@ -134,7 +134,7 @@ class WatchTimelineSyncer {
return;
}
- final plugin = pluginValue.data!.value;
+ final plugin = pluginValue.value!;
const AndroidNotificationDetails androidPlatformChannelSpecifics =
AndroidNotificationDetails("WARNINGS", "Warnings",
diff --git a/lib/infrastructure/backgroundcomm/BackgroundRpc.dart b/lib/infrastructure/backgroundcomm/BackgroundRpc.dart
index 3dbb53c9..9cc53c69 100644
--- a/lib/infrastructure/backgroundcomm/BackgroundRpc.dart
+++ b/lib/infrastructure/backgroundcomm/BackgroundRpc.dart
@@ -71,10 +71,10 @@ class BackgroundRpc {
} else if (receivedMessage.errorResult != null) {
result = AsyncValue.error(
receivedMessage.errorResult!,
- stackTrace: receivedMessage.errorStacktrace,
+ receivedMessage.errorStacktrace ?? StackTrace.current,
);
} else {
- result = AsyncValue.error("Received result without any data.");
+ result = AsyncValue.error("Received result without any data.", StackTrace.current);
}
waitingCompleter.complete(result);
diff --git a/lib/infrastructure/datasources/web_services/auth.dart b/lib/infrastructure/datasources/web_services/auth.dart
index b52ada24..c22b1528 100644
--- a/lib/infrastructure/datasources/web_services/auth.dart
+++ b/lib/infrastructure/datasources/web_services/auth.dart
@@ -37,6 +37,10 @@ class AuthService extends Service {
}
}
+ OAuthToken get token {
+ return _token;
+ }
+
Future signOut() async {
await _oauth.signOut();
}
diff --git a/lib/infrastructure/datasources/web_services/rest_client.dart b/lib/infrastructure/datasources/web_services/rest_client.dart
index d2bbdf98..23be997d 100644
--- a/lib/infrastructure/datasources/web_services/rest_client.dart
+++ b/lib/infrastructure/datasources/web_services/rest_client.dart
@@ -28,7 +28,7 @@ class RESTClient {
..addAll(params ?? {}),
);
- HttpClientRequest req = await _client.getUrl(requestUri);
+ HttpClientRequest req = await _client.openUrl(method, requestUri);
if (token != null) {
req.headers.add("Authorization", "Bearer $token");
}
@@ -58,4 +58,4 @@ class RESTClient {
}
return _completer.future;
}
-}
\ No newline at end of file
+}
diff --git a/lib/infrastructure/datasources/workarounds.dart b/lib/infrastructure/datasources/workarounds.dart
index 44bb75ea..f0059828 100644
--- a/lib/infrastructure/datasources/workarounds.dart
+++ b/lib/infrastructure/datasources/workarounds.dart
@@ -17,7 +17,7 @@ final neededWorkaroundsProvider = StreamProvider>((ref) {
return Stream>.empty();
}
- final preferences = preferencesData.data!.value;
+ final preferences = preferencesData.value!;
fetchControls() async {
final workaroundControl = WorkaroundsControl();
diff --git a/lib/localization/model/model_generator.model.dart b/lib/localization/model/model_generator.model.dart
index 4681a73e..4b01aecd 100644
--- a/lib/localization/model/model_generator.model.dart
+++ b/lib/localization/model/model_generator.model.dart
@@ -166,6 +166,13 @@ class Language {
)
final LanguageLockerPage lockerPage;
+ @JsonKey(
+ name: 'store_page',
+ required: true,
+ disallowNullValue: true,
+ )
+ final LanguageStorePage storePage;
+
Language(
this.common,
this.firstRun,
@@ -187,6 +194,7 @@ class Language {
this.systemApps,
this.calendar,
this.lockerPage,
+ this.storePage,
);
factory Language.fromJson(Map json) =>
@@ -1716,6 +1724,38 @@ class LanguageSplashPage {
_$LanguageSplashPageFromJson(json);
}
+@JsonSerializable(
+ createToJson: false,
+ disallowUnrecognizedKeys: true,
+)
+class LanguageStorePage {
+ @JsonKey(
+ name: 'faces',
+ required: true,
+ disallowNullValue: true,
+ )
+ final String faces;
+
+ @JsonKey(
+ name: 'apps',
+ required: true,
+ disallowNullValue: true,
+ )
+ final String apps;
+
+ @JsonKey(
+ name: 'search_bar',
+ required: true,
+ disallowNullValue: true,
+ )
+ final String searchBar;
+
+ LanguageStorePage(this.faces, this.apps, this.searchBar);
+
+ factory LanguageStorePage.fromJson(Map json) =>
+ _$LanguageStorePageFromJson(json);
+}
+
@JsonSerializable(
createToJson: false,
disallowUnrecognizedKeys: true,
diff --git a/lib/localization/model/model_generator.model.g.dart b/lib/localization/model/model_generator.model.g.dart
index 2ce3a42f..73c9a6ff 100644
--- a/lib/localization/model/model_generator.model.g.dart
+++ b/lib/localization/model/model_generator.model.g.dart
@@ -29,7 +29,8 @@ Language _$LanguageFromJson(Map json) {
'settings',
'system_apps',
'calendar',
- 'locker_page'
+ 'locker_page',
+ 'store_page'
],
requiredKeys: const [
'common',
@@ -51,7 +52,8 @@ Language _$LanguageFromJson(Map json) {
'settings',
'system_apps',
'calendar',
- 'locker_page'
+ 'locker_page',
+ 'store_page'
],
disallowNullValues: const [
'common',
@@ -73,7 +75,8 @@ Language _$LanguageFromJson(Map json) {
'settings',
'system_apps',
'calendar',
- 'locker_page'
+ 'locker_page',
+ 'store_page'
],
);
return Language(
@@ -103,6 +106,7 @@ Language _$LanguageFromJson(Map json) {
LanguageSystemApps.fromJson(json['system_apps'] as Map),
LanguageCalendar.fromJson(json['calendar'] as Map),
LanguageLockerPage.fromJson(json['locker_page'] as Map),
+ LanguageStorePage.fromJson(json['store_page'] as Map),
);
}
@@ -860,6 +864,20 @@ LanguageSplashPage _$LanguageSplashPageFromJson(Map json) {
);
}
+LanguageStorePage _$LanguageStorePageFromJson(Map json) {
+ $checkKeys(
+ json,
+ allowedKeys: const ['faces', 'apps', 'search_bar'],
+ requiredKeys: const ['faces', 'apps', 'search_bar'],
+ disallowNullValues: const ['faces', 'apps', 'search_bar'],
+ );
+ return LanguageStorePage(
+ json['faces'] as String,
+ json['apps'] as String,
+ json['search_bar'] as String,
+ );
+}
+
LanguageSystemApps _$LanguageSystemAppsFromJson(Map json) {
$checkKeys(
json,
diff --git a/lib/main.dart b/lib/main.dart
index d05ae4c9..ee710292 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -6,6 +6,7 @@ import 'package:cobble/infrastructure/datasources/preferences.dart';
import 'package:cobble/localization/localization.dart';
import 'package:cobble/localization/localization_delegate.dart';
import 'package:cobble/localization/model/model_generator.model.dart';
+import 'package:cobble/ui/home/tabs/store_tab.dart';
import 'package:cobble/ui/splash/splash_page.dart';
import 'package:cobble/ui/theme/cobble_scheme.dart';
import 'package:cobble/ui/theme/cobble_theme.dart';
@@ -88,6 +89,9 @@ class MyApp extends HookConsumerWidget {
child: MaterialApp(
onGenerateTitle: (context) => tr.common.title,
theme: CobbleTheme.appTheme(brightness),
+ routes: {
+ '/appstore': (context) => StoreTab(),
+ },
home: SplashPage(),
// List all of the app's supported locales here
supportedLocales: supportedLocales,
diff --git a/lib/ui/devoptions/debug_options_page.dart b/lib/ui/devoptions/debug_options_page.dart
index e8d75a0a..1f4aa5f0 100644
--- a/lib/ui/devoptions/debug_options_page.dart
+++ b/lib/ui/devoptions/debug_options_page.dart
@@ -11,11 +11,11 @@ class DebugOptionsPage extends HookConsumerWidget implements CobbleScreen {
@override
Widget build(BuildContext context, WidgetRef ref) {
final preferences = ref.watch(preferencesProvider);
- final bootUrl = ref.watch(bootUrlProvider).data?.value ?? "";
+ final bootUrl = ref.watch(bootUrlProvider).value ?? "";
final shouldOverrideBoot =
- ref.watch(shouldOverrideBootProvider).data?.value ?? false;
+ ref.watch(shouldOverrideBootProvider).value ?? false;
final overrideBootUrl =
- ref.watch(overrideBootValueProvider).data?.value ?? "";
+ ref.watch(overrideBootValueProvider).value ?? "";
final bootUrlController = useTextEditingController();
final bootOverrideUrlController = useTextEditingController();
diff --git a/lib/ui/home/tabs/store_tab.dart b/lib/ui/home/tabs/store_tab.dart
index 87c81167..c3909ddb 100644
--- a/lib/ui/home/tabs/store_tab.dart
+++ b/lib/ui/home/tabs/store_tab.dart
@@ -1,22 +1,337 @@
+import 'dart:async';
+import 'dart:collection';
+import 'dart:convert';
+import 'dart:io';
+import 'package:cobble/domain/api/appstore/locker_sync.dart';
+import 'package:cobble/domain/api/auth/auth.dart';
+import 'package:cobble/domain/api/auth/oauth_token.dart';
+import 'package:cobble/domain/connection/connection_state_provider.dart';
+import 'package:cobble/domain/entities/hardware_platform.dart';
+import 'package:cobble/infrastructure/datasources/web_services/auth.dart';
+import 'package:cobble/localization/localization.dart';
+import 'package:cobble/ui/common/components/cobble_button.dart';
+import 'package:cobble/ui/common/icons/fonts/rebble_icons.dart';
+import 'package:cobble/ui/common/icons/watch_icon.dart';
import 'package:cobble/ui/router/cobble_scaffold.dart';
import 'package:cobble/ui/router/cobble_screen.dart';
+import 'package:cobble/ui/theme/with_cobble_theme.dart';
import 'package:flutter/material.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter_hooks/flutter_hooks.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:package_info_plus/package_info_plus.dart';
+import 'package:url_launcher/url_launcher.dart';
import 'package:webview_flutter/webview_flutter.dart';
+import 'package:cobble/infrastructure/pigeons/pigeons.g.dart';
+import 'package:path/path.dart' as path;
+import 'package:uuid_type/uuid_type.dart';
-class StoreTab extends StatefulWidget implements CobbleScreen {
- @override
- State createState() => new _StoreTabState();
+class _TabConfig {
+ final String label;
+ final Uri url;
+
+ _TabConfig(this.label, this.url);
}
-class _StoreTabState extends State {
+class StoreTab extends HookConsumerWidget implements CobbleScreen {
+ final _config = [
+ _TabConfig(tr.storePage.faces, Uri.parse("https://store-beta.rebble.io/faces")),
+ _TabConfig(tr.storePage.apps, Uri.parse("https://store-beta.rebble.io/apps")),
+ ];
+
@override
- Widget build(BuildContext context) {
+ Widget build(BuildContext context, WidgetRef ref) {
+ // Those are args from outside that tell us what application to display
+ final args = ModalRoute.of(context)!.settings.arguments as AppstoreArguments;
+ final appUrl = "https://store-beta.rebble.io/app/";
+
+ final indexTab = useState(0);
+ final pageTitle = useState("Loading");
+ final backButton = useState(false);
+ final searchBar = useState(false);
+ final attrs = useState