diff --git a/lib/constants.dart b/lib/constants.dart index b63a17948..5c000907c 100644 --- a/lib/constants.dart +++ b/lib/constants.dart @@ -258,6 +258,12 @@ const kGatewayTypes = { const String kNotificationChannelEmail = 'email'; const String kNotificationChannelSlack = 'slack'; +const String kPlatformWindows = 'Windows'; +const String kPlatformLinux = 'Linux'; +const String kPlatformMacOS = 'macOS'; +const String kPlatformAndroid = 'Android'; +const String kPlatformiPhone = 'iPhone'; + const String kNotificationsAll = 'all_notifications'; const String kNotificationsAllUser = 'all_user_notifications'; const String kNotificationsPaymentSuccess = 'payment_success'; diff --git a/lib/redux/app/app_state.dart b/lib/redux/app/app_state.dart index b90f0ae66..ebcb4acb6 100644 --- a/lib/redux/app/app_state.dart +++ b/lib/redux/app/app_state.dart @@ -50,30 +50,24 @@ import 'package:invoiceninja_flutter/ui/credit/edit/credit_edit_vm.dart'; import 'package:invoiceninja_flutter/ui/design/edit/design_edit_vm.dart'; import 'package:invoiceninja_flutter/ui/group/edit/group_edit_vm.dart'; import 'package:invoiceninja_flutter/ui/product/edit/product_edit_vm.dart'; - // STARTER: import - do not remove comment import 'package:invoiceninja_flutter/redux/recurring_expense/recurring_expense_state.dart'; import 'package:invoiceninja_flutter/ui/recurring_expense/edit/recurring_expense_edit_vm.dart'; import 'package:invoiceninja_flutter/redux/recurring_expense/recurring_expense_selectors.dart'; - import 'package:invoiceninja_flutter/redux/subscription/subscription_state.dart'; import 'package:invoiceninja_flutter/ui/recurring_expense/recurring_expense_screen.dart'; import 'package:invoiceninja_flutter/ui/subscription/edit/subscription_edit_vm.dart'; import 'package:invoiceninja_flutter/redux/subscription/subscription_selectors.dart'; - import 'package:invoiceninja_flutter/redux/task_status/task_status_state.dart'; import 'package:invoiceninja_flutter/ui/recurring_invoice/recurring_invoice_screen.dart'; import 'package:invoiceninja_flutter/ui/task_status/edit/task_status_edit_vm.dart'; import 'package:invoiceninja_flutter/redux/task_status/task_status_selectors.dart'; - import 'package:invoiceninja_flutter/redux/expense_category/expense_category_state.dart'; import 'package:invoiceninja_flutter/ui/expense_category/edit/expense_category_edit_vm.dart'; import 'package:invoiceninja_flutter/redux/expense_category/expense_category_selectors.dart'; - import 'package:invoiceninja_flutter/redux/recurring_invoice/recurring_invoice_state.dart'; import 'package:invoiceninja_flutter/ui/recurring_invoice/edit/recurring_invoice_edit_vm.dart'; import 'package:invoiceninja_flutter/redux/recurring_invoice/recurring_invoice_selectors.dart'; - import 'package:invoiceninja_flutter/redux/webhook/webhook_state.dart'; import 'package:invoiceninja_flutter/ui/webhook/edit/webhook_edit_vm.dart'; import 'package:invoiceninja_flutter/redux/webhook/webhook_selectors.dart'; diff --git a/lib/ui/auth/login_view.dart b/lib/ui/auth/login_view.dart index 9b67c5d31..304c130a9 100644 --- a/lib/ui/auth/login_view.dart +++ b/lib/ui/auth/login_view.dart @@ -269,6 +269,7 @@ class _LoginState extends State { @override Widget build(BuildContext context) { final localization = AppLocalization.of(context); + final platform = getNativePlatform(); final viewModel = widget.viewModel; final state = viewModel.state; @@ -676,9 +677,15 @@ class _LoginState extends State { ]), ), ), - if (!_recoverPassword && !_isSelfHosted) + if (!_recoverPassword && (!_isSelfHosted || kIsWeb)) InkWell( - onTap: () => launch(kStatusCheckUrl), + onTap: () { + if (platform.isNotEmpty) { + launch(getNativeAppUrl(platform)); + } else { + launch(kStatusCheckUrl); + } + }, child: Padding( padding: const EdgeInsets.only( left: 25, top: 12, right: 20, bottom: 12), @@ -686,9 +693,15 @@ class _LoginState extends State { mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(Icons.security, size: 16), + Icon( + platform.isEmpty + ? Icons.security + : getNativeAppIcon(platform), + size: 16), SizedBox(width: 8), - Text(localization.checkStatus) + Text(platform.isEmpty + ? localization.checkStatus + : '$platform ${localization.app}') ], ), ), diff --git a/lib/utils/i18n.dart b/lib/utils/i18n.dart index 257a1c4f4..7d07d1f98 100644 --- a/lib/utils/i18n.dart +++ b/lib/utils/i18n.dart @@ -15,6 +15,8 @@ mixin LocalizationsProvider on LocaleCodeAware { static final Map> _localizedValues = { 'en': { // STARTER: lang key - do not remove comment + 'app': 'App', + 'for_best_performance': 'For the best performance download the :app app', 'gross_line_total': 'Gross Line Total', 'bulk_email_invoice': 'Email Invoice', 'bulk_email_quote': 'Email Quote', @@ -62623,6 +62625,13 @@ mixin LocalizationsProvider on LocaleCodeAware { _localizedValues[localeCode]['gross_line_total'] ?? _localizedValues['en']['gross_line_total']; + String get forBestPerformance => + _localizedValues[localeCode]['for_best_performance'] ?? + _localizedValues['en']['for_best_performance']; + + String get app => + _localizedValues[localeCode]['app'] ?? _localizedValues['en']['app']; + // STARTER: lang field - do not remove comment String lookup(String key) { diff --git a/lib/utils/platforms.dart b/lib/utils/platforms.dart index 2c0b209ab..09a99e82b 100644 --- a/lib/utils/platforms.dart +++ b/lib/utils/platforms.dart @@ -7,6 +7,9 @@ import 'package:invoiceninja_flutter/constants.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/ui/pref_state.dart'; import 'package:invoiceninja_flutter/utils/localization.dart'; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; +import 'package:invoiceninja_flutter/utils/web_stub.dart' + if (dart.library.html) 'package:invoiceninja_flutter/utils/web.dart'; bool isDesktopOS() => isMacOS() || isWindows() || isLinux(); @@ -78,6 +81,69 @@ String getPdfRequirements(BuildContext context) { } } +String getNativePlatform() { + String userAgent = WebUtils.getHtmlValue('user-agent') ?? ''; + userAgent = userAgent.toLowerCase(); + + if (userAgent.contains('ipad') || + userAgent.contains('ipod') || + userAgent.contains('iphone')) { + return kPlatformiPhone; + } else if (userAgent.contains('android')) { + return kPlatformAndroid; + } else if (userAgent.contains('win')) { + return kPlatformWindows; + } else if (userAgent.contains('mac')) { + return kPlatformMacOS; + } else if (userAgent.contains('linux')) { + return kPlatformLinux; + } else { + return ''; + } +} + +String getNativeAppUrl(String platform) { + if (!kIsWeb) { + return ''; + } + + switch (platform) { + case kPlatformAndroid: + return kGoogleStoreUrl; + case kPlatformiPhone: + return kAppleStoreUrl; + case kPlatformWindows: + return kWindowsUrl; + case kPlatformMacOS: + return kMacOSUrl; + case kPlatformLinux: + return kLinuxUrl; + } + + return ''; +} + +IconData getNativeAppIcon(String platform) { + if (!kIsWeb) { + return null; + } + + switch (platform) { + case kPlatformAndroid: + return Icons.android; + case kPlatformiPhone: + return MdiIcons.apple; + case kPlatformWindows: + return MdiIcons.microsoft; + case kPlatformMacOS: + return MdiIcons.apple; + case kPlatformLinux: + return MdiIcons.linux; + } + + return null; +} + String getPlatform(BuildContext context) => Theme.of(context).platform == TargetPlatform.iOS ? 'ios' : 'android'; diff --git a/web/index.html b/web/index.html index f1db48e58..e642f1562 100644 --- a/web/index.html +++ b/web/index.html @@ -1,5 +1,5 @@ - +