Merge pull request #246 from cypherstack/desktop

Desktop
This commit is contained in:
Diego Salazar 2022-11-28 17:00:49 -07:00 committed by GitHub
commit cb4ba48d5d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 1154 additions and 160 deletions

View file

@ -76,11 +76,17 @@ void main() async {
Util.libraryPath = await getLibraryDirectory();
}
Screen? screen;
if (Platform.isLinux || Util.isDesktop) {
screen = await getCurrentScreen();
Util.screenWidth = screen?.frame.width;
}
if (Util.isDesktop) {
setWindowTitle('Stack Wallet');
setWindowMinSize(const Size(1220, 100));
setWindowMaxSize(Size.infinite);
final screen = await getCurrentScreen();
final screenHeight = screen?.frame.height;
if (screenHeight != null) {
// starting to height be 3/4 screen height or 900, whichever is smaller
@ -312,17 +318,17 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
final colorScheme = DB.instance
.get<dynamic>(boxName: DB.boxNameTheme, key: "colorScheme") as String?;
ThemeType themeType;
StackColorTheme colorTheme;
switch (colorScheme) {
case "dark":
themeType = ThemeType.dark;
colorTheme = DarkColors();
break;
case "oceanBreeze":
themeType = ThemeType.oceanBreeze;
colorTheme = OceanBreezeColors();
break;
case "light":
default:
themeType = ThemeType.light;
colorTheme = LightColors();
}
loadingCompleter = Completer();
WidgetsBinding.instance.addObserver(this);
@ -333,11 +339,7 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
WidgetsBinding.instance.addPostFrameCallback((_) async {
ref.read(colorThemeProvider.state).state =
StackColors.fromStackColorTheme(themeType == ThemeType.dark
? DarkColors()
: (themeType == ThemeType.light
? LightColors()
: OceanBreezeColors()));
StackColors.fromStackColorTheme(colorTheme);
if (Platform.isAndroid) {
// fetch open file if it exists

View file

@ -23,6 +23,8 @@ import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
import 'package:stackwallet/widgets/rounded_date_picker/flutter_rounded_date_picker_widget.dart'
as datePicker;
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:tuple/tuple.dart';
@ -152,10 +154,10 @@ class _RestoreOptionsViewState extends ConsumerState<RestoreOptionsView> {
await Future<void>.delayed(const Duration(milliseconds: 125));
}
final date = await showRoundedDatePicker(
final date = await datePicker.showRoundedDatePicker(
context: context,
initialDate: DateTime.now(),
height: height / 3.2,
height: height * 0.5,
theme: ThemeData(
primarySwatch: Util.createMaterialColor(fetchedColor),
),

View file

@ -25,6 +25,9 @@ import 'package:stackwallet/widgets/icon_widgets/addressbook_icon.dart';
import 'package:stackwallet/widgets/loading_indicator.dart';
import 'package:stackwallet/widgets/rounded_container.dart';
import '../../../../../pages_desktop_specific/home/desktop_home_view.dart';
import '../../../../../pages_desktop_specific/home/desktop_menu.dart';
import '../../../../../providers/desktop/current_desktop_menu_item.dart';
import '../../../../../widgets/desktop/primary_button.dart';
class StackRestoreProgressView extends ConsumerStatefulWidget {
@ -685,7 +688,19 @@ class _StackRestoreProgressViewState
enabled: true,
label: "Done",
onPressed: () async {
Navigator.of(context).pop();
DesktopMenuItemId keyID =
DesktopMenuItemId.myStack;
ref
.read(currentDesktopMenuItemProvider
.state)
.state = keyID;
Navigator.of(context, rootNavigator: true)
.popUntil(
ModalRoute.withName(
DesktopHomeView.routeName),
);
},
)
: SecondaryButton(

View file

@ -49,6 +49,8 @@ class _FavoriteCardState extends ConsumerState<FavoriteCard> {
super.initState();
}
bool _hovering = false;
@override
Widget build(BuildContext context) {
final coin = ref.watch(managerProvider.select((value) => value.coin));
@ -59,7 +61,48 @@ class _FavoriteCardState extends ConsumerState<FavoriteCard> {
condition: Util.isDesktop,
builder: (child) => MouseRegion(
cursor: SystemMouseCursors.click,
child: child,
onEnter: (_) {
setState(() {
_hovering = true;
});
},
onExit: (_) {
setState(() {
_hovering = false;
});
},
child: AnimatedScale(
duration: const Duration(milliseconds: 200),
scale: _hovering ? 1.05 : 1,
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
decoration: _hovering
? BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
boxShadow: [
Theme.of(context)
.extension<StackColors>()!
.standardBoxShadow,
Theme.of(context)
.extension<StackColors>()!
.standardBoxShadow,
Theme.of(context)
.extension<StackColors>()!
.standardBoxShadow,
],
)
: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
child: child,
),
),
),
child: GestureDetector(
onTap: () {

View file

@ -9,12 +9,19 @@ import 'package:stackwallet/pages_desktop_specific/home/notifications/desktop_no
import 'package:stackwallet/pages_desktop_specific/home/support_and_about_view/desktop_about_view.dart';
import 'package:stackwallet/pages_desktop_specific/home/support_and_about_view/desktop_support_view.dart';
import 'package:stackwallet/providers/desktop/current_desktop_menu_item.dart';
import 'package:stackwallet/providers/global/auto_swb_service_provider.dart';
import 'package:stackwallet/providers/global/notifications_provider.dart';
import 'package:stackwallet/providers/global/prefs_provider.dart';
import 'package:stackwallet/providers/global/wallets_provider.dart';
import 'package:stackwallet/providers/ui/transaction_filter_provider.dart';
import 'package:stackwallet/providers/ui/unread_notifications_provider.dart';
import 'package:stackwallet/route_generator.dart';
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/widgets/background.dart';
final currentWalletIdProvider = StateProvider<String?>((_) => null);
class DesktopHomeView extends ConsumerStatefulWidget {
const DesktopHomeView({Key? key}) : super(key: key);
@ -25,12 +32,25 @@ class DesktopHomeView extends ConsumerStatefulWidget {
}
class _DesktopHomeViewState extends ConsumerState<DesktopHomeView> {
final Map<DesktopMenuItemId, Widget> contentViews = {
DesktopMenuItemId.myStack: const Navigator(
key: Key("desktopStackHomeKey"),
final GlobalKey key = GlobalKey<NavigatorState>();
late final Navigator myStackViewNav;
@override
void initState() {
myStackViewNav = Navigator(
key: key,
onGenerateRoute: RouteGenerator.generateRoute,
initialRoute: MyStackView.routeName,
),
);
super.initState();
}
final Map<DesktopMenuItemId, Widget> contentViews = {
DesktopMenuItemId.myStack: Container(
// key: Key("desktopStackHomeKey"),
// onGenerateRoute: RouteGenerator.generateRoute,
// initialRoute: MyStackView.routeName,
),
DesktopMenuItemId.exchange: const Navigator(
key: Key("desktopExchangeHomeKey"),
onGenerateRoute: RouteGenerator.generateRoute,
@ -63,7 +83,30 @@ class _DesktopHomeViewState extends ConsumerState<DesktopHomeView> {
),
};
DesktopMenuItemId prev = DesktopMenuItemId.myStack;
void onMenuSelectionWillChange(DesktopMenuItemId newKey) {
if (prev == DesktopMenuItemId.myStack && prev == newKey) {
Navigator.of(key.currentContext!)
.popUntil(ModalRoute.withName(MyStackView.routeName));
if (ref.read(currentWalletIdProvider.state).state != null) {
final managerProvider = ref
.read(walletsChangeNotifierProvider)
.getManagerProvider(ref.read(currentWalletIdProvider.state).state!);
if (ref.read(managerProvider).shouldAutoSync) {
ref.read(managerProvider).shouldAutoSync = false;
}
ref.read(transactionFilterProvider.state).state = null;
if (ref.read(prefsChangeNotifierProvider).isAutoBackupEnabled &&
ref.read(prefsChangeNotifierProvider).backupFrequencyType ==
BackupFrequencyType.afterClosingAWallet) {
ref.read(autoSWBServiceProvider).doBackup();
}
ref.read(managerProvider.notifier).isActiveWallet = false;
}
}
prev = newKey;
// check for unread notifications and refresh provider before
// showing notifications view
if (newKey == DesktopMenuItemId.notifications) {
@ -111,9 +154,25 @@ class _DesktopHomeViewState extends ConsumerState<DesktopHomeView> {
color: Theme.of(context).extension<StackColors>()!.background,
),
Expanded(
child: contentViews[
ref.watch(currentDesktopMenuItemProvider.state).state]!,
child: IndexedStack(
index: ref
.watch(currentDesktopMenuItemProvider.state)
.state
.index >
0
? 1
: 0,
children: [
myStackViewNav,
contentViews[
ref.watch(currentDesktopMenuItemProvider.state).state]!,
],
),
),
// Expanded(
// child: contentViews[
// ref.watch(currentDesktopMenuItemProvider.state).state]!,
// ),
],
),
),

View file

@ -9,6 +9,7 @@ import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/widgets/desktop/living_stack_icon.dart';
enum DesktopMenuItemId {
myStack,
@ -38,6 +39,9 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> {
static const expandedWidth = 225.0;
static const minimizedWidth = 72.0;
final Duration duration = const Duration(milliseconds: 250);
late final List<DMIController> controllers;
double _width = expandedWidth;
void updateSelectedMenuItem(DesktopMenuItemId idKey) {
@ -49,45 +53,84 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> {
}
void toggleMinimize() {
final expanded = _width == expandedWidth;
for (var e in controllers) {
e.toggle?.call();
}
setState(() {
_width = _width == expandedWidth ? minimizedWidth : expandedWidth;
_width = expanded ? minimizedWidth : expandedWidth;
});
}
@override
void initState() {
controllers = [
DMIController(),
DMIController(),
DMIController(),
DMIController(),
DMIController(),
DMIController(),
DMIController(),
DMIController(),
];
super.initState();
}
@override
void dispose() {
for (var e in controllers) {
e.dispose();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
return Material(
color: Theme.of(context).extension<StackColors>()!.popupBG,
child: SizedBox(
child: AnimatedContainer(
width: _width,
duration: duration,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
height: _width == expandedWidth ? 22 : 25,
const SizedBox(
height: 25,
),
SizedBox(
AnimatedContainer(
duration: duration,
width: _width == expandedWidth ? 70 : 32,
height: _width == expandedWidth ? 70 : 32,
child: SvgPicture.asset(
Assets.svg.stackIcon(context),
child: LivingStackIcon(
onPressed: toggleMinimize,
),
),
const SizedBox(
height: 10,
),
Text(
_width == expandedWidth ? "Stack Wallet" : "",
style: STextStyles.desktopH2(context).copyWith(
fontSize: 18,
height: 23.4 / 18,
AnimatedOpacity(
duration: duration,
opacity: _width == expandedWidth ? 1 : 0,
child: SizedBox(
height: 28,
child: Text(
"Stack Wallet",
style: STextStyles.desktopH2(context).copyWith(
fontSize: 18,
height: 23.4 / 18,
),
),
),
),
const SizedBox(
height: 60,
),
Expanded(
child: SizedBox(
child: AnimatedContainer(
duration: duration,
width: _width == expandedWidth
? _width - 32 // 16 padding on either side
: _width - 16, // 8 padding on either side
@ -95,6 +138,7 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> {
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
DesktopMenuItem(
duration: duration,
icon: SvgPicture.asset(
Assets.svg.walletDesktop,
width: 20,
@ -113,15 +157,14 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> {
),
label: "My Stack",
value: DesktopMenuItemId.myStack,
group:
ref.watch(currentDesktopMenuItemProvider.state).state,
onChanged: updateSelectedMenuItem,
iconOnly: _width == minimizedWidth,
controller: controllers[0],
),
const SizedBox(
height: 2,
),
DesktopMenuItem(
duration: duration,
icon: SvgPicture.asset(
Assets.svg.exchangeDesktop,
width: 20,
@ -140,15 +183,14 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> {
),
label: "Exchange",
value: DesktopMenuItemId.exchange,
group:
ref.watch(currentDesktopMenuItemProvider.state).state,
onChanged: updateSelectedMenuItem,
iconOnly: _width == minimizedWidth,
controller: controllers[1],
),
const SizedBox(
height: 2,
),
DesktopMenuItem(
duration: duration,
icon: SvgPicture.asset(
ref.watch(notificationsProvider.select(
(value) => value.hasUnreadNotifications))
@ -174,15 +216,14 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> {
),
label: "Notifications",
value: DesktopMenuItemId.notifications,
group:
ref.watch(currentDesktopMenuItemProvider.state).state,
onChanged: updateSelectedMenuItem,
iconOnly: _width == minimizedWidth,
controller: controllers[2],
),
const SizedBox(
height: 2,
),
DesktopMenuItem(
duration: duration,
icon: SvgPicture.asset(
Assets.svg.addressBookDesktop,
width: 20,
@ -201,15 +242,14 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> {
),
label: "Address Book",
value: DesktopMenuItemId.addressBook,
group:
ref.watch(currentDesktopMenuItemProvider.state).state,
onChanged: updateSelectedMenuItem,
iconOnly: _width == minimizedWidth,
controller: controllers[3],
),
const SizedBox(
height: 2,
),
DesktopMenuItem(
duration: duration,
icon: SvgPicture.asset(
Assets.svg.gear,
width: 20,
@ -228,15 +268,14 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> {
),
label: "Settings",
value: DesktopMenuItemId.settings,
group:
ref.watch(currentDesktopMenuItemProvider.state).state,
onChanged: updateSelectedMenuItem,
iconOnly: _width == minimizedWidth,
controller: controllers[4],
),
const SizedBox(
height: 2,
),
DesktopMenuItem(
duration: duration,
icon: SvgPicture.asset(
Assets.svg.messageQuestion,
width: 20,
@ -255,15 +294,14 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> {
),
label: "Support",
value: DesktopMenuItemId.support,
group:
ref.watch(currentDesktopMenuItemProvider.state).state,
onChanged: updateSelectedMenuItem,
iconOnly: _width == minimizedWidth,
controller: controllers[5],
),
const SizedBox(
height: 2,
),
DesktopMenuItem(
duration: duration,
icon: SvgPicture.asset(
Assets.svg.aboutDesktop,
width: 20,
@ -282,13 +320,13 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> {
),
label: "About",
value: DesktopMenuItemId.about,
group:
ref.watch(currentDesktopMenuItemProvider.state).state,
onChanged: updateSelectedMenuItem,
iconOnly: _width == minimizedWidth,
controller: controllers[6],
),
const Spacer(),
DesktopMenuItem(
duration: duration,
labelLength: 123,
icon: SvgPicture.asset(
Assets.svg.exitDesktop,
width: 20,
@ -300,13 +338,11 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> {
),
label: "Exit",
value: 7,
group:
ref.watch(currentDesktopMenuItemProvider.state).state,
onChanged: (_) {
// todo: save stuff/ notify before exit?
exit(0);
},
iconOnly: _width == minimizedWidth,
controller: controllers[7],
),
],
),

View file

@ -1,27 +1,96 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/providers/desktop/current_desktop_menu_item.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
class DesktopMenuItem<T> extends StatelessWidget {
class DMIController {
VoidCallback? toggle;
void dispose() {
toggle = null;
}
}
class DesktopMenuItem<T> extends ConsumerStatefulWidget {
const DesktopMenuItem({
Key? key,
required this.icon,
required this.label,
required this.value,
required this.group,
required this.onChanged,
required this.iconOnly,
required this.duration,
this.labelLength = 125,
this.controller,
}) : super(key: key);
final Widget icon;
final String label;
final T value;
final T group;
final void Function(T) onChanged;
final bool iconOnly;
final Duration duration;
final double labelLength;
final DMIController? controller;
@override
ConsumerState<DesktopMenuItem<T>> createState() => _DesktopMenuItemState<T>();
}
class _DesktopMenuItemState<T> extends ConsumerState<DesktopMenuItem<T>>
with SingleTickerProviderStateMixin {
late final Widget icon;
late final String label;
late final T value;
late final void Function(T) onChanged;
late final Duration duration;
late final double labelLength;
late final DMIController? controller;
late final AnimationController animationController;
bool _iconOnly = false;
void toggle() {
setState(() {
_iconOnly = !_iconOnly;
});
if (_iconOnly) {
animationController.reverse();
} else {
animationController.forward();
}
}
@override
void initState() {
icon = widget.icon;
label = widget.label;
value = widget.value;
onChanged = widget.onChanged;
duration = widget.duration;
labelLength = widget.labelLength;
controller = widget.controller;
controller?.toggle = toggle;
animationController = AnimationController(
vsync: this,
duration: duration,
)..forward();
super.initState();
}
@override
void dispose() {
controller?.dispose();
animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final group = ref.watch(currentDesktopMenuItemProvider.state).state;
debugPrint("============ value:$value ============ group:$group");
return TextButton(
style: value == group
? Theme.of(context)
@ -34,26 +103,42 @@ class DesktopMenuItem<T> extends StatelessWidget {
onChanged(value);
},
child: Padding(
padding: EdgeInsets.symmetric(
padding: const EdgeInsets.symmetric(
vertical: 16,
horizontal: iconOnly ? 0 : 16,
),
child: Row(
mainAxisAlignment:
iconOnly ? MainAxisAlignment.center : MainAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedContainer(
duration: duration,
width: _iconOnly ? 0 : 16,
),
icon,
if (!iconOnly)
const SizedBox(
width: 12,
),
if (!iconOnly)
Text(
label,
style: value == group
? STextStyles.desktopMenuItemSelected(context)
: STextStyles.desktopMenuItem(context),
AnimatedOpacity(
duration: duration,
opacity: _iconOnly ? 0 : 1.0,
child: SizeTransition(
sizeFactor: animationController,
axis: Axis.horizontal,
axisAlignment: -1,
child: SizedBox(
width: labelLength,
child: Row(
children: [
const SizedBox(
width: 12,
),
Text(
label,
style: value == group
? STextStyles.desktopMenuItemSelected(context)
: STextStyles.desktopMenuItem(context),
),
],
),
),
),
)
],
),
),

View file

@ -1,8 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart';
import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/desktop_wallet_view.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/widgets/rounded_container.dart';
import 'package:stackwallet/widgets/wallet_info_row/wallet_info_row.dart';
class CoinWalletsTable extends ConsumerWidget {
@ -24,8 +26,10 @@ class CoinWalletsTable extends ConsumerWidget {
),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 16,
// horizontal: 20,
// vertical: 16,
horizontal: 6,
vertical: 6,
),
child: Column(
children: [
@ -36,14 +40,29 @@ class CoinWalletsTable extends ConsumerWidget {
const SizedBox(
height: 32,
),
WalletInfoRow(
walletId: walletIds[i],
onPressed: () async {
await Navigator.of(context).pushNamed(
DesktopWalletView.routeName,
arguments: walletIds[i],
);
},
Stack(
children: [
WalletInfoRow(
padding: const EdgeInsets.symmetric(
horizontal: 14,
vertical: 10,
),
walletId: walletIds[i],
),
Positioned.fill(
child: WalletRowHoverOverlay(
onPressed: () async {
ref.read(currentWalletIdProvider.state).state =
walletIds[i];
await Navigator.of(context).pushNamed(
DesktopWalletView.routeName,
arguments: walletIds[i],
);
},
),
),
],
),
],
),
@ -53,3 +72,55 @@ class CoinWalletsTable extends ConsumerWidget {
);
}
}
class WalletRowHoverOverlay extends StatefulWidget {
const WalletRowHoverOverlay({
Key? key,
required this.onPressed,
}) : super(key: key);
final VoidCallback onPressed;
@override
State<WalletRowHoverOverlay> createState() => _WalletRowHoverOverlayState();
}
class _WalletRowHoverOverlayState extends State<WalletRowHoverOverlay> {
late final VoidCallback onPressed;
bool _hovering = false;
@override
void initState() {
onPressed = widget.onPressed;
super.initState();
}
@override
Widget build(BuildContext context) {
return MouseRegion(
cursor: SystemMouseCursors.click,
onEnter: (_) {
setState(() {
_hovering = true;
});
},
onExit: (_) {
setState(() {
_hovering = false;
});
},
child: GestureDetector(
onTap: onPressed,
child: AnimatedOpacity(
duration: const Duration(milliseconds: 100),
opacity: _hovering ? 0.1 : 0,
child: RoundedContainer(
color:
Theme.of(context).extension<StackColors>()!.buttonBackSecondary,
),
),
),
);
}
}

View file

@ -81,13 +81,13 @@ class _DesktopWalletViewState extends ConsumerState<DesktopWalletView> {
// disable auto sync if it was enabled only when loading wallet
ref.read(managerProvider).shouldAutoSync = false;
}
ref.read(managerProvider.notifier).isActiveWallet = false;
ref.read(transactionFilterProvider.state).state = null;
if (ref.read(prefsChangeNotifierProvider).isAutoBackupEnabled &&
ref.read(prefsChangeNotifierProvider).backupFrequencyType ==
BackupFrequencyType.afterClosingAWallet) {
unawaited(ref.read(autoSWBServiceProvider).doBackup());
}
ref.read(managerProvider.notifier).isActiveWallet = false;
}
void _loadCNData() {

View file

@ -95,7 +95,8 @@ class _DeleteWalletKeysPopup extends ConsumerState<DeleteWalletKeysPopup> {
horizontal: 32,
),
child: Text(
"Please write down your recovery phrase in the correct order and save it to keep your funds secure. You will also be asked to verify the words on the next screen.",
"Please write down your recovery phrase in the correct order and "
"save it to keep your funds secure. You will be shown your recovery phrase on the next screen.",
style: STextStyles.desktopTextExtraExtraSmall(context),
textAlign: TextAlign.center,
),

View file

@ -1,14 +1,24 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
abstract class Util {
static Directory? libraryPath;
static double? screenWidth;
static bool get isDesktop {
if(Platform.isIOS && libraryPath != null && !libraryPath!.path.contains("/var/mobile/")){
// special check for running on linux based phones
if (Platform.isLinux && screenWidth != null && screenWidth! < 800) {
return false;
}
// special check for running under ipad mode in macos
if (Platform.isIOS &&
libraryPath != null &&
!libraryPath!.path.contains("/var/mobile/")) {
return true;
}
return Platform.isLinux || Platform.isMacOS || Platform.isWindows;
}

View file

@ -0,0 +1,54 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:stackwallet/utilities/assets.dart';
class LivingStackIcon extends StatefulWidget {
const LivingStackIcon({Key? key, this.onPressed,}) : super(key: key);
final VoidCallback? onPressed;
@override
State<LivingStackIcon> createState() => _LivingStackIconState();
}
class _LivingStackIconState extends State<LivingStackIcon> {
bool _hovering = false;
late final VoidCallback? onPressed;
@override
void initState() {
onPressed = widget.onPressed;
super.initState();
}
@override
Widget build(BuildContext context) {
return SizedBox(
height: 76,
child: MouseRegion(
cursor: SystemMouseCursors.click,
onEnter: (_) {
setState(() {
_hovering = true;
});
},
onExit: (_) {
setState(() {
_hovering = false;
});
},
child: GestureDetector(
onTap: () => onPressed?.call(),
child: AnimatedScale(
duration: const Duration(milliseconds: 200),
scale: _hovering ? 1.2 : 1,
child: SvgPicture.asset(
Assets.svg.stackIcon(context),
),
),
),
),
);
}
}

View file

@ -0,0 +1,5 @@
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with the License. You may obtain a copy of the License in the LICENSE file, or at:
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

View file

@ -0,0 +1,332 @@
import 'package:flutter/material.dart';
import 'package:flutter/semantics.dart';
import 'package:flutter/services.dart';
import 'package:flutter_rounded_date_picker/flutter_rounded_date_picker.dart';
import 'package:flutter_rounded_date_picker/src/flutter_rounded_button_action.dart';
import 'package:flutter_rounded_date_picker/src/material_rounded_date_picker_style.dart';
import 'package:flutter_rounded_date_picker/src/material_rounded_year_picker_style.dart';
import 'package:flutter_rounded_date_picker/src/widgets/flutter_rounded_date_picker_header.dart';
import 'package:flutter_rounded_date_picker/src/widgets/flutter_rounded_day_picker.dart';
import 'package:flutter_rounded_date_picker/src/widgets/flutter_rounded_month_picker.dart';
import 'package:flutter_rounded_date_picker/src/widgets/flutter_rounded_year_picker.dart';
import 'package:stackwallet/utilities/util.dart';
///
/// This file uses code taken from https://github.com/benznest/flutter_rounded_date_picker
///
class FlutterRoundedDatePickerDialog extends StatefulWidget {
const FlutterRoundedDatePickerDialog(
{Key? key,
this.height,
required this.initialDate,
required this.firstDate,
required this.lastDate,
this.selectableDayPredicate,
required this.initialDatePickerMode,
required this.era,
this.locale,
required this.borderRadius,
this.imageHeader,
this.description = "",
this.fontFamily,
this.textNegativeButton,
this.textPositiveButton,
this.textActionButton,
this.onTapActionButton,
this.styleDatePicker,
this.styleYearPicker,
this.customWeekDays,
this.builderDay,
this.listDateDisabled,
this.onTapDay,
this.onMonthChange})
: super(key: key);
final DateTime initialDate;
final DateTime firstDate;
final DateTime lastDate;
final SelectableDayPredicate? selectableDayPredicate;
final DatePickerMode initialDatePickerMode;
/// double height.
final double? height;
/// Custom era year.
final EraMode era;
final Locale? locale;
/// Border
final double borderRadius;
/// Header;
final ImageProvider? imageHeader;
final String description;
/// Font
final String? fontFamily;
/// Button
final String? textNegativeButton;
final String? textPositiveButton;
final String? textActionButton;
final VoidCallback? onTapActionButton;
/// Style
final MaterialRoundedDatePickerStyle? styleDatePicker;
final MaterialRoundedYearPickerStyle? styleYearPicker;
/// Custom Weekday
final List<String>? customWeekDays;
final BuilderDayOfDatePicker? builderDay;
final List<DateTime>? listDateDisabled;
final OnTapDay? onTapDay;
final Function? onMonthChange;
@override
_FlutterRoundedDatePickerDialogState createState() =>
_FlutterRoundedDatePickerDialogState();
}
class _FlutterRoundedDatePickerDialogState
extends State<FlutterRoundedDatePickerDialog> {
@override
void initState() {
super.initState();
_selectedDate = widget.initialDate;
_mode = widget.initialDatePickerMode;
}
bool _announcedInitialDate = false;
late MaterialLocalizations localizations;
late TextDirection textDirection;
@override
void didChangeDependencies() {
super.didChangeDependencies();
localizations = MaterialLocalizations.of(context);
textDirection = Directionality.of(context);
if (!_announcedInitialDate) {
_announcedInitialDate = true;
SemanticsService.announce(
localizations.formatFullDate(_selectedDate),
textDirection,
);
}
}
late DateTime _selectedDate;
late DatePickerMode _mode;
final GlobalKey _pickerKey = GlobalKey();
void _vibrate() {
switch (Theme.of(context).platform) {
case TargetPlatform.android:
case TargetPlatform.fuchsia:
HapticFeedback.vibrate();
break;
case TargetPlatform.iOS:
default:
break;
}
}
void _handleModeChanged(DatePickerMode mode) {
_vibrate();
setState(() {
_mode = mode;
if (_mode == DatePickerMode.day) {
SemanticsService.announce(
localizations.formatMonthYear(_selectedDate),
textDirection,
);
} else {
SemanticsService.announce(
localizations.formatYear(_selectedDate),
textDirection,
);
}
});
}
Future<void> _handleYearChanged(DateTime value) async {
if (value.isBefore(widget.firstDate)) {
value = widget.firstDate;
} else if (value.isAfter(widget.lastDate)) {
value = widget.lastDate;
}
if (value == _selectedDate) return;
if (widget.onMonthChange != null) await widget.onMonthChange!(value);
_vibrate();
setState(() {
_mode = DatePickerMode.day;
_selectedDate = value;
});
}
void _handleDayChanged(DateTime value) {
_vibrate();
setState(() {
_selectedDate = value;
});
}
void _handleCancel() {
Navigator.of(context).pop();
}
void _handleOk() {
Navigator.of(context).pop(_selectedDate);
}
Widget _buildPicker() {
switch (_mode) {
case DatePickerMode.year:
return FlutterRoundedYearPicker(
key: _pickerKey,
selectedDate: _selectedDate,
onChanged: (DateTime date) async => await _handleYearChanged(date),
firstDate: widget.firstDate,
lastDate: widget.lastDate,
era: widget.era,
fontFamily: widget.fontFamily,
style: widget.styleYearPicker,
);
case DatePickerMode.day:
default:
return FlutterRoundedMonthPicker(
key: _pickerKey,
selectedDate: _selectedDate,
onChanged: _handleDayChanged,
firstDate: widget.firstDate,
lastDate: widget.lastDate,
era: widget.era,
locale: widget.locale,
selectableDayPredicate: widget.selectableDayPredicate,
fontFamily: widget.fontFamily,
style: widget.styleDatePicker,
borderRadius: widget.borderRadius,
customWeekDays: widget.customWeekDays,
builderDay: widget.builderDay,
listDateDisabled: widget.listDateDisabled,
onTapDay: widget.onTapDay,
onMonthChange: widget.onMonthChange);
}
}
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final Widget picker = _buildPicker();
final isDesktop = Util.isDesktop;
final Widget actions = FlutterRoundedButtonAction(
textButtonNegative: widget.textNegativeButton,
textButtonPositive: widget.textPositiveButton,
onTapButtonNegative: _handleCancel,
onTapButtonPositive: _handleOk,
textActionButton: widget.textActionButton,
onTapButtonAction: widget.onTapActionButton,
localizations: localizations,
textStyleButtonNegative: widget.styleDatePicker?.textStyleButtonNegative,
textStyleButtonPositive: widget.styleDatePicker?.textStyleButtonPositive,
textStyleButtonAction: widget.styleDatePicker?.textStyleButtonAction,
borderRadius: widget.borderRadius,
paddingActionBar: widget.styleDatePicker?.paddingActionBar,
background: widget.styleDatePicker?.backgroundActionBar,
);
Color backgroundPicker = theme.dialogBackgroundColor;
if (_mode == DatePickerMode.day) {
backgroundPicker = widget.styleDatePicker?.backgroundPicker ??
theme.dialogBackgroundColor;
} else {
backgroundPicker = widget.styleYearPicker?.backgroundPicker ??
theme.dialogBackgroundColor;
}
final Dialog dialog = Dialog(
child: OrientationBuilder(
builder: (BuildContext context, Orientation orientation) {
final Widget header = FlutterRoundedDatePickerHeader(
selectedDate: _selectedDate,
mode: _mode,
onModeChanged: _handleModeChanged,
orientation: orientation,
era: widget.era,
borderRadius: widget.borderRadius,
imageHeader: widget.imageHeader,
description: widget.description,
fontFamily: widget.fontFamily,
style: widget.styleDatePicker);
switch (orientation) {
case Orientation.landscape:
return Container(
height: isDesktop ? 600 : null,
width: isDesktop ? 700 : null,
decoration: BoxDecoration(
color: backgroundPicker,
borderRadius: BorderRadius.circular(widget.borderRadius),
),
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Flexible(flex: 1, child: header),
Flexible(
flex: 2, // have the picker take up 2/3 of the dialog width
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
SizedBox(
height: isDesktop ? 530 : null,
width: isDesktop ? 700 : null,
child: picker),
actions,
],
),
),
],
),
);
case Orientation.portrait:
default:
return Container(
decoration: BoxDecoration(
color: backgroundPicker,
borderRadius: BorderRadius.circular(widget.borderRadius),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
header,
if (widget.height == null)
Flexible(child: picker)
else
SizedBox(
height: widget.height,
child: picker,
),
actions,
],
),
);
}
}),
);
return Theme(
data: theme.copyWith(dialogBackgroundColor: Colors.transparent),
child: dialog,
);
}
}

View file

@ -0,0 +1,216 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
// import 'package:flutter_rounded_date_picker/src/dialogs/flutter_rounded_date_picker_dialog.dart';
import 'package:flutter_rounded_date_picker/src/era_mode.dart';
import 'package:flutter_rounded_date_picker/src/material_rounded_date_picker_style.dart';
import 'package:flutter_rounded_date_picker/src/material_rounded_year_picker_style.dart';
import 'package:flutter_rounded_date_picker/src/widgets/flutter_rounded_day_picker.dart';
import 'package:stackwallet/widgets/rounded_date_picker/flutter_rounded_date_picker_dialog.dart';
///
/// This file uses code taken from https://github.com/benznest/flutter_rounded_date_picker
///
// Examples can assume:
// BuildContext context;
/// Initial display mode of the date picker dialog.
///
/// Date picker UI mode for either showing a list of available years or a
/// monthly calendar initially in the dialog shown by calling [showDatePicker].
///
// Shows the selected date in large font and toggles between year and day mode
/// Signature for predicating dates for enabled date selections.
///
/// See [showDatePicker].
typedef SelectableDayPredicate = bool Function(DateTime day);
/// Shows a dialog containing a material design date picker.
///
/// The returned [Future] resolves to the date selected by the user when the
/// user closes the dialog. If the user cancels the dialog, null is returned.
///
/// An optional [selectableDayPredicate] function can be passed in to customize
/// the days to enable for selection. If provided, only the days that
/// [selectableDayPredicate] returned true for will be selectable.
///
/// An optional [initialDatePickerMode] argument can be used to display the
/// date picker initially in the year or month+day picker mode. It defaults
/// to month+day, and must not be null.
///
/// An optional [locale] argument can be used to set the locale for the date
/// picker. It defaults to the ambient locale provided by [Localizations].
///
/// An optional [textDirection] argument can be used to set the text direction
/// (RTL or LTR) for the date picker. It defaults to the ambient text direction
/// provided by [Directionality]. If both [locale] and [textDirection] are not
/// null, [textDirection] overrides the direction chosen for the [locale].
///
/// The [context] argument is passed to [showDialog], the documentation for
/// which discusses how it is used.
///
/// The [builder] parameter can be used to wrap the dialog widget
/// to add inherited widgets like [Theme].
///
/// {@tool sample}
/// Show a date picker with the dark theme.
///
/// ```dart
/// Future<DateTime> selectedDate = showDatePicker(
/// context: context,
/// initialDate: DateTime.now(),
/// firstDate: DateTime(2018),
/// lastDate: DateTime(2030),
/// builder: (BuildContext context, Widget child) {
/// return Theme(
/// data: ThemeData.dark(),
/// child: child,
/// );
/// },
/// );
/// ```
/// {@end-tool}
///
/// The [context], [initialDate], [firstDate], and [lastDate] parameters must
/// not be null.
///
/// See also:
///
/// * [showTimePicker], which shows a dialog that contains a material design
/// time picker.
/// * [DayPicker], which displays the days of a given month and allows
/// choosing a day.
/// * [MonthPicker], which displays a scrollable list of months to allow
/// picking a month.
/// * [YearPicker], which displays a scrollable list of years to allow picking
/// a year.
///
Future<DateTime?> showRoundedDatePicker(
{required BuildContext context,
double? height,
DateTime? initialDate,
DateTime? firstDate,
DateTime? lastDate,
SelectableDayPredicate? selectableDayPredicate,
DatePickerMode initialDatePickerMode = DatePickerMode.day,
Locale? locale,
TextDirection? textDirection,
ThemeData? theme,
double borderRadius = 16,
EraMode era = EraMode.CHRIST_YEAR,
ImageProvider? imageHeader,
String description = "",
String? fontFamily,
bool barrierDismissible = false,
Color background = Colors.transparent,
String? textNegativeButton,
String? textPositiveButton,
String? textActionButton,
VoidCallback? onTapActionButton,
MaterialRoundedDatePickerStyle? styleDatePicker,
MaterialRoundedYearPickerStyle? styleYearPicker,
List<String>? customWeekDays,
BuilderDayOfDatePicker? builderDay,
List<DateTime>? listDateDisabled,
OnTapDay? onTapDay,
Function? onMonthChange}) async {
initialDate ??= DateTime.now();
firstDate ??= DateTime(initialDate.year - 1);
lastDate ??= DateTime(initialDate.year + 1);
theme ??= ThemeData();
assert(
!initialDate.isBefore(firstDate),
'initialDate must be on or after firstDate',
);
assert(
!initialDate.isAfter(lastDate),
'initialDate must be on or before lastDate',
);
assert(
!firstDate.isAfter(lastDate),
'lastDate must be on or after firstDate',
);
assert(
selectableDayPredicate == null || selectableDayPredicate(initialDate),
'Provided initialDate must satisfy provided selectableDayPredicate',
);
assert(
(onTapActionButton != null && textActionButton != null) ||
onTapActionButton == null,
"If you provide onLeftBtn, you must provide leftBtn",
);
assert(debugCheckHasMaterialLocalizations(context));
Widget child = GestureDetector(
onTap: () {
if (!barrierDismissible) {
Navigator.pop(context);
}
},
child: Container(
color: background,
child: GestureDetector(
onTap: () {
//
},
child: FlutterRoundedDatePickerDialog(
height: height,
initialDate: initialDate,
firstDate: firstDate,
lastDate: lastDate,
selectableDayPredicate: selectableDayPredicate,
initialDatePickerMode: initialDatePickerMode,
era: era,
locale: locale,
borderRadius: borderRadius,
imageHeader: imageHeader,
description: description,
fontFamily: fontFamily,
textNegativeButton: textNegativeButton,
textPositiveButton: textPositiveButton,
textActionButton: textActionButton,
onTapActionButton: onTapActionButton,
styleDatePicker: styleDatePicker,
styleYearPicker: styleYearPicker,
customWeekDays: customWeekDays,
builderDay: builderDay,
listDateDisabled: listDateDisabled,
onTapDay: onTapDay,
onMonthChange: onMonthChange,
),
),
),
);
if (textDirection != null) {
child = Directionality(
textDirection: textDirection,
child: child,
);
}
if (locale != null) {
child = Localizations.override(
context: context,
locale: locale,
child: child,
);
}
return await showDialog<DateTime>(
context: context,
barrierDismissible: barrierDismissible,
builder: (_) => Theme(data: theme!, child: child),
);
}

View file

@ -3,7 +3,7 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/widgets/expandable.dart';
import 'package:stackwallet/widgets/table_view/table_view_cell.dart';
class TableViewRow extends StatelessWidget {
class TableViewRow extends StatefulWidget {
const TableViewRow({
Key? key,
required this.cells,
@ -17,40 +17,66 @@ class TableViewRow extends StatelessWidget {
final List<TableViewCell> cells;
final Widget? expandingChild;
final Decoration? decoration;
final BoxDecoration? decoration;
final void Function(ExpandableState)? onExpandChanged;
final EdgeInsetsGeometry padding;
final double spacing;
final CrossAxisAlignment crossAxisAlignment;
@override
State<TableViewRow> createState() => _TableViewRowState();
}
class _TableViewRowState extends State<TableViewRow> {
late final List<TableViewCell> cells;
late final Widget? expandingChild;
late final BoxDecoration? decoration;
late final void Function(ExpandableState)? onExpandChanged;
late final EdgeInsetsGeometry padding;
late final double spacing;
late final CrossAxisAlignment crossAxisAlignment;
bool _hovering = false;
@override
void initState() {
cells = widget.cells;
expandingChild = widget.expandingChild;
decoration = widget.decoration;
onExpandChanged = widget.onExpandChanged;
padding = widget.padding;
spacing = widget.spacing;
crossAxisAlignment = widget.crossAxisAlignment;
super.initState();
}
@override
Widget build(BuildContext context) {
return Container(
decoration: decoration,
decoration: !_hovering
? decoration
: decoration?.copyWith(
boxShadow: [
Theme.of(context).extension<StackColors>()!.standardBoxShadow,
Theme.of(context).extension<StackColors>()!.standardBoxShadow,
],
),
child: expandingChild == null
? Padding(
padding: padding,
child: Row(
crossAxisAlignment: crossAxisAlignment,
children: [
for (int i = 0; i < cells.length; i++) ...[
if (i != 0 && i != cells.length)
SizedBox(
width: spacing,
),
Expanded(
flex: cells[i].flex,
child: cells[i],
),
],
],
),
)
: Expandable(
onExpandChanged: onExpandChanged,
header: Padding(
? MouseRegion(
onEnter: (_) {
setState(() {
_hovering = true;
});
},
onExit: (_) {
setState(() {
_hovering = false;
});
},
child: Padding(
padding: padding,
child: Row(
crossAxisAlignment: crossAxisAlignment,
children: [
for (int i = 0; i < cells.length; i++) ...[
if (i != 0 && i != cells.length)
@ -65,6 +91,38 @@ class TableViewRow extends StatelessWidget {
],
),
),
)
: Expandable(
onExpandChanged: onExpandChanged,
header: MouseRegion(
onEnter: (_) {
setState(() {
_hovering = true;
});
},
onExit: (_) {
setState(() {
_hovering = false;
});
},
child: Padding(
padding: padding,
child: Row(
children: [
for (int i = 0; i < cells.length; i++) ...[
if (i != 0 && i != cells.length)
SizedBox(
width: spacing,
),
Expanded(
flex: cells[i].flex,
child: cells[i],
),
],
],
),
),
),
body: Column(
children: [
Container(

View file

@ -14,10 +14,12 @@ class WalletInfoRow extends ConsumerWidget {
Key? key,
required this.walletId,
this.onPressed,
this.padding = const EdgeInsets.all(0),
}) : super(key: key);
final String walletId;
final VoidCallback? onPressed;
final EdgeInsets padding;
@override
Widget build(BuildContext context, WidgetRef ref) {
@ -30,53 +32,56 @@ class WalletInfoRow extends ConsumerWidget {
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: onPressed,
child: Container(
color: Colors.transparent,
child: Row(
children: [
Expanded(
flex: 4,
child: Row(
children: [
WalletInfoCoinIcon(coin: manager.coin),
const SizedBox(
width: 12,
),
Text(
manager.walletName,
style:
STextStyles.desktopTextExtraSmall(context).copyWith(
child: Padding(
padding: padding,
child: Container(
color: Colors.transparent,
child: Row(
children: [
Expanded(
flex: 4,
child: Row(
children: [
WalletInfoCoinIcon(coin: manager.coin),
const SizedBox(
width: 12,
),
Text(
manager.walletName,
style: STextStyles.desktopTextExtraSmall(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark,
),
),
],
),
),
Expanded(
flex: 4,
child: WalletInfoRowBalanceFuture(
walletId: walletId,
),
),
Expanded(
flex: 6,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
SvgPicture.asset(
Assets.svg.chevronRight,
width: 20,
height: 20,
color: Theme.of(context)
.extension<StackColors>()!
.textDark,
),
),
],
),
),
Expanded(
flex: 4,
child: WalletInfoRowBalanceFuture(
walletId: walletId,
),
),
Expanded(
flex: 6,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
SvgPicture.asset(
Assets.svg.chevronRight,
width: 20,
height: 20,
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
)
],
),
)
],
.textSubtitle1,
)
],
),
)
],
),
),
),
),