CW-342 Enable Mac Layout on iPad (#958)

* Add iPad responsive layout

* Set magic number to position input field on wider screens

* Adjust screen resolution

* Update target device family for ios

* Add icons for iPad

* Revert width adjustment for ipad

* Fix overflowing balance

* Fix overflowing balance

* Fix PR issues

* Remove unused icons [skip ci]

---------

Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
This commit is contained in:
Godwin Asuquo
2023-06-14 02:04:52 +03:00
committed by GitHub
co-authored by OmarHatem
parent e84d02f661
commit affc35f335
34 changed files with 144 additions and 110 deletions
+2 -1
View File
@@ -36,6 +36,7 @@ import 'package:cake_wallet/themes/theme_list.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/store/anonpay/anonpay_transactions_store.dart';
import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/view_model/dashboard/desktop_sidebar_view_model.dart';
import 'package:cake_wallet/view_model/anon_invoice_page_view_model.dart';
import 'package:cake_wallet/view_model/anonpay_details_view_model.dart';
@@ -244,7 +245,7 @@ Future setup({
nodeSource: _nodeSource,
isBitcoinBuyEnabled: isBitcoinBuyEnabled,
// Enforce darkTheme on platforms other than mobile till the design for other themes is completed
initialTheme: DeviceInfo.instance.isMobile ? null : ThemeList.darkTheme,
initialTheme: ResponsiveLayoutUtil.instance.isMobile && DeviceInfo.instance.isMobile ? null : ThemeList.darkTheme,
);
if (_isSetupFinished) {
+1
View File
@@ -6,6 +6,7 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cw_core/wallet_type.dart';
+4 -1
View File
@@ -6,6 +6,7 @@ import 'package:cake_wallet/buy/order.dart';
import 'package:cake_wallet/locales/locale.dart';
import 'package:cake_wallet/store/yat/yat_store.dart';
import 'package:cake_wallet/utils/exception_handler.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -194,7 +195,9 @@ class App extends StatefulWidget {
class AppState extends State<App> with SingleTickerProviderStateMixin {
AppState() : yatStore = getIt.get<YatStore>() {
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
ResponsiveLayoutUtil.instance.isIpad ?
[DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight] :
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
}
YatStore yatStore;
@@ -43,7 +43,7 @@ class DashboardPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: ResponsiveLayoutUtil.instance.isMobile(context)
body: ResponsiveLayoutUtil.instance.isMobile
? _DashboardPageView(
balancePage: balancePage,
dashboardViewModel: dashboardViewModel,
@@ -73,7 +73,7 @@ class AddressPage extends BasePage {
? closeButtonImageDarkTheme
: closeButtonImage;
bool isMobileView = ResponsiveLayoutUtil.instance.isMobile(context);
bool isMobileView = ResponsiveLayoutUtil.instance.isMobile;
return MergeSemantics(
child: SizedBox(
@@ -120,19 +120,22 @@ class BalancePage extends StatelessWidget {
.backgroundColor!,
height: 1)),
SizedBox(height: 5),
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
AutoSizeText(availableBalance,
style: TextStyle(
fontSize: 24,
fontFamily: 'Lato',
fontWeight: FontWeight.w900,
color: Theme.of(context)
.accentTextTheme!
.displayMedium!
.backgroundColor!,
height: 1),
maxLines: 1,
textAlign: TextAlign.center),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Expanded(
child: AutoSizeText(availableBalance,
style: TextStyle(
fontSize: 24,
fontFamily: 'Lato',
fontWeight: FontWeight.w900,
color: Theme.of(context)
.accentTextTheme!
.displayMedium!
.backgroundColor!,
height: 1),
maxLines: 1,
textAlign: TextAlign.start),
),
Text(currency,
style: TextStyle(
fontSize: 28,
@@ -31,7 +31,7 @@ class TransactionsPage extends StatelessWidget {
onLongPressUp: () => dashboardViewModel.balanceViewModel.isReversing =
!dashboardViewModel.balanceViewModel.isReversing,
child: Container(
color: ResponsiveLayoutUtil.instance.isMobile(context)
color: ResponsiveLayoutUtil.instance.isMobile
? null
: Theme.of(context).colorScheme.background,
padding: EdgeInsets.only(top: 24, bottom: 24),
+2 -2
View File
@@ -117,7 +117,7 @@ class ExchangePage extends BasePage {
final _closeButton = currentTheme.type == ThemeType.dark
? closeButtonImageDarkTheme : closeButtonImage;
bool isMobileView = ResponsiveLayoutUtil.instance.isMobile(context);
bool isMobileView = ResponsiveLayoutUtil.instance.isMobile;
return MergeSemantics(
child: SizedBox(
@@ -731,7 +731,7 @@ class ExchangePage extends BasePage {
},
));
if (ResponsiveLayoutUtil.instance.isMobile(context)) {
if (ResponsiveLayoutUtil.instance.isMobile) {
return MobileExchangeCardsSection(
firstExchangeCard: firstExchangeCard,
secondExchangeCard: secondExchangeCard,
@@ -9,6 +9,7 @@ import 'package:cake_wallet/src/screens/receive/widgets/anonpay_input_form.dart'
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/view_model/anon_invoice_page_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
import 'package:flutter/material.dart';
@@ -88,32 +89,30 @@ class AnonPayInvoicePage extends BasePage {
.accentTextTheme!
.bodyLarge!
.backgroundColor!,
nextFocus: false,
actions: [
KeyboardActionsItem(
focusNode: _amountFocusNode,
toolbarButtons: [(_) => KeyboardDoneButton()],
nextFocus: false,
actions: [
KeyboardActionsItem(
focusNode: _amountFocusNode,
toolbarButtons: [(_) => KeyboardDoneButton()],
),
]),
child: Container(
color: Theme.of(context).colorScheme.background,
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 24),
content: Container(
decoration: ResponsiveLayoutUtil.instance.isMobile ? BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
gradient: LinearGradient(
colors: [
Theme.of(context).primaryTextTheme!.titleSmall!.color!,
Theme.of(context).primaryTextTheme!.titleSmall!.decorationColor!,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
]),
child: Container(
color: Theme.of(context).colorScheme.background,
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 24),
content: Container(
decoration: DeviceInfo.instance.isMobile
? BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
gradient: LinearGradient(
colors: [
Theme.of(context).primaryTextTheme!.titleSmall!.color!,
Theme.of(context).primaryTextTheme!.titleSmall!.decorationColor!,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
)
: null,
) : null,
child: Observer(builder: (_) {
return Padding(
padding: EdgeInsets.fromLTRB(24, 120, 24, 0),
@@ -1,4 +1,5 @@
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cw_core/currency.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -9,7 +10,8 @@ class CurrencyInputField extends StatelessWidget {
required this.onTapPicker,
required this.selectedCurrency,
this.focusNode,
required this.controller, required this.isLight,
required this.controller,
required this.isLight,
});
final Function() onTapPicker;
@@ -22,13 +24,12 @@ class CurrencyInputField extends StatelessWidget {
Widget build(BuildContext context) {
final arrowBottomPurple = Image.asset(
'assets/images/arrow_bottom_purple_icon.png',
color: Theme.of(context)
.accentTextTheme!
.displayMedium!
.backgroundColor!,
color: Theme.of(context).accentTextTheme!.displayMedium!.backgroundColor!,
height: 8,
);
final _width = MediaQuery.of(context).size.width;
// This magic number for wider screen sets the text input focus at center of the inputfield
final _width =
ResponsiveLayoutUtil.instance.isMobile ? MediaQuery.of(context).size.width : 500;
return Column(
children: [
@@ -42,10 +43,12 @@ class CurrencyInputField extends StatelessWidget {
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'^\d+(\.|\,)?\d{0,8}'))],
hintText: '0.000',
placeholderTextStyle: isLight ? null : TextStyle(
color: Theme.of(context).primaryTextTheme!.headlineSmall!.color!,
fontWeight: FontWeight.w600,
),
placeholderTextStyle: isLight
? null
: TextStyle(
color: Theme.of(context).primaryTextTheme!.headlineSmall!.color!,
fontWeight: FontWeight.w600,
),
borderColor: Theme.of(context).accentTextTheme!.titleLarge!.backgroundColor!,
textColor: Theme.of(context).accentTextTheme!.displayMedium!.backgroundColor!,
textStyle: TextStyle(
@@ -72,7 +75,10 @@ class CurrencyInputField extends StatelessWidget {
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
color: Theme.of(context).accentTextTheme!.displayMedium!.backgroundColor!,
color: Theme.of(context)
.accentTextTheme!
.displayMedium!
.backgroundColor!,
),
),
if (selectedCurrency.tag != null)
@@ -107,8 +113,10 @@ class CurrencyInputField extends StatelessWidget {
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 20,
color:
Theme.of(context).accentTextTheme!.displayMedium!.backgroundColor!,
color: Theme.of(context)
.accentTextTheme!
.displayMedium!
.backgroundColor!,
),
),
),
+2 -2
View File
@@ -62,7 +62,7 @@ class SendPage extends BasePage {
final _closeButton = currentTheme.type == ThemeType.dark
? closeButtonImageDarkTheme : closeButtonImage;
bool isMobileView = ResponsiveLayoutUtil.instance.isMobile(context);
bool isMobileView = ResponsiveLayoutUtil.instance.isMobile;
return MergeSemantics(
child: SizedBox(
@@ -92,7 +92,7 @@ class SendPage extends BasePage {
double _sendCardHeight(BuildContext context) {
final double initialHeight = sendViewModel.isElectrumWallet ? 490 : 465;
if (!ResponsiveLayoutUtil.instance.isMobile(context)) {
if (!ResponsiveLayoutUtil.instance.isMobile) {
return initialHeight - 66;
}
return initialHeight;
+3 -3
View File
@@ -122,7 +122,7 @@ class SendCardState extends State<SendCard>
color: Colors.transparent,
)),
Container(
decoration: ResponsiveLayoutUtil.instance.isMobile(context) ? BoxDecoration(
decoration: ResponsiveLayoutUtil.instance.isMobile ? BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24)),
@@ -137,9 +137,9 @@ class SendCardState extends State<SendCard>
child: Padding(
padding: EdgeInsets.fromLTRB(
24,
ResponsiveLayoutUtil.instance.isMobile(context) ? 100 : 55,
ResponsiveLayoutUtil.instance.isMobile ? 100 : 55,
24,
ResponsiveLayoutUtil.instance.isMobile(context) ? 32 : 0,
ResponsiveLayoutUtil.instance.isMobile ? 32 : 0,
),
child: SingleChildScrollView(
child: Observer(builder: (_) => Column(
@@ -8,6 +8,7 @@ import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/themes/theme_list.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/view_model/settings/choices_list_item.dart';
import 'package:cake_wallet/view_model/settings/display_settings_view_model.dart';
import 'package:flutter/material.dart';
@@ -29,11 +30,11 @@ class DisplaySettingsPage extends BasePage {
child: Column(
children: [
SettingsSwitcherCell(
title: S.current.settings_display_balance,
value: _displaySettingsViewModel.shouldDisplayBalance,
onValueChange: (_, bool value) {
_displaySettingsViewModel.setShouldDisplayBalance(value);
}),
title: S.current.settings_display_balance,
value: _displaySettingsViewModel.shouldDisplayBalance,
onValueChange: (_, bool value) {
_displaySettingsViewModel.setShouldDisplayBalance(value);
}),
SettingsSwitcherCell(
title: S.current.show_market_place,
value: _displaySettingsViewModel.shouldShowMarketPlaceInDashboard,
@@ -42,14 +43,17 @@ class DisplaySettingsPage extends BasePage {
},
),
//if (!isHaven) it does not work correctly
if(!_displaySettingsViewModel.disabledFiatApiMode)
if (!_displaySettingsViewModel.disabledFiatApiMode)
SettingsPickerCell<FiatCurrency>(
title: S.current.settings_currency,
searchHintText: S.current.search_currency,
items: FiatCurrency.all,
selectedItem: _displaySettingsViewModel.fiatCurrency,
onItemSelected: (FiatCurrency currency) => _displaySettingsViewModel.setFiatCurrency(currency),
images: FiatCurrency.all.map((e) => Image.asset("assets/images/flags/${e.countryCode}.png")).toList(),
onItemSelected: (FiatCurrency currency) =>
_displaySettingsViewModel.setFiatCurrency(currency),
images: FiatCurrency.all
.map((e) => Image.asset("assets/images/flags/${e.countryCode}.png"))
.toList(),
isGridView: true,
matchingCriteria: (FiatCurrency currency, String searchText) {
return currency.title.toLowerCase().contains(searchText) ||
@@ -66,13 +70,14 @@ class DisplaySettingsPage extends BasePage {
selectedItem: _displaySettingsViewModel.languageCode,
onItemSelected: _displaySettingsViewModel.onLanguageSelected,
images: LanguageService.list.keys
.map((e) => Image.asset("assets/images/flags/${LanguageService.localeCountryCode[e]}.png"))
.map((e) => Image.asset(
"assets/images/flags/${LanguageService.localeCountryCode[e]}.png"))
.toList(),
matchingCriteria: (String code, String searchText) {
return LanguageService.list[code]?.toLowerCase().contains(searchText) ?? false;
},
),
if (DeviceInfo.instance.isMobile)
if (ResponsiveLayoutUtil.instance.isMobile && DeviceInfo.instance.isMobile)
SettingsChoicesCell(
ChoicesListItem<ThemeBase>(
title: S.current.color_theme,
@@ -1,6 +1,6 @@
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
@@ -229,7 +229,7 @@ class WalletListBodyState extends State<WalletListBody> {
await hideProgressText();
// only pop the wallets route in mobile as it will go back to dashboard page
// in desktop platforms the navigation tree is different
if (DeviceInfo.instance.isMobile) {
if (ResponsiveLayoutUtil.instance.isMobile) {
WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.of(context).pop();
});
+1 -1
View File
@@ -26,7 +26,7 @@ class AddTemplateButton extends StatelessWidget {
child: Container(
height: 34,
padding: EdgeInsets.symmetric(
horizontal: ResponsiveLayoutUtil.instance.isMobile(context) ? 10 : 30),
horizontal: ResponsiveLayoutUtil.instance.isMobile ? 10 : 30),
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20)),
+2 -2
View File
@@ -1,6 +1,6 @@
import 'dart:io';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:flutter/services.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/routes.dart';
@@ -105,7 +105,7 @@ class AddressTextField extends StatelessWidget {
width: prefixIconWidth * options.length +
(spaceBetweenPrefixIcons * options.length),
child: Row(
mainAxisAlignment: DeviceInfo.instance.isMobile
mainAxisAlignment: ResponsiveLayoutUtil.instance.isMobile
? MainAxisAlignment.spaceBetween : MainAxisAlignment.end,
children: [
SizedBox(width: 5),
+10 -11
View File
@@ -130,17 +130,16 @@ class ExceptionHandler {
_ignoredErrors.any((element) => error.contains(element));
static const List<String> _ignoredErrors = const [
"errno = 9", // SocketException: Bad file descriptor
"errno = 28", // OS Error: No space left on device
"errno = 32", // SocketException: Write failed (OS Error: Broken pipe)
"errno = 49", // SocketException: Can't assign requested address
"errno = 54", // SocketException: Connection reset by peer
"errno = 57", // SocketException: Read failed (OS Error: Socket is not connected)
"errno = 60", // SocketException: Operation timed out
"errno = 65", // SocketException: No route to host
"errno = 103", // SocketException: Software caused connection abort
"errno = 104", // SocketException: Connection reset by peer
"errno = 110", // SocketException: Connection timed out
"Bad file descriptor",
"No space left on device",
"Write failed (OS Error: Broken pipe)",
"Can't assign requested address",
"Read failed (OS Error: Socket is not connected)",
"Operation timed out",
"No route to host",
"Software caused connection abort",
"Connection reset by peer",
"Connection timed out",
"Connection reset by peer",
"Connection closed before full header was received",
"Connection terminated during handshake",
+9 -7
View File
@@ -1,33 +1,35 @@
import 'package:flutter/material.dart';
class ResponsiveLayoutUtil {
static const double _kMobileThreshold = 900;
static const double _kMobileThreshold = 768;
static const double kDesktopMaxWidthConstraint = 400;
static const double kPopupWidth = 400;
static const double kPopupSpaceHeight = 100;
static const _kIpadMaxWidth = 2560.0;
const ResponsiveLayoutUtil._();
static final instance = ResponsiveLayoutUtil._();
bool isMobile(BuildContext context) {
final MediaQueryData mediaQueryData = MediaQuery.of(context);
return mediaQueryData.size.width < _kMobileThreshold;
bool get isMobile =>
WidgetsBinding.instance.platformDispatcher.views.first.physicalSize.width < _kMobileThreshold;
bool get isIpad {
final width = WidgetsBinding.instance.platformDispatcher.views.first.physicalSize.width;
return width >= _kMobileThreshold && !(width > _kIpadMaxWidth);
}
/// Returns dynamic size.
///
/// If screen size is mobile, it returns 66% ([scale]) of the [originalValue].
double getDynamicSize(
BuildContext context,
double originalValue, {
double? mobileSize,
double? scale,
}) {
scale ??= 2 / 3;
mobileSize ??= originalValue * scale;
final value = isMobile(context) ? mobileSize : originalValue;
final value = isMobile ? mobileSize : originalValue;
return value.roundToDouble();
}