mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-12-23 11:59:30 +00:00
794 lines
28 KiB
Dart
794 lines
28 KiB
Dart
/*
|
|
* This file is part of Stack Wallet.
|
|
*
|
|
* Copyright (c) 2023 Cypher Stack
|
|
* All Rights Reserved.
|
|
* The code is distributed under GPLv3 license, see LICENSE file for details.
|
|
* Generated by Cypher Stack on 2023-05-26
|
|
*
|
|
*/
|
|
|
|
import 'dart:async';
|
|
import 'dart:io';
|
|
import 'dart:math';
|
|
|
|
import 'package:coinlib_flutter/coinlib_flutter.dart';
|
|
import 'package:cw_core/node.dart';
|
|
import 'package:cw_core/pathForWallet.dart';
|
|
import 'package:cw_core/unspent_coins_info.dart';
|
|
import 'package:cw_core/wallet_info.dart';
|
|
import 'package:cw_core/wallet_type.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_libmonero/monero/monero.dart';
|
|
import 'package:flutter_libmonero/wownero/wownero.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|
import 'package:google_fonts/google_fonts.dart';
|
|
import 'package:hive_flutter/hive_flutter.dart';
|
|
import 'package:isar/isar.dart';
|
|
import 'package:keyboard_dismisser/keyboard_dismisser.dart';
|
|
import 'package:path_provider/path_provider.dart';
|
|
import 'package:window_size/window_size.dart';
|
|
|
|
import 'app_config.dart';
|
|
import 'db/db_version_migration.dart';
|
|
import 'db/hive/db.dart';
|
|
import 'db/isar/main_db.dart';
|
|
import 'models/exchange/change_now/exchange_transaction.dart';
|
|
import 'models/exchange/change_now/exchange_transaction_status.dart';
|
|
import 'models/exchange/response_objects/trade.dart';
|
|
import 'models/isar/models/isar_models.dart';
|
|
import 'models/models.dart';
|
|
import 'models/node_model.dart';
|
|
import 'models/notification_model.dart';
|
|
import 'models/trade_wallet_lookup.dart';
|
|
import 'pages/home_view/home_view.dart';
|
|
import 'pages/intro_view.dart';
|
|
import 'pages/loading_view.dart';
|
|
import 'pages/pinpad_views/create_pin_view.dart';
|
|
import 'pages/pinpad_views/lock_screen_view.dart';
|
|
import 'pages/settings_views/global_settings_view/stack_backup_views/restore_from_encrypted_string_view.dart';
|
|
import 'pages_desktop_specific/password/desktop_login_view.dart';
|
|
import 'providers/db/main_db_provider.dart';
|
|
import 'providers/desktop/storage_crypto_handler_provider.dart';
|
|
import 'providers/global/auto_swb_service_provider.dart';
|
|
import 'providers/global/base_currencies_provider.dart';
|
|
// import 'package:stackwallet/providers/global/has_authenticated_start_state_provider.dart';
|
|
import 'providers/global/trades_service_provider.dart';
|
|
import 'providers/providers.dart';
|
|
import 'route_generator.dart';
|
|
// import 'package:stackwallet/services/buy/buy_data_loading_service.dart';
|
|
import 'services/debug_service.dart';
|
|
import 'services/exchange/exchange_data_loading_service.dart';
|
|
import 'services/locale_service.dart';
|
|
import 'services/node_service.dart';
|
|
import 'services/notifications_api.dart';
|
|
import 'services/notifications_service.dart';
|
|
import 'services/tor_service.dart';
|
|
import 'services/trade_service.dart';
|
|
import 'themes/theme_providers.dart';
|
|
import 'themes/theme_service.dart';
|
|
import 'utilities/constants.dart';
|
|
import 'utilities/enums/backup_frequency_type.dart';
|
|
import 'utilities/flutter_secure_storage_interface.dart';
|
|
import 'utilities/logger.dart';
|
|
import 'utilities/prefs.dart';
|
|
import 'utilities/stack_file_system.dart';
|
|
import 'utilities/util.dart';
|
|
import 'wallets/isar/providers/all_wallets_info_provider.dart';
|
|
import 'widgets/crypto_notifications.dart';
|
|
|
|
final openedFromSWBFileStringStateProvider =
|
|
StateProvider<String?>((ref) => null);
|
|
|
|
// main() is the entry point to the app. It initializes Hive (local database),
|
|
// runs the MyApp widget and checks for new users, caching the value in the
|
|
// miscellaneous box for later use
|
|
void main(List<String> args) async {
|
|
WidgetsFlutterBinding.ensureInitialized();
|
|
|
|
if (Util.isDesktop && args.length == 2 && args.first == "-d") {
|
|
StackFileSystem.setDesktopOverrideDir(args.last);
|
|
}
|
|
|
|
// Tell flutter_libmonero how to get access to the application dir
|
|
FS.setApplicationRootDirectoryFunction(
|
|
StackFileSystem.applicationRootDirectory,
|
|
);
|
|
// TODO set any other external libs file paths (bad external lib design workaround)
|
|
|
|
final loadCoinlibFuture = loadCoinlib();
|
|
|
|
GoogleFonts.config.allowRuntimeFetching = false;
|
|
if (Platform.isIOS) {
|
|
Util.libraryPath = await getLibraryDirectory();
|
|
}
|
|
Screen? screen;
|
|
if (Platform.isLinux || (Util.isDesktop && !Platform.isIOS)) {
|
|
screen = await getCurrentScreen();
|
|
Util.screenWidth = screen?.frame.width;
|
|
}
|
|
|
|
if (Util.isDesktop && !Platform.isIOS) {
|
|
setWindowTitle(AppConfig.appName);
|
|
setWindowMinSize(const Size(1220, 100));
|
|
setWindowMaxSize(Size.infinite);
|
|
|
|
final screenHeight = screen?.frame.height;
|
|
if (screenHeight != null) {
|
|
// starting to height be 3/4 screen height or 900, whichever is smaller
|
|
final height = min<double>(screenHeight * 0.75, 900);
|
|
setWindowFrame(
|
|
Rect.fromLTWH(0, 0, 1220, height),
|
|
);
|
|
}
|
|
}
|
|
|
|
// FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
|
|
if (!(Logging.isArmLinux || Logging.isTestEnv)) {
|
|
final isar = await Isar.open(
|
|
[LogSchema],
|
|
directory: (await StackFileSystem.applicationIsarDirectory()).path,
|
|
inspector: false,
|
|
maxSizeMiB: 512,
|
|
);
|
|
await Logging.instance.init(isar);
|
|
await DebugService.instance.init(isar);
|
|
|
|
// clear out all info logs on startup. No need to await and block
|
|
unawaited(DebugService.instance.deleteLogsOlderThan());
|
|
}
|
|
|
|
// Registering Transaction Model Adapters
|
|
Hive.registerAdapter(TransactionDataAdapter());
|
|
Hive.registerAdapter(TransactionChunkAdapter());
|
|
Hive.registerAdapter(TransactionAdapter());
|
|
Hive.registerAdapter(InputAdapter());
|
|
Hive.registerAdapter(OutputAdapter());
|
|
|
|
// Registering Utxo Model Adapters
|
|
Hive.registerAdapter(UtxoDataAdapter());
|
|
Hive.registerAdapter(UtxoObjectAdapter());
|
|
Hive.registerAdapter(StatusAdapter());
|
|
|
|
// Registering Lelantus Model Adapters
|
|
Hive.registerAdapter(LelantusCoinAdapter());
|
|
|
|
// notification model adapter
|
|
Hive.registerAdapter(NotificationModelAdapter());
|
|
|
|
// change now trade adapters
|
|
Hive.registerAdapter(ExchangeTransactionAdapter());
|
|
Hive.registerAdapter(ExchangeTransactionStatusAdapter());
|
|
|
|
Hive.registerAdapter(TradeAdapter());
|
|
|
|
// reference lookup data adapter
|
|
Hive.registerAdapter(TradeWalletLookupAdapter());
|
|
|
|
// node model adapter
|
|
Hive.registerAdapter(NodeModelAdapter());
|
|
|
|
Hive.registerAdapter(NodeAdapter());
|
|
|
|
if (!Hive.isAdapterRegistered(WalletInfoAdapter().typeId)) {
|
|
Hive.registerAdapter(WalletInfoAdapter());
|
|
}
|
|
|
|
Hive.registerAdapter(WalletTypeAdapter());
|
|
|
|
Hive.registerAdapter(UnspentCoinsInfoAdapter());
|
|
await Hive.initFlutter(
|
|
(await StackFileSystem.applicationHiveDirectory()).path);
|
|
|
|
await Hive.openBox<dynamic>(DB.boxNameDBInfo);
|
|
await Hive.openBox<dynamic>(DB.boxNamePrefs);
|
|
await Prefs.instance.init();
|
|
|
|
// TODO:
|
|
// This should be moved to happen during the loading animation instead of
|
|
// showing a blank screen for 4-10 seconds.
|
|
// Some refactoring will need to be done here to make sure we don't make any
|
|
// network calls before starting up tor
|
|
if (Prefs.instance.useTor) {
|
|
TorService.sharedInstance.init(
|
|
torDataDirPath: (await StackFileSystem.applicationTorDirectory()).path,
|
|
);
|
|
await TorService.sharedInstance.start();
|
|
}
|
|
|
|
await StackFileSystem.initThemesDir();
|
|
|
|
// Desktop migrate handled elsewhere (currently desktop_login_view.dart)
|
|
if (!Util.isDesktop) {
|
|
int dbVersion = DB.instance.get<dynamic>(
|
|
boxName: DB.boxNameDBInfo, key: "hive_data_version") as int? ??
|
|
0;
|
|
if (dbVersion < Constants.currentDataVersion) {
|
|
try {
|
|
await DbVersionMigrator().migrate(
|
|
dbVersion,
|
|
secureStore: const SecureStorageWrapper(
|
|
store: FlutterSecureStorage(),
|
|
isDesktop: false,
|
|
),
|
|
);
|
|
} catch (e, s) {
|
|
Logging.instance.log("Cannot migrate mobile database\n$e $s",
|
|
level: LogLevel.Error, printFullLength: true);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!Platform.isWindows) {
|
|
monero.onStartup();
|
|
}
|
|
if (!Platform.isLinux && !Platform.isWindows) {
|
|
wownero.onStartup();
|
|
}
|
|
|
|
// SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
|
|
// overlays: [SystemUiOverlay.bottom]);
|
|
await NotificationApi.init();
|
|
|
|
await loadCoinlibFuture;
|
|
|
|
await MainDB.instance.initMainDB();
|
|
ThemeService.instance.init(MainDB.instance);
|
|
|
|
// check and update or install default themes
|
|
await ThemeService.instance.checkDefaultThemesOnStartup();
|
|
|
|
// verify current user preference theme and revert to default
|
|
// if problems are found to prevent app being unusable
|
|
if (!(await ThemeService.instance
|
|
.verifyInstalled(themeId: Prefs.instance.themeId))) {
|
|
Prefs.instance.themeId = "light";
|
|
}
|
|
|
|
// verify current user preference light brightness theme and revert to default
|
|
// if problems are found to prevent app being unusable
|
|
if (!(await ThemeService.instance
|
|
.verifyInstalled(themeId: Prefs.instance.systemBrightnessLightThemeId))) {
|
|
Prefs.instance.systemBrightnessLightThemeId = "light";
|
|
}
|
|
|
|
// verify current user preference dark brightness theme and revert to default
|
|
// if problems are found to prevent app being unusable
|
|
if (!(await ThemeService.instance
|
|
.verifyInstalled(themeId: Prefs.instance.systemBrightnessDarkThemeId))) {
|
|
Prefs.instance.systemBrightnessDarkThemeId = "dark";
|
|
}
|
|
|
|
runApp(const ProviderScope(child: MyApp()));
|
|
}
|
|
|
|
/// MyApp initialises relevant services with a MultiProvider
|
|
class MyApp extends StatelessWidget {
|
|
const MyApp({Key? key}) : super(key: key);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final localeService = LocaleService();
|
|
localeService.loadLocale();
|
|
|
|
return const KeyboardDismisser(
|
|
child: MaterialAppWithTheme(),
|
|
);
|
|
}
|
|
}
|
|
|
|
// Sidenote: MaterialAppWithTheme and InitView are only separated for clarity. No other reason.
|
|
|
|
class MaterialAppWithTheme extends ConsumerStatefulWidget {
|
|
const MaterialAppWithTheme({
|
|
Key? key,
|
|
}) : super(key: key);
|
|
|
|
@override
|
|
ConsumerState<MaterialAppWithTheme> createState() =>
|
|
_MaterialAppWithThemeState();
|
|
}
|
|
|
|
class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|
with WidgetsBindingObserver {
|
|
static const platform = MethodChannel("STACK_WALLET_RESTORE");
|
|
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
|
|
|
// late final Wallets _wallets;
|
|
// late final Prefs _prefs;
|
|
late final NotificationsService _notificationsService;
|
|
late final NodeService _nodeService;
|
|
late final TradesService _tradesService;
|
|
|
|
late final Completer<void> loadingCompleter;
|
|
|
|
bool didLoad = false;
|
|
bool didLoadShared = false;
|
|
bool _desktopHasPassword = false;
|
|
|
|
Future<void> loadShared() async {
|
|
if (didLoadShared) {
|
|
return;
|
|
}
|
|
didLoadShared = true;
|
|
|
|
await DB.instance.init();
|
|
await ref.read(prefsChangeNotifierProvider).init();
|
|
|
|
final familiarity = ref.read(prefsChangeNotifierProvider).familiarity + 1;
|
|
ref.read(prefsChangeNotifierProvider).familiarity = familiarity;
|
|
|
|
Constants.exchangeForExperiencedUsers(familiarity);
|
|
|
|
if (Util.isDesktop) {
|
|
_desktopHasPassword =
|
|
await ref.read(storageCryptoHandlerProvider).hasPassword();
|
|
}
|
|
}
|
|
|
|
Future<void> load() async {
|
|
try {
|
|
if (didLoad) {
|
|
return;
|
|
}
|
|
didLoad = true;
|
|
|
|
if (!Util.isDesktop) {
|
|
await loadShared();
|
|
}
|
|
|
|
ref.read(applicationThemesDirectoryPathProvider.notifier).state =
|
|
StackFileSystem.themesDir!.path;
|
|
|
|
_notificationsService = ref.read(notificationsProvider);
|
|
_nodeService = ref.read(nodeServiceChangeNotifierProvider);
|
|
_tradesService = ref.read(tradesServiceProvider);
|
|
|
|
NotificationApi.prefs = ref.read(prefsChangeNotifierProvider);
|
|
NotificationApi.notificationsService = _notificationsService;
|
|
|
|
unawaited(ref.read(baseCurrenciesProvider).update());
|
|
|
|
await _nodeService.updateDefaults();
|
|
await _notificationsService.init(
|
|
nodeService: _nodeService,
|
|
tradesService: _tradesService,
|
|
prefs: ref.read(prefsChangeNotifierProvider),
|
|
);
|
|
ref.read(priceAnd24hChangeNotifierProvider).start(true);
|
|
await ref.read(pWallets).load(
|
|
ref.read(prefsChangeNotifierProvider),
|
|
ref.read(mainDBProvider),
|
|
);
|
|
loadingCompleter.complete();
|
|
// TODO: this should probably run unawaited. Keep commented out for now as proper community nodes ui hasn't been implemented yet
|
|
// unawaited(_nodeService.updateCommunityNodes());
|
|
|
|
await ExchangeDataLoadingService.instance.initDB();
|
|
// run without awaiting
|
|
if (ref.read(prefsChangeNotifierProvider).externalCalls &&
|
|
await ref.read(prefsChangeNotifierProvider).isExternalCallsSet()) {
|
|
if (Constants.enableExchange) {
|
|
await ExchangeDataLoadingService.instance.setCurrenciesIfEmpty(
|
|
ref.read(efCurrencyPairProvider),
|
|
ref.read(efRateTypeProvider),
|
|
);
|
|
unawaited(ExchangeDataLoadingService.instance.loadAll());
|
|
}
|
|
// if (Constants.enableBuy) {
|
|
// unawaited(BuyDataLoadingService().loadAll(ref));
|
|
// }
|
|
}
|
|
|
|
if (ref.read(prefsChangeNotifierProvider).isAutoBackupEnabled) {
|
|
switch (ref.read(prefsChangeNotifierProvider).backupFrequencyType) {
|
|
case BackupFrequencyType.everyTenMinutes:
|
|
ref.read(autoSWBServiceProvider).startPeriodicBackupTimer(
|
|
duration: const Duration(minutes: 10));
|
|
break;
|
|
case BackupFrequencyType.everyAppStart:
|
|
unawaited(ref.read(autoSWBServiceProvider).doBackup());
|
|
break;
|
|
case BackupFrequencyType.afterClosingAWallet:
|
|
// ignore this case here
|
|
break;
|
|
}
|
|
}
|
|
|
|
// ref
|
|
// .read(prefsChangeNotifierProvider)
|
|
// .userID; // Just reading the ref should set it if it's not already set
|
|
// We shouldn't need to do this, instead only generating an ID when (or if) the userID is looked up when creating a quote
|
|
} catch (e, s) {
|
|
Logger.print("$e $s", normalLength: false);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void initState() {
|
|
String themeId;
|
|
if (ref.read(prefsChangeNotifierProvider).enableSystemBrightness) {
|
|
final brightness = WidgetsBinding.instance.window.platformBrightness;
|
|
switch (brightness) {
|
|
case Brightness.dark:
|
|
themeId =
|
|
ref.read(prefsChangeNotifierProvider).systemBrightnessDarkThemeId;
|
|
break;
|
|
case Brightness.light:
|
|
themeId = ref
|
|
.read(prefsChangeNotifierProvider)
|
|
.systemBrightnessLightThemeId;
|
|
break;
|
|
}
|
|
} else {
|
|
themeId = ref.read(prefsChangeNotifierProvider).themeId;
|
|
}
|
|
|
|
loadingCompleter = Completer();
|
|
WidgetsBinding.instance.addObserver(this);
|
|
// load locale and prefs
|
|
ref
|
|
.read(localeServiceChangeNotifierProvider.notifier)
|
|
.loadLocale(notify: false);
|
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
|
//Add themes path to provider
|
|
ref.read(applicationThemesDirectoryPathProvider.notifier).state =
|
|
StackFileSystem.themesDir!.path;
|
|
|
|
ref.read(themeProvider.state).state = ref.read(pThemeService).getTheme(
|
|
themeId: themeId,
|
|
)!;
|
|
|
|
if (Platform.isAndroid) {
|
|
// fetch open file if it exists
|
|
await getOpenFile();
|
|
|
|
if (ref.read(openedFromSWBFileStringStateProvider.state).state !=
|
|
null) {
|
|
// waiting for loading to complete before going straight to restore if the app was opened via file
|
|
await loadingCompleter.future;
|
|
|
|
await goToRestoreSWB(
|
|
ref.read(openedFromSWBFileStringStateProvider.state).state!);
|
|
ref.read(openedFromSWBFileStringStateProvider.state).state = null;
|
|
}
|
|
// ref.read(shouldShowLockscreenOnResumeStateProvider.state).state = false;
|
|
}
|
|
});
|
|
|
|
WidgetsBinding.instance.window.onPlatformBrightnessChanged = () {
|
|
String themeId;
|
|
switch (WidgetsBinding.instance.window.platformBrightness) {
|
|
case Brightness.dark:
|
|
themeId =
|
|
ref.read(prefsChangeNotifierProvider).systemBrightnessDarkThemeId;
|
|
break;
|
|
case Brightness.light:
|
|
themeId = ref
|
|
.read(prefsChangeNotifierProvider)
|
|
.systemBrightnessLightThemeId;
|
|
break;
|
|
}
|
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
if (ref.read(prefsChangeNotifierProvider).enableSystemBrightness) {
|
|
ref.read(themeProvider.state).state =
|
|
ref.read(pThemeService).getTheme(
|
|
themeId: themeId,
|
|
)!;
|
|
}
|
|
});
|
|
};
|
|
|
|
super.initState();
|
|
}
|
|
|
|
@override
|
|
dispose() {
|
|
WidgetsBinding.instance.removeObserver(this);
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
void didChangeLocales(List<Locale>? locales) {
|
|
ref.read(localeServiceChangeNotifierProvider).loadLocale();
|
|
super.didChangeLocales(locales);
|
|
}
|
|
|
|
@override
|
|
void didChangeAppLifecycleState(AppLifecycleState state) async {
|
|
debugPrint("didChangeAppLifecycleState: ${state.name}");
|
|
if (state == AppLifecycleState.resumed) {}
|
|
switch (state) {
|
|
case AppLifecycleState.inactive:
|
|
break;
|
|
case AppLifecycleState.paused:
|
|
break;
|
|
case AppLifecycleState.resumed:
|
|
if (Platform.isAndroid) {
|
|
// fetch open file if it exists
|
|
await getOpenFile();
|
|
// go straight to restore if the app was resumed via file
|
|
if (ref.read(openedFromSWBFileStringStateProvider.state).state !=
|
|
null) {
|
|
await goToRestoreSWB(
|
|
ref.read(openedFromSWBFileStringStateProvider.state).state!);
|
|
ref.read(openedFromSWBFileStringStateProvider.state).state = null;
|
|
}
|
|
}
|
|
// if (ref.read(hasAuthenticatedOnStartStateProvider.state).state &&
|
|
// ref.read(shouldShowLockscreenOnResumeStateProvider.state).state) {
|
|
// final now = DateTime.now().toUtc().millisecondsSinceEpoch ~/ 1000;
|
|
//
|
|
// if (now - _prefs.lastUnlocked > _prefs.lastUnlockedTimeout) {
|
|
// ref.read(shouldShowLockscreenOnResumeStateProvider.state).state =
|
|
// false;
|
|
// Navigator.of(navigatorKey.currentContext!).push(
|
|
// MaterialPageRoute<dynamic>(
|
|
// builder: (_) => LockscreenView(
|
|
// routeOnSuccess: "",
|
|
// popOnSuccess: true,
|
|
// biometricsAuthenticationTitle: "Unlock Stack",
|
|
// biometricsLocalizedReason:
|
|
// "Unlock your stack wallet using biometrics",
|
|
// biometricsCancelButtonString: "Cancel",
|
|
// onSuccess: () {
|
|
// ref
|
|
// .read(shouldShowLockscreenOnResumeStateProvider.state)
|
|
// .state = true;
|
|
// },
|
|
// ),
|
|
// ),
|
|
// );
|
|
// }
|
|
// }
|
|
break;
|
|
case AppLifecycleState.detached:
|
|
break;
|
|
case AppLifecycleState.hidden:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// should only be called on android currently
|
|
Future<void> getOpenFile() async {
|
|
// update provider with new file content state
|
|
ref.read(openedFromSWBFileStringStateProvider.state).state =
|
|
await platform.invokeMethod("getOpenFile");
|
|
|
|
// call reset to clear cached value
|
|
await resetOpenPath();
|
|
|
|
Logging.instance.log(
|
|
"This is the .swb content from intent: ${ref.read(openedFromSWBFileStringStateProvider.state).state}",
|
|
level: LogLevel.Info);
|
|
}
|
|
|
|
/// should only be called on android currently
|
|
Future<void> resetOpenPath() async {
|
|
await platform.invokeMethod("resetOpenPath");
|
|
}
|
|
|
|
Future<void> goToRestoreSWB(String encrypted) async {
|
|
if (!ref.read(prefsChangeNotifierProvider).hasPin) {
|
|
await Navigator.of(navigatorKey.currentContext!)
|
|
.pushNamed(CreatePinView.routeName, arguments: true)
|
|
.then((value) {
|
|
if (value is! bool || value == false) {
|
|
Navigator.of(navigatorKey.currentContext!).pushNamed(
|
|
RestoreFromEncryptedStringView.routeName,
|
|
arguments: encrypted);
|
|
}
|
|
});
|
|
} else {
|
|
unawaited(Navigator.push(
|
|
navigatorKey.currentContext!,
|
|
RouteGenerator.getRoute(
|
|
shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute,
|
|
builder: (_) => LockscreenView(
|
|
showBackButton: true,
|
|
routeOnSuccess: RestoreFromEncryptedStringView.routeName,
|
|
routeOnSuccessArguments: encrypted,
|
|
biometricsCancelButtonString: "CANCEL",
|
|
biometricsLocalizedReason:
|
|
"Authenticate to restore ${AppConfig.appName} backup",
|
|
biometricsAuthenticationTitle: "Restore ${AppConfig.prefix} backup",
|
|
),
|
|
settings: const RouteSettings(name: "/swbrestorelockscreen"),
|
|
),
|
|
));
|
|
}
|
|
}
|
|
|
|
InputBorder _buildOutlineInputBorder(Color color) {
|
|
return OutlineInputBorder(
|
|
borderSide: BorderSide(
|
|
width: 1,
|
|
color: color,
|
|
),
|
|
borderRadius: BorderRadius.circular(Constants.size.circularBorderRadius),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
debugPrint("BUILD: $runtimeType");
|
|
// ref.listen(shouldShowLockscreenOnResumeStateProvider, (previous, next) {
|
|
// Logging.instance.log("shouldShowLockscreenOnResumeStateProvider set to: $next",
|
|
// addToDebugMessagesDB: false);
|
|
// });
|
|
|
|
final colorScheme = ref.watch(colorProvider.state).state;
|
|
|
|
return MaterialApp(
|
|
key: GlobalKey(),
|
|
navigatorKey: navigatorKey,
|
|
title: AppConfig.appName,
|
|
onGenerateRoute: RouteGenerator.generateRoute,
|
|
theme: ThemeData(
|
|
extensions: [colorScheme],
|
|
highlightColor: colorScheme.highlight,
|
|
brightness: colorScheme.brightness,
|
|
fontFamily: GoogleFonts.inter().fontFamily,
|
|
unselectedWidgetColor: colorScheme.radioButtonBorderDisabled,
|
|
// textTheme: GoogleFonts.interTextTheme().copyWith(
|
|
// button: STextStyles.button(context),
|
|
// subtitle1: STextStyles.field(context).copyWith(
|
|
// color: colorScheme.textDark,
|
|
// ),
|
|
// ),
|
|
radioTheme: const RadioThemeData(
|
|
splashRadius: 0,
|
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
|
),
|
|
// splashFactory: NoSplash.splashFactory,
|
|
splashColor: Colors.transparent,
|
|
buttonTheme: ButtonThemeData(
|
|
splashColor: colorScheme.splash,
|
|
),
|
|
textButtonTheme: TextButtonThemeData(
|
|
style: ButtonStyle(
|
|
// splashFactory: NoSplash.splashFactory,
|
|
overlayColor: MaterialStateProperty.all(colorScheme.splash),
|
|
minimumSize: MaterialStateProperty.all<Size>(const Size(46, 46)),
|
|
// textStyle: MaterialStateProperty.all<TextStyle>(
|
|
// STextStyles.button(context)),
|
|
foregroundColor:
|
|
MaterialStateProperty.all(colorScheme.buttonTextSecondary),
|
|
backgroundColor: MaterialStateProperty.all<Color>(
|
|
colorScheme.buttonBackSecondary),
|
|
shape: MaterialStateProperty.all<OutlinedBorder>(
|
|
RoundedRectangleBorder(
|
|
// 1000 to be relatively sure it keeps its pill shape
|
|
borderRadius: BorderRadius.circular(1000),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
primaryColor: colorScheme.accentColorDark,
|
|
primarySwatch: Util.createMaterialColor(colorScheme.accentColorDark),
|
|
checkboxTheme: CheckboxThemeData(
|
|
splashRadius: 0,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius:
|
|
BorderRadius.circular(Constants.size.checkboxBorderRadius),
|
|
),
|
|
checkColor: MaterialStateColor.resolveWith(
|
|
(state) {
|
|
if (state.contains(MaterialState.selected)) {
|
|
return colorScheme.checkboxIconChecked;
|
|
}
|
|
return colorScheme.checkboxBGChecked;
|
|
},
|
|
),
|
|
fillColor: MaterialStateColor.resolveWith(
|
|
(states) {
|
|
if (states.contains(MaterialState.selected)) {
|
|
return colorScheme.checkboxBGChecked;
|
|
}
|
|
return colorScheme.checkboxBorderEmpty;
|
|
},
|
|
),
|
|
),
|
|
appBarTheme: AppBarTheme(
|
|
centerTitle: false,
|
|
color: colorScheme.background,
|
|
surfaceTintColor: colorScheme.background,
|
|
elevation: 0,
|
|
),
|
|
inputDecorationTheme: InputDecorationTheme(
|
|
focusColor: colorScheme.textFieldDefaultBG,
|
|
fillColor: colorScheme.textFieldDefaultBG,
|
|
filled: true,
|
|
contentPadding: const EdgeInsets.symmetric(
|
|
vertical: 6,
|
|
horizontal: 12,
|
|
),
|
|
// labelStyle: STextStyles.fieldLabel(context),
|
|
// hintStyle: STextStyles.fieldLabel(context),
|
|
enabledBorder:
|
|
_buildOutlineInputBorder(colorScheme.textFieldDefaultBG),
|
|
focusedBorder:
|
|
_buildOutlineInputBorder(colorScheme.textFieldDefaultBG),
|
|
errorBorder: _buildOutlineInputBorder(colorScheme.textFieldDefaultBG),
|
|
disabledBorder:
|
|
_buildOutlineInputBorder(colorScheme.textFieldDefaultBG),
|
|
focusedErrorBorder:
|
|
_buildOutlineInputBorder(colorScheme.textFieldDefaultBG),
|
|
),
|
|
),
|
|
home: CryptoNotifications(
|
|
child: Util.isDesktop
|
|
? FutureBuilder(
|
|
future: loadShared(),
|
|
builder: (context, snapshot) {
|
|
if (snapshot.connectionState == ConnectionState.done) {
|
|
if (_desktopHasPassword) {
|
|
String? startupWalletId;
|
|
if (ref
|
|
.read(prefsChangeNotifierProvider)
|
|
.gotoWalletOnStartup) {
|
|
startupWalletId = ref
|
|
.read(prefsChangeNotifierProvider)
|
|
.startupWalletId;
|
|
}
|
|
|
|
return DesktopLoginView(
|
|
startupWalletId: startupWalletId,
|
|
load: load,
|
|
);
|
|
} else {
|
|
return const IntroView();
|
|
}
|
|
} else {
|
|
return const LoadingView();
|
|
}
|
|
},
|
|
)
|
|
: FutureBuilder(
|
|
future: load(),
|
|
builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
|
|
if (snapshot.connectionState == ConnectionState.done) {
|
|
// FlutterNativeSplash.remove();
|
|
if (ref.read(pAllWalletsInfo).isNotEmpty ||
|
|
ref.read(prefsChangeNotifierProvider).hasPin) {
|
|
// return HomeView();
|
|
|
|
String? startupWalletId;
|
|
if (ref
|
|
.read(prefsChangeNotifierProvider)
|
|
.gotoWalletOnStartup) {
|
|
startupWalletId = ref
|
|
.read(prefsChangeNotifierProvider)
|
|
.startupWalletId;
|
|
}
|
|
|
|
return LockscreenView(
|
|
isInitialAppLogin: true,
|
|
routeOnSuccess: HomeView.routeName,
|
|
routeOnSuccessArguments: startupWalletId,
|
|
biometricsAuthenticationTitle:
|
|
"Unlock ${AppConfig.prefix}",
|
|
biometricsLocalizedReason:
|
|
"Unlock your ${AppConfig.appName} using biometrics",
|
|
biometricsCancelButtonString: "Cancel",
|
|
);
|
|
} else {
|
|
return const IntroView();
|
|
}
|
|
} else {
|
|
// CURRENTLY DISABLED as cannot be animated
|
|
// technically not needed as FlutterNativeSplash will overlay
|
|
// anything returned here until the future completes but
|
|
// FutureBuilder requires you to return something
|
|
return const LoadingView();
|
|
}
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|