From 610235a02d3417af5eb452b763cdb750eab74263 Mon Sep 17 00:00:00 2001 From: Jacob Michalskie Date: Tue, 9 Jan 2024 01:14:26 +0100 Subject: [PATCH] More improvements to the store tab --- android/app/src/main/AndroidManifest.xml | 3 +- lang/en.json | 2 +- .../datasources/web_services/rest_client.dart | 4 +- lib/main.dart | 4 + lib/ui/home/tabs/store_tab.dart | 163 ++++++++++-------- lib/ui/router/uri_navigator.dart | 16 +- 6 files changed, 112 insertions(+), 80 deletions(-) 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 6fa0ad08..6048007f 100644 --- a/lang/en.json +++ b/lang/en.json @@ -246,7 +246,7 @@ }, "store_page": { "faces": "Watchfaces", - "apps": "Apps / Timeline", + "apps": "Apps", "search_bar": "Search for something..." } } 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/main.dart b/lib/main.dart index 80c8b09f..4217f803 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'; @@ -89,6 +90,9 @@ class MyApp extends HookWidget { 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/home/tabs/store_tab.dart b/lib/ui/home/tabs/store_tab.dart index 989d2a47..76daee13 100644 --- a/lib/ui/home/tabs/store_tab.dart +++ b/lib/ui/home/tabs/store_tab.dart @@ -2,12 +2,11 @@ import 'dart:async'; import 'dart:collection'; import 'dart:convert'; import 'dart:io'; -import 'package:cobble/domain/api/appstore/appstore.dart'; +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/appstore.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'; @@ -25,6 +24,7 @@ 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 _TabConfig { final String label; @@ -35,12 +35,16 @@ class _TabConfig { class StoreTab extends HookWidget implements CobbleScreen { final _config = [ - _TabConfig(tr.storePage.faces, Uri.parse("https://apps.rebble.io/en_US/watchfaces")), - _TabConfig(tr.storePage.apps, Uri.parse("https://apps.rebble.io/en_US/watchapps")), + _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) { + // 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); @@ -52,18 +56,11 @@ class StoreTab extends HookWidget implements CobbleScreen { useMemoized(() => Completer()); final searchController = useTextEditingController(); - final _appstore = useProvider(appstoreServiceProvider.future); + final locker_sync = useProvider(lockerSyncProvider); final connectionState = useProvider(connectionStateProvider.state); final _auth = useProvider(authServiceProvider.future); - - void onEachPageLoad() async { - WebViewController controller = await _controller.future; - String current = await controller.currentUrl() ?? baseUrl.value.toString(); - bool canGoBack = await controller.canGoBack(); - backButton.value = canGoBack && baseUrl.value != Uri.parse(current); - } void handleRequest(String methodName, Map data) async { WebViewController controller = await _controller.future; @@ -71,6 +68,13 @@ class StoreTab extends HookWidget implements CobbleScreen { controller.runJavascript("PebbleBridge.handleRequest(${json.encode(data)})"); } + void onEachPageLoad() async { + WebViewController controller = await _controller.future; + String current = await controller.currentUrl() ?? baseUrl.value.toString(); + bool canGoBack = await controller.canGoBack(); + backButton.value = canGoBack && baseUrl.value != Uri.parse(current); + } + void handleResponse(Map data, int? callback) async { WebViewController controller = await _controller.future; Map response = HashMap(); @@ -79,13 +83,12 @@ class StoreTab extends HookWidget implements CobbleScreen { controller.runJavascript("PebbleBridge.handleResponse(${json.encode(response)})"); } - void installApp(String uuid, int? callback) async { - AppstoreService appstore = await _appstore; + void installApp(Uuid uuid, int? callback) async { Map data = HashMap(); data['added_to_locker'] = false; try { - await appstore.addToLocker(uuid); + await locker_sync.addToLocker(uuid); data['added_to_locker'] = true; handleResponse(data, callback); } on Exception { @@ -106,7 +109,7 @@ class StoreTab extends HookWidget implements CobbleScreen { launchURL(data["url"]); break; case "loadAppToDeviceAndLocker": - installApp(data["id"], callback); + installApp(Uuid.parse(data["uuid"]), callback); break; case "setVisibleApp": // In the original app, this was used for displaying sharing button on the app view @@ -139,7 +142,7 @@ class StoreTab extends HookWidget implements CobbleScreen { AuthService auth = await _auth; OAuthToken token = auth.token; if (auth != null) { - WebViewCookie accessTokenCookie = new WebViewCookie(name: 'access_token', value: token.accessToken, domain: 'apps.rebble.io'); + WebViewCookie accessTokenCookie = new WebViewCookie(name: 'access_token', value: token.accessToken, domain: 'store-beta.rebble.io'); CookieManager cookieManager = new CookieManager(); cookieManager.setCookie(accessTokenCookie); } @@ -164,7 +167,6 @@ class StoreTab extends HookWidget implements CobbleScreen { baseUrl.value = _config[newValue].url.replace(queryParameters: attrs.value); _setWebviewUrl(baseUrl.value); // This would be changed anyway, but it looked ugly when it jumped from the previous title - pageTitle.value = _config[newValue].label; } Future initialSetup() async { @@ -175,7 +177,14 @@ class StoreTab extends HookWidget implements CobbleScreen { useEffect(() { initialSetup() - .whenComplete(() => { _setIndexTab(indexTab.value) }); + .whenComplete(() { + if (args == null) { + _setIndexTab(indexTab.value); + } else { + Uri appUri = Uri.parse(appUrl + args.id).replace(queryParameters: attrs.value); + _setWebviewUrl(appUri); + } + }); }, [connectionState], ); @@ -188,15 +197,16 @@ class StoreTab extends HookWidget implements CobbleScreen { tooltip: MaterialLocalizations.of(context).backButtonTooltip, ) : null, - actions: [ + actions: args == null + ? [ IconButton( - icon: Icon(RebbleIcons.search), - onPressed: () { - searchBar.value = true; - }, - tooltip: MaterialLocalizations.of(context).searchFieldLabel, - ), - ], + icon: Icon(RebbleIcons.search), + onPressed: () { + searchBar.value = true; + }, + tooltip: MaterialLocalizations.of(context).searchFieldLabel, + ), + ] : [], bottomAppBar: searchBar.value ? PreferredSize( preferredSize: Size.fromHeight(57), @@ -234,55 +244,60 @@ class StoreTab extends HookWidget implements CobbleScreen { ), ) : null, - titleWidget: DropdownButton( - value: indexTab.value, - icon: Icon(RebbleIcons.dropdown), - iconSize: 25, - underline: Container( - height: 0, - ), - onChanged: (int? newValue) => _setIndexTab(newValue!), - selectedItemBuilder: (BuildContext context) { - return [0, 1].map((int value) { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox(width: 25.0), //Offset to appear centered - Container( - width: 125, - height: 57, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - pageTitle.value, - overflow: TextOverflow.ellipsis, - ), - if (pageTitle.value != _config[value].label) ...[ - SizedBox(height: 4), + titleWidget: args == null + ? DropdownButton( + value: indexTab.value, + icon: Icon(RebbleIcons.dropdown), + iconSize: 25, + underline: Container( + height: 0, + ), + onChanged: (int? newValue) => _setIndexTab(newValue!), + selectedItemBuilder: (BuildContext context) { + return [0, 1].map((int value) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(width: 25.0), //Offset to appear centered + Container( + width: 125, + height: 57, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ Text( - _config[value].label, - style: context.theme.appBarTheme.textTheme!.headline6! - .copyWith( - fontSize: 14, - color: context.scheme!.muted, - ), + pageTitle.value, + overflow: TextOverflow.ellipsis, ), + if (pageTitle.value != _config[value].label) ...[ + SizedBox(height: 4), + Text( + _config[value].label, + style: context.theme.appBarTheme.textTheme!.headline6! + .copyWith( + fontSize: 14, + color: context.scheme!.muted, + ), + ), + ], ], - ], + ), ), - ), - ], + ], + ); + }).toList(); + }, + items: [0, 1].map>((int value) { + return DropdownMenuItem( + value: value, + child: Text(_config[value].label), ); - }).toList(); - }, - items: [0, 1].map>((int value) { - return DropdownMenuItem( - value: value, - child: Text(_config[value].label), - ); - }).toList(), - ), + }).toList(), + ) + : Text( + pageTitle.value, + overflow: TextOverflow.ellipsis, + ), child: WebView( initialUrl: baseUrl.value.toString(), javascriptMode: JavascriptMode.unrestricted, @@ -314,3 +329,9 @@ class StoreTab extends HookWidget implements CobbleScreen { } } } + +class AppstoreArguments { + final String id; + + AppstoreArguments(this.id); +} diff --git a/lib/ui/router/uri_navigator.dart b/lib/ui/router/uri_navigator.dart index 3ed844cb..a9ba58a8 100644 --- a/lib/ui/router/uri_navigator.dart +++ b/lib/ui/router/uri_navigator.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:cobble/ui/home/tabs/store_tab.dart'; import 'package:cobble/ui/router/cobble_navigator.dart'; import 'package:cobble/ui/screens/install_prompt.dart'; import 'package:flutter/cupertino.dart'; @@ -25,9 +26,16 @@ class UriNavigator implements IntentCallbacks { @override void openUri(StringWrapper arg) async { - String uri = arg.value!; + Uri uri = Uri.parse(arg.value!); + if (uri.isScheme("pebble") && uri.host == 'appstore') { + String id = uri.pathSegments[0]; + // TODO: We currently set up a minified StoreTab() for this, but it would be + // better if we just used the StoreTab() that already exists and navigated + // directly to the app from it (ie. handleRequest('navigate', { 'url': '/application/$id' })) + Navigator.of(_context).pushNamed('/appstore', arguments: AppstoreArguments(id)); + } - if (Platform.isAndroid && !uri.startsWith("content://")) { + if (Platform.isAndroid && !uri.isScheme("content")) { // Only content URIs are supported return; } @@ -35,10 +43,10 @@ class UriNavigator implements IntentCallbacks { AppInstallControl control = AppInstallControl(); final uriWrapper = StringWrapper(); - uriWrapper.value = uri; + uriWrapper.value = uri.toString(); final pbwResult = await control.getAppInfo(uriWrapper); - _context.push(InstallPrompt(uri, pbwResult)); + _context.push(InstallPrompt(uri.toString(), pbwResult)); } }