Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions android/app/src/main/res/drawable/receive.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="251dp"
android:height="251dp"
android:viewportWidth="251"
android:viewportHeight="251">
<path
android:pathData="M125.5,0.5L125.5,0.5A125,125 0,0 1,250.5 125.5L250.5,125.5A125,125 0,0 1,125.5 250.5L125.5,250.5A125,125 0,0 1,0.5 125.5L0.5,125.5A125,125 0,0 1,125.5 0.5z"
android:fillColor="#233461"/>
<path
android:pathData="M125,154.99L91.2,121.19L99.55,112.61L119.06,132.13V65.63H130.94V132.13L150.45,112.61L158.8,121.19L125,154.99ZM79.94,184.38C75.94,184.38 72.55,182.99 69.78,180.22C67.01,177.45 65.63,174.06 65.63,170.06V148.6H77.5V170.06C77.5,170.67 77.75,171.23 78.26,171.74C78.77,172.25 79.33,172.5 79.94,172.5H170.06C170.67,172.5 171.23,172.25 171.74,171.74C172.25,171.23 172.5,170.67 172.5,170.06V148.6H184.38V170.06C184.38,174.06 182.99,177.45 180.22,180.22C177.45,182.99 174.06,184.38 170.06,184.38H79.94Z"
android:fillColor="#91B0FF"/>
</vector>
12 changes: 12 additions & 0 deletions android/app/src/main/res/drawable/send.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="251dp"
android:height="251dp"
android:viewportWidth="251"
android:viewportHeight="251">
<path
android:pathData="M125.5,0.5L125.5,0.5A125,125 0,0 1,250.5 125.5L250.5,125.5A125,125 0,0 1,125.5 250.5L125.5,250.5A125,125 0,0 1,0.5 125.5L0.5,125.5A125,125 0,0 1,125.5 0.5z"
android:fillColor="#233461"/>
<path
android:pathData="M63.02,177.86V137.2L113.5,125L63.02,112.8V72.14L188.52,125L63.02,177.86Z"
android:fillColor="#91B0FF"/>
</vector>
6 changes: 6 additions & 0 deletions ios/Runner/Assets.xcassets/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
23 changes: 23 additions & 0 deletions ios/Runner/Assets.xcassets/receive.imageset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "receive.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "receive 1.svg",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "receive 2.svg",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
4 changes: 4 additions & 0 deletions ios/Runner/Assets.xcassets/receive.imageset/receive 1.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions ios/Runner/Assets.xcassets/receive.imageset/receive 2.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions ios/Runner/Assets.xcassets/receive.imageset/receive.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions ios/Runner/Assets.xcassets/send.imageset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "send.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "send 1.svg",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "send 2.svg",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
4 changes: 4 additions & 0 deletions ios/Runner/Assets.xcassets/send.imageset/send 1.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions ios/Runner/Assets.xcassets/send.imageset/send 2.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions ios/Runner/Assets.xcassets/send.imageset/send.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
73 changes: 69 additions & 4 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import 'package:flutter_daemon/flutter_daemon.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:hive/hive.dart';
import 'package:cw_core/root_dir.dart';
import 'package:quick_actions/quick_actions.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cw_core/window_size.dart';
import 'package:logging/logging.dart';
Expand All @@ -67,6 +68,7 @@ import 'package:trezor_connect/trezor_connect.dart';
final navigatorKey = GlobalKey<NavigatorState>();
final rootKey = GlobalKey<RootState>();
final RouteObserver<PageRoute<dynamic>> routeObserver = RouteObserver<PageRoute<dynamic>>();
final quickActionsStream = StreamController<Uri?>.broadcast();

Future<void> main({Key? topLevelKey}) async {
await runAppWithZone(topLevelKey: topLevelKey);
Expand All @@ -77,6 +79,43 @@ Future<void> runAppWithZone({Key? topLevelKey}) async {

await runZonedGuarded(() async {
WidgetsFlutterBinding.ensureInitialized();

final Completer<String?> initialShortcutCompleter = Completer<String?>();

const QuickActions quickActions = QuickActions();

quickActions.initialize((String shortcutType) {
// Complete the completer only once (for cold starts)
if (!initialShortcutCompleter.isCompleted) {
initialShortcutCompleter.complete(shortcutType);
}

// Convert the shortcut type to a URI and add it to the stream
final uri = Uri.parse('cakewallet://quickaction/$shortcutType');
quickActionsStream.sink.add(uri);
});

quickActions.setShortcutItems(<ShortcutItem>[
const ShortcutItem(
type: 'send',
icon: 'send',
localizedTitle: 'Send',
localizedSubtitle: 'Send funds'),
const ShortcutItem(
type: 'receive',
icon: 'receive',
localizedTitle: 'Receive',
localizedSubtitle: 'Receive funds')
]);

// Fallback in case the initial shortcut is not received (normal cold start)
Future<void>.delayed(const Duration(milliseconds: 500), () {
if (!initialShortcutCompleter.isCompleted) {
initialShortcutCompleter.complete(null);
}
});

final initialQuickAction = await initialShortcutCompleter.future;
FlutterError.onError = ExceptionHandler.onError;

/// A callback that is invoked when an unhandled error occurs in the root
Expand Down Expand Up @@ -114,11 +153,11 @@ Future<void> runAppWithZone({Key? topLevelKey}) async {
runApp(
DefaultAssetBundle(
bundle: TestAssetBundle(),
child: App(key: topLevelKey),
child: App(key: topLevelKey, initialQuickAction:initialQuickAction,quickActionsStream: quickActionsStream.stream),
),
);
} else {
runApp(App(key: topLevelKey));
runApp(App(key: topLevelKey, initialQuickAction: initialQuickAction, quickActionsStream: quickActionsStream.stream));
}

isAppRunning = true;
Expand Down Expand Up @@ -334,9 +373,11 @@ Future<void> initialSetup({
}

class App extends StatefulWidget {
App({this.key});
App({this.key, this.initialQuickAction, required this.quickActionsStream});

final Key? key;
final String? initialQuickAction;
final Stream<Uri?> quickActionsStream;
@override
AppState createState() => AppState();
}
Expand Down Expand Up @@ -367,9 +408,13 @@ class AppState extends State<App> with SingleTickerProviderStateMixin {
statusBarBrightness: statusBarBrightness,
statusBarIconBrightness: statusBarIconBrightness));

final appRouteObserver = AppRouteObserver();

return Root(
key: widget.key ?? rootKey,
appStore: appStore,
initialQuickAction: widget.initialQuickAction,
quickActionsStream: widget.quickActionsStream,
authenticationStore: authenticationStore,
navigatorKey: navigatorKey,
authService: authService,
Expand All @@ -380,7 +425,7 @@ class AppState extends State<App> with SingleTickerProviderStateMixin {
child: ThemeProvider(
themeStore: appStore.themeStore,
materialAppBuilder: (context, theme, darkTheme, themeMode) => MaterialApp(
navigatorObservers: [routeObserver],
navigatorObservers: [routeObserver, appRouteObserver],
navigatorKey: navigatorKey,
debugShowCheckedModeBanner: false,
theme: theme,
Expand Down Expand Up @@ -506,3 +551,23 @@ Future<void> backgroundSync() async {
}
}
}

class AppRouteObserver extends RouteObserver<PageRoute<dynamic>> {
final AppStore appStore = getIt.get<AppStore>();

@override
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
super.didPush(route, previousRoute);
if (route is PageRoute) {
appStore.currentRouteName = route.settings.name;
}
}

@override
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
super.didPop(route, previousRoute);
if (previousRoute is PageRoute) {
appStore.currentRouteName = previousRoute.settings.name;
}
}
}
8 changes: 5 additions & 3 deletions lib/router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,13 @@ late RouteSettings currentRouteSettings;

Route<T> handleRouteWithPlatformAwareness<T>(
Widget Function(BuildContext) builder, {
RouteSettings? settings,
bool fullscreenDialog = false,
}) {
if (Platform.isIOS) {
return CupertinoPageRoute<T>(builder: builder, fullscreenDialog: fullscreenDialog);
return CupertinoPageRoute<T>(builder: builder, fullscreenDialog: fullscreenDialog, settings: settings);
} else {
return MaterialPageRoute<T>(builder: builder, fullscreenDialog: fullscreenDialog);
return MaterialPageRoute<T>(builder: builder, fullscreenDialog: fullscreenDialog, settings: settings);
}
}

Expand Down Expand Up @@ -412,14 +413,15 @@ Route<dynamic> createRoute(RouteSettings settings) {
param1: initialPaymentRequest,
param2: coinTypeToSpendFrom,
),
settings: settings
);

case Routes.sendTemplate:
return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<SendTemplatePage>());

case Routes.receive:
return CupertinoPageRoute<void>(builder: (context) => getIt.get<ReceivePage>());
return CupertinoPageRoute<void>(builder: (context) => getIt.get<ReceivePage>(), settings: settings);

case Routes.addressPage:
return handleRouteWithPlatformAwareness(
Expand Down
15 changes: 15 additions & 0 deletions lib/src/screens/root/root.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ class Root extends StatefulWidget {
required this.tradeMonitor,
required this.nodeSwitchingService,
required this.trezorConnect,
this.initialQuickAction,
required this.quickActionsStream,
}) : super(key: key);

final AuthenticationStore authenticationStore;
Expand All @@ -47,6 +49,8 @@ class Root extends StatefulWidget {
final TradeMonitor tradeMonitor;
final NodeSwitchingService nodeSwitchingService;
final TrezorConnect trezorConnect;
final String? initialQuickAction;
final Stream<Uri?> quickActionsStream;

@override
RootState createState() => RootState();
Expand Down Expand Up @@ -103,7 +107,18 @@ class RootState extends State<Root> with WidgetsBindingObserver {
handleDeepLinking(uri);
});

// listen for quick actions
widget.quickActionsStream.listen((Uri? uri) {
handleDeepLinking(uri);
});

handleDeepLinking(await getInitialUri());

if (widget.initialQuickAction != null) {
final uri = Uri.parse('cakewallet://quickaction/${widget.initialQuickAction}');
handleDeepLinking(uri);
}

} catch (e) {
printV(e);
}
Expand Down
3 changes: 3 additions & 0 deletions lib/store/app_store.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ abstract class AppStoreBase with Store {
@observable
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>? wallet;

@observable
String? currentRouteName;

WalletListStore walletList;

SettingsStore settingsStore;
Expand Down
24 changes: 24 additions & 0 deletions lib/view_model/link_view_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ class LinkViewModel {
bool get _isValidPaymentUri => currentLink?.path.isNotEmpty ?? false;
bool get isWalletConnectLink => currentLink?.authority == 'wc';
bool get isNanoGptLink => currentLink?.scheme == 'nano-gpt';
bool get isQuickActionLink =>
currentLink?.scheme == 'cakewallet' && currentLink?.host == 'quickaction';

String? getRouteToGo() {
if (isWalletConnectLink) {
Expand All @@ -35,6 +37,19 @@ class LinkViewModel {
return Routes.walletConnectConnectionsListing;
}

// Check for a quick action first.
if (isQuickActionLink) {
final action = currentLink!.pathSegments.isNotEmpty ? currentLink!.pathSegments.first : null;
switch (action) {
case 'send':
return Routes.send;
case 'receive':
return Routes.receive;
default:
return null;
}
}

if (authenticationStore.state == AuthenticationState.uninitialized) {
return null;
}
Expand Down Expand Up @@ -62,6 +77,10 @@ class LinkViewModel {
return currentLink;
}

if (isQuickActionLink) {
return null;
}

if (isNanoGptLink) {
switch (currentLink?.authority ?? '') {
case "exchange":
Expand Down Expand Up @@ -101,6 +120,11 @@ class LinkViewModel {
return;
}

// Prevent navigating to the same route again.
if (appStore.currentRouteName == route) {
return;
}

if (isNanoGptLink) {
if (route == Routes.buySellPage || route == Routes.exchange) {
await _errorToast(S.current.nano_gpt_thanks_message, fontSize: 14);
Expand Down
1 change: 1 addition & 0 deletions pubspec_base.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ dependencies:
ref: b48a9defddce036048bae02e2069ebcbd6824afc
torch_dart:
path: ./scripts/torch_dart
quick_actions: ^1.1.0

dev_dependencies:
flutter_test:
Expand Down
Loading