diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e11f2fe0..3a449383 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: - name: "Test" run: flutter test - name: "Upload Failure Screenshots" - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 if: failure() with: name: golden-failures @@ -198,7 +198,7 @@ jobs: done exit 1 - name: "Store .dmg artifact" - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: notifi-dmg path: notifi.dmg @@ -312,7 +312,7 @@ jobs: fi - name: "Store .aab artifact" - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: notifi-aab path: notifi.aab @@ -365,7 +365,7 @@ jobs: fi - name: "Store .snap artifact" - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: notifi-snap path: notifi.snap @@ -381,17 +381,17 @@ jobs: needs: [ checks, macos, android, snap, ios, version ] steps: - uses: actions/checkout@v2 - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v4 name: "Download notifi-aab" id: download-aab with: name: notifi-aab - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v4 name: "Download notifi-snap" id: download-snap with: name: notifi-snap - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v4 name: "Download notifi-dmg" id: download-dmg with: diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index d9bba8bb..4584dd7f 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -20,7 +20,7 @@ jobs: - name: "Test" run: flutter test - name: "Upload Failure Screenshots" - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 if: failure() with: name: golden-failures diff --git a/backend b/backend new file mode 160000 index 00000000..63398201 --- /dev/null +++ b/backend @@ -0,0 +1 @@ +Subproject commit 63398201857ec0bf1c86fe45fafc9514602c826c diff --git a/lib/main.dart b/lib/main.dart index 3f38192e..eaac2cc1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -19,11 +19,10 @@ import 'package:provider/provider.dart'; import 'package:provider/single_child_widget.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; -import 'package:sqflite/sqflite.dart'; Future main() => mainImpl(); -Future mainImpl({bool integration: false}) async { +Future mainImpl({bool integration = false}) async { WidgetsFlutterBinding.ensureInitialized(); final SharedPreferences sp = await SharedPreferences.getInstance(); @@ -67,7 +66,7 @@ Future mainImpl({bool integration: false}) async { DBProvider('notifications.db', fillWithNotifications: integration); final List notifications = await db.getAll(); - FlutterLocalNotificationsPlugin pushNotifications = null; + FlutterLocalNotificationsPlugin? pushNotifications; if (!integration) { pushNotifications = await initPushNotifications(); } @@ -88,16 +87,27 @@ Future mainImpl({bool integration: false}) async { Provider.of(context, listen: false), canBadge: canBadge), update: (BuildContext context, TableNotifier tableNotifier, - Notifications user) => - user..setTableNotifier(tableNotifier), + Notifications? n) { + final Notifications notificationsInstance = n ?? + Notifications(notifications, db, + Provider.of(context, listen: false), + canBadge: canBadge); + notificationsInstance.setTableNotifier(tableNotifier); + return notificationsInstance; + }, ), ChangeNotifierProxyProvider( create: (BuildContext context) => User( Provider.of(context, listen: false), pushNotifications), update: - (BuildContext context, Notifications notifications, User user) => - user..setNotifications(notifications), + (BuildContext context, Notifications notifications, User? user) { + final User userInstance = user ?? + User(Provider.of(context, listen: false), + pushNotifications); + userInstance.setNotifications(notifications); + return userInstance; + }, ), ], child: const MyApp(), @@ -105,7 +115,7 @@ Future mainImpl({bool integration: false}) async { } class MyApp extends StatefulWidget { - const MyApp({Key key}) : super(key: key); + const MyApp({Key? key}) : super(key: key); @override _MyAppState createState() => _MyAppState(); @@ -135,7 +145,6 @@ class _MyAppState extends State { focusColor: MyColour.transparent, highlightColor: Colors.transparent, splashColor: Colors.transparent, - backgroundColor: MyColour.transparent, appBarTheme: const AppBarTheme( iconTheme: IconThemeData( color: MyColour.darkGrey, @@ -149,8 +158,7 @@ class _MyAppState extends State { size: 22, ), scrollbarTheme: ScrollbarThemeData( - thickness: MaterialStateProperty.all(4.0), - showTrackOnHover: false, + thickness: WidgetStateProperty.all(4.0), ), textTheme: getTextTheme(), buttonTheme: const ButtonThemeData( @@ -158,15 +166,10 @@ class _MyAppState extends State { splashColor: Colors.transparent), textButtonTheme: TextButtonThemeData( style: ButtonStyle( - overlayColor: MaterialStateProperty.all(Colors.transparent), + overlayColor: WidgetStateProperty.all(Colors.transparent), splashFactory: NoSplash.splashFactory, )), - indicatorColor: MyColour.offOffGrey, - colorScheme: ColorScheme.fromSwatch( - primarySwatch: Colors.grey, - accentColor: MyColour.red, - ), - dialogTheme: DialogTheme( + dialogTheme: DialogThemeData( elevation: 0, shape: Border.all(width: 0, color: MyColour.offOffGrey), contentTextStyle: const TextStyle( @@ -178,7 +181,12 @@ class _MyAppState extends State { fontFamily: 'Inconsolata', color: MyColour.black, fontWeight: FontWeight.w900, - fontSize: 30))), + fontSize: 30)), + colorScheme: ColorScheme.fromSwatch( + primarySwatch: Colors.grey, + accentColor: MyColour.red, + ).copyWith(surface: MyColour.transparent), + tabBarTheme: TabBarThemeData(indicatorColor: MyColour.offOffGrey)), routes: { '/': (BuildContext context) => HomeScreen(), '/settings': (BuildContext context) => const SettingsScreen(), diff --git a/lib/notifications/db_provider.dart b/lib/notifications/db_provider.dart index 7e5ef671..a2871b81 100644 --- a/lib/notifications/db_provider.dart +++ b/lib/notifications/db_provider.dart @@ -9,16 +9,16 @@ import 'package:sqflite/sqflite.dart'; import 'package:uuid/uuid.dart'; class DBProvider { - DBProvider(this.dbPath, {this.fillWithNotifications: false}); + DBProvider(this.dbPath, {this.fillWithNotifications = false}); final String _table = 'notifications'; final String dbPath; - Database _db; + Database? _db; bool fillWithNotifications; Future initDB() async { if (_db != null) { - return _db; + return _db!; } Directory dir; @@ -52,7 +52,7 @@ class DBProvider { if (fillWithNotifications) { await _insertDummyNotifications(); } - return _db; + return _db!; } Future store(NotificationUI notification) async { @@ -83,7 +83,7 @@ class DBProvider { return db.rawDelete('DELETE FROM $_table'); } - Future markRead(int id, {bool isRead}) async { + Future markRead(int id, {required bool isRead}) async { if (isTest) return -1; int read = 0; if (isRead) read = 1; diff --git a/lib/notifications/notification.dart b/lib/notifications/notification.dart index e86a1e47..539cf1e1 100644 --- a/lib/notifications/notification.dart +++ b/lib/notifications/notification.dart @@ -11,28 +11,24 @@ import 'package:notifi/utils/pallete.dart'; import 'package:notifi/utils/utils.dart'; import 'package:provider/provider.dart'; import 'package:timeago/timeago.dart' as timeago; -import 'package:toast/toast.dart'; @JsonSerializable() // ignore: must_be_immutable class NotificationUI extends StatefulWidget { NotificationUI( - {@required this.uuid, - @required this.time, - @required this.title, - this.message, - this.image, - this.link, - this.id, - this.read, - this.canExpand}) + {required this.uuid, + required this.time, + required this.title, + this.message = '', + this.image = '', + this.link = '', + this.id = -1, + this.read = false, + this.canExpand = false}) : super(key: Key('notification')) { dttmTime = i.DateFormat('yyyy-MM-dd HH:mm:ss').parse(time, true).toLocal(); - message = message ?? ''; - image = image ?? ''; - link = link ?? ''; - read = read ?? false; - canExpand = canExpand ?? false; + shrinkTitle = title; + shrinkMessage = message; } factory NotificationUI.fromJson(Map json) => @@ -48,13 +44,13 @@ class NotificationUI extends StatefulWidget { bool read; bool canExpand; bool isExpanded = false; - int index; - DateTime dttmTime; - void Function(BuildContext context, int id) toggleExpand; - String shrinkTitle; - String shrinkMessage; + late int index; + late DateTime dttmTime; + late void Function(BuildContext context, int id) toggleExpand; + late String shrinkTitle; + late String shrinkMessage; - bool get isRead => read != null && read; + bool get isRead => read; Map toJson() => _$NotificationToJson(this); @@ -92,12 +88,12 @@ class NotificationUIState extends State final GlobalKey _titleKey = GlobalKey(); final GlobalKey _messageKey = GlobalKey(); final ValueNotifier _timeStr = ValueNotifier(''); - Timer timer; + Timer? timer; double iconSize = 15.0; @override - void setState(Function fn) { + void setState(VoidCallback fn) { if (mounted) { super.setState(fn); } @@ -109,7 +105,10 @@ class NotificationUIState extends State WidgetsBinding.instance .addPostFrameCallback((_) => _canExpandHandler(context)); WidgetsBinding.instance.addObserver(this); - timer = Timer.periodic(const Duration(minutes: 1), (Timer t) => _setTime()); + if (!isTest) { + timer = + Timer.periodic(const Duration(minutes: 1), (Timer t) => _setTime()); + } } @override @@ -126,11 +125,11 @@ class NotificationUIState extends State @override Widget build(BuildContext context) { return Consumer(builder: - (BuildContext context, Notifications reloadTable, Widget child) { + (BuildContext context, Notifications reloadTable, Widget? child) { String title = widget.title; String message = widget.message; - int messageMaxLines = 3; - int titleMaxLines = 1; + int? messageMaxLines = 3; + int? titleMaxLines = 1; _setTime(); @@ -139,12 +138,8 @@ class NotificationUIState extends State titleMaxLines = null; messageMaxLines = null; } else { - if (widget.shrinkTitle != null) { - title = widget.shrinkTitle; - } - if (widget.shrinkMessage != null) { - message = widget.shrinkMessage; - } + title = widget.shrinkTitle; + message = widget.shrinkMessage; } // if read notification @@ -168,7 +163,7 @@ class NotificationUIState extends State }); }, scrollPhysics: const NeverScrollableScrollPhysics(), - style: Theme.of(context).textTheme.bodyText1, + style: Theme.of(context).textTheme.bodyLarge!, minLines: 1, maxLines: messageMaxLines)), ]); @@ -177,7 +172,7 @@ class NotificationUIState extends State } // if link - Widget linkBtn; + Widget? linkBtn; if (widget.link != '') { linkBtn = InkWell( onTap: () async { @@ -188,7 +183,7 @@ class NotificationUIState extends State }); }, onLongPress: () { - Toast.show(widget.link, context, gravity: Toast.CENTER); + showToast(widget.link, context); }, child: Container( padding: const EdgeInsets.only(top: 7.0), @@ -200,7 +195,7 @@ class NotificationUIState extends State } // if image - Widget image; + Widget? image; if (widget.image != '') { image = MouseRegion( cursor: SystemMouseCursors.alias, @@ -244,7 +239,9 @@ class NotificationUIState extends State left: padding, right: padding, top: padding), child: Container( decoration: BoxDecoration( - border: Border.all(color: Theme.of(context).indicatorColor), + border: Border.all( + color: Theme.of(context).tabBarTheme.indicatorColor ?? + Theme.of(context).primaryColor), color: backgroundColour, borderRadius: const BorderRadius.all(Radius.circular(padding))), @@ -329,9 +326,10 @@ class NotificationUIState extends State scrollPhysics: // ignore: lines_longer_than_80_chars const NeverScrollableScrollPhysics(), - style: Theme.of(context) - .textTheme - .headline1 + style: (Theme.of(context) + .textTheme + .displayLarge ?? + const TextStyle()) .copyWith(color: titleColour), textAlign: TextAlign.left, minLines: 1, @@ -365,12 +363,13 @@ class NotificationUIState extends State ValueListenableBuilder( valueListenable: _timeStr, builder: (BuildContext context, - String timeStr, Widget child) { + String timeStr, Widget? child) { return Expanded( child: SelectableText(timeStr, - style: Theme.of(context) - .textTheme - .subtitle1), + style: Theme.of(context) + .textTheme + .titleMedium ?? + const TextStyle()), ); }) ]), @@ -396,18 +395,23 @@ class NotificationUIState extends State bool canExpand = false; // prevent check if can expand when window is scaling up - if (Platform.isMacOS && _columnKey.currentContext.size.width <= 123) return; + if (!isTest && + Platform.isMacOS && + (_columnKey.currentContext?.size?.width ?? 0) <= 123) return; - double maxWidth = _columnKey.currentContext.size.width; + double maxWidth = _columnKey.currentContext?.size?.width ?? 0; // account for icon if (Platform.isMacOS || Platform.isLinux) maxWidth -= iconSize; + final TextTheme textTheme = Theme.of(context).textTheme; if (_columnKey.currentContext != null && - hasTextOverflow(widget.title, Theme.of(context).textTheme.headline1, + textTheme.displayLarge != null && + maxWidth > 10 && + hasTextOverflow(widget.title, textTheme.displayLarge!, maxWidth: maxWidth)) { canExpand = true; widget.shrinkTitle = getEclipsedText( - widget.title, Theme.of(context).textTheme.headline1, + widget.title, textTheme.displayLarge!, maxWidth: maxWidth); } else { widget.shrinkTitle = widget.title; @@ -415,18 +419,21 @@ class NotificationUIState extends State if (_messageKey.currentContext != null && widget.message != '' && - hasTextOverflow(widget.message, Theme.of(context).textTheme.bodyText1, - maxWidth: _messageKey.currentContext.size.width, maxLines: 3)) { + textTheme.bodyLarge != null && + (_messageKey.currentContext?.size?.width ?? 0) > 10 && + hasTextOverflow(widget.message, textTheme.bodyLarge!, + maxWidth: _messageKey.currentContext?.size?.width ?? 0, + maxLines: 3)) { canExpand = true; widget.shrinkMessage = getEclipsedText( - widget.message, Theme.of(context).textTheme.bodyText1, - maxWidth: _messageKey.currentContext.size.width, maxLines: 3); + widget.message, textTheme.bodyLarge!, + maxWidth: _messageKey.currentContext?.size?.width ?? 0, maxLines: 3); } else { widget.shrinkMessage = widget.message; } widget.canExpand = canExpand; - setState(() {}); + if (mounted) setState(() {}); } void _setTime() { diff --git a/lib/notifications/notifications_table.dart b/lib/notifications/notifications_table.dart index f9fb6ce3..1afe3333 100644 --- a/lib/notifications/notifications_table.dart +++ b/lib/notifications/notifications_table.dart @@ -6,7 +6,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:notifi/notifications/notification.dart'; import 'package:notifi/notifications/notifis.dart'; -import 'package:notifi/screens/utils/loading_gif.dart'; import 'package:notifi/user.dart'; import 'package:notifi/utils/pallete.dart'; import 'package:notifi/utils/utils.dart'; @@ -14,7 +13,7 @@ import 'package:provider/provider.dart'; import 'package:share_plus/share_plus.dart'; class NotificationTable extends StatefulWidget { - const NotificationTable({Key key}) : super(key: key); + const NotificationTable({Key? key}) : super(key: key); @override NotificationTableState createState() => NotificationTableState(); @@ -25,9 +24,8 @@ class NotificationTableState extends State @override Widget build(BuildContext context) { return Consumer(builder: - (BuildContext context, TableNotifier reloadTable, Widget child) { - final Notifications notifications = - Provider.of(context, listen: true); + (BuildContext context, TableNotifier reloadTable, Widget? child) { + final Notifications notifications = Provider.of(context); if (notifications.notifications.isNotEmpty) { return AnimatedList( padding: const EdgeInsets.only(bottom: 10), @@ -47,32 +45,29 @@ class NotificationTableState extends State width: imageWidth, filterQuality: FilterQuality.high), Container(padding: const EdgeInsets.only(top: 30.0)), Consumer( - builder: (BuildContext context, User user, Widget child) { + builder: (BuildContext context, User user, Widget? child) { final String credentials = user.getCredentials(); String howToLink = '$httpEndpoint#how-to'; Color howToColour = Theme.of(context).colorScheme.primary; Widget credentialsWidget; - if (credentials != null) { - howToLink = '$httpEndpoint?c=$credentials#how-to'; - howToColour = Theme.of(context).colorScheme.secondary; - credentialsWidget = InkWell( - child: Text(credentials, - key: Key('credentials'), - style: TextStyle( - color: Theme.of(context).colorScheme.secondary, - fontWeight: FontWeight.w800, - fontSize: 17), - textAlign: TextAlign.center), - onTap: () async { - if (Platform.isIOS) { - await Share.share('$credentials '); - } else { - await copyText(credentials, context); - } - }); - } else { - credentialsWidget = LoadingGif(); - } + howToLink = '$httpEndpoint?c=$credentials#how-to'; + howToColour = Theme.of(context).colorScheme.secondary; + credentialsWidget = InkWell( + child: Text(credentials, + key: Key('credentials'), + style: TextStyle( + color: Theme.of(context).colorScheme.secondary, + fontWeight: FontWeight.w800, + fontSize: 17), + textAlign: TextAlign.center), + onTap: () async { + if (Platform.isIOS) { + // ignore: deprecated_member_use + await Share.share('$credentials '); + } else { + await copyText(credentials, context); + } + }); TextStyle textStyle = TextStyle( color: MyColour.grey, @@ -134,7 +129,7 @@ class NotificationTableState extends State notification = Provider.of(context, listen: false).get(index); } catch (e) { - L.e(e); + L.e(e.toString()); return SizedBox(); } notification.index = index; @@ -231,8 +226,8 @@ class NotificationTableState extends State class MouseRegionSpan extends WidgetSpan { MouseRegionSpan({ - @required MouseCursor mouseCursor, - @required InlineSpan inlineSpan, + required MouseCursor mouseCursor, + required InlineSpan inlineSpan, }) : super( child: MouseRegion( cursor: mouseCursor, diff --git a/lib/notifications/notifis.dart b/lib/notifications/notifis.dart index d8f4834f..1328b892 100644 --- a/lib/notifications/notifis.dart +++ b/lib/notifications/notifis.dart @@ -7,7 +7,7 @@ import 'package:notifi/utils/utils.dart'; class Notifications extends ChangeNotifier { Notifications(this.notifications, this.db, this.tableNotifier, - {this.canBadge}) { + {this.canBadge = false}) { setUnreadCnt(); } @@ -48,11 +48,9 @@ class Notifications extends ChangeNotifier { void setUnreadCnt() { int cnt = 0; - if (notifications != null) { - for (int i = 0; i < notifications.length; i++) { - if (!notifications[i].isRead) { - cnt++; - } + for (int i = 0; i < notifications.length; i++) { + if (!notifications[i].isRead) { + cnt++; } } @@ -98,7 +96,7 @@ class Notifications extends ChangeNotifier { tableNotifier.notify(); } else { // animate out notification - tableKey.currentState.removeItem(index, + tableKey.currentState?.removeItem(index, (BuildContext context, Animation animation) { final Animation _offsetAnimation = Tween( begin: const Offset(-0.2, 0.0), @@ -109,7 +107,7 @@ class Notifications extends ChangeNotifier { position: _offsetAnimation, child: notification, ); - }, duration: const Duration(milliseconds: 300)); + }); } } @@ -122,7 +120,7 @@ class Notifications extends ChangeNotifier { setUnreadCnt(); } - Future markRead(int index, {bool isRead}) async { + Future markRead(int index, {required bool isRead}) async { HapticFeedback.lightImpact(); notifications[index].read = isRead; await db.markRead(notifications[index].id, isRead: isRead); diff --git a/lib/screens/home.dart b/lib/screens/home.dart index 098e9635..fec423ad 100644 --- a/lib/screens/home.dart +++ b/lib/screens/home.dart @@ -31,8 +31,8 @@ class HomeScreen extends StatelessWidget { decoration: BoxDecoration( border: Border( top: BorderSide( - color: Theme.of(context) - .indicatorColor) // red as border color + color: Theme.of(context).tabBarTheme.indicatorColor ?? + Theme.of(context).primaryColor) // red as border color ), ), height: 50, @@ -61,7 +61,9 @@ class HomeScreen extends StatelessWidget { SizedBox( height: 30, child: Container( - color: Theme.of(context).indicatorColor, width: 1)), + color: Theme.of(context).tabBarTheme.indicatorColor ?? + Theme.of(context).primaryColor, + width: 1)), Expanded( child: TextButton( onPressed: () { diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart index 691a0b53..ef83b0de 100644 --- a/lib/screens/settings.dart +++ b/lib/screens/settings.dart @@ -6,7 +6,7 @@ import 'package:app_settings/app_settings.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:launch_at_login/launch_at_login.dart'; + import 'package:notifi/notifications/notifications_table.dart'; import 'package:notifi/screens/utils/alert.dart'; import 'package:notifi/screens/utils/scaffold.dart'; @@ -18,19 +18,19 @@ import 'package:package_info_plus/package_info_plus.dart'; import 'package:provider/provider.dart'; import 'package:share_plus/share_plus.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:toast/toast.dart'; + import 'package:url_launcher/url_launcher.dart'; class SettingsScreen extends StatefulWidget { - const SettingsScreen({Key key}) : super(key: key); + const SettingsScreen({Key? key}) : super(key: key); @override SettingsScreenState createState() => SettingsScreenState(); } class SettingsScreenState extends State { - ValueNotifier _versionString; - ValueNotifier _hasUpgrade; + late ValueNotifier _versionString; + late ValueNotifier _hasUpgrade; @override void initState() { @@ -89,7 +89,7 @@ class SettingsScreenState extends State { }), body: Column(children: [ Consumer( - builder: (BuildContext context, User user, Widget child) { + builder: (BuildContext context, User user, Widget? child) { final String credentials = user.getCredentials(); SettingOption credentialsSettingWidget = @@ -101,6 +101,7 @@ class SettingsScreenState extends State { credentialsSettingWidget = SettingOption('Share Credentials', AkarIcons.share_box, onTapCallback: () async { + // ignore: deprecated_member_use await Share.share('$credentials '); }); } @@ -128,18 +129,18 @@ class SettingsScreenState extends State { .setNewUser(); Navigator.pop(context); if (!gotUser) { - Toast.show( + showToast( 'Problem fetching new credentials. ' 'Please try again later...', - context, - gravity: Toast.CENTER); + context); } } }); }), if (Platform.isIOS) SettingOption('iOS App Settings...', AkarIcons.gear, - onTapCallback: AppSettings.openNotificationSettings), + onTapCallback: () => AppSettings.openAppSettings( + type: AppSettingsType.notification)), SettingOption('About...', AkarIcons.info, onTapCallback: () => openUrl('https://notifi.it')), SettingOption('Other Platforms...', otherPlatformsIcon, @@ -150,52 +151,25 @@ class SettingsScreenState extends State { androidAppId: 'it.notifi.notifi', iOSAppId: '1563961135', )), - if (Platform.isMacOS) - Container( - padding: const EdgeInsets.only(top: 5), - child: FutureBuilder( - future: LaunchAtLogin.isEnabled, - builder: (BuildContext context, AsyncSnapshot f) { - if (f.connectionState == ConnectionState.none && - f.hasData == null) { - return const CircularProgressIndicator(); - } - return SettingOption( - 'Open notifi at Login', - AkarIcons.person, - switchValue: f.data, - switchCallback: (_) async { - final bool enabled = await LaunchAtLogin.isEnabled; - if (enabled) { - await LaunchAtLogin.disable; - } else { - await LaunchAtLogin.enable; - } - setState(() {}); - }, - ); - }), - ), if (Platform.isLinux) Container( padding: const EdgeInsets.only(top: 5), child: FutureBuilder( future: linuxDoesAutoLogin(), builder: (BuildContext context, AsyncSnapshot f) { - if (f.connectionState == ConnectionState.none && - f.hasData == null) { + if (f.connectionState == ConnectionState.none) { return const CircularProgressIndicator(); } return SettingOption( 'Open notifi at Login', AkarIcons.person, - switchValue: f.data, + switchValue: f.data ?? false, switchCallback: (_) async { File desktopPath = await getOpenOnLinuxLoginSnapDesktopFilePath(); File localSnapDesktopPath = File('snap/gui/notifi.desktop'); - if (f.data) { + if (f.data ?? false) { await desktopPath.delete(); } else { localSnapDesktopPath.copy(desktopPath.path); @@ -211,7 +185,6 @@ class SettingsScreenState extends State { // ignore: always_specify_types builder: (BuildContext context, AsyncSnapshot f) { if (f.connectionState == ConnectionState.none || - f.hasData == null || f.data == null) { return const CircularProgressIndicator(); } @@ -255,7 +228,7 @@ class SettingsScreenState extends State { fontFamily: 'Inconsolata'), recognizer: TapGestureRecognizer() ..onTap = () { - launch('https://max.me.uk'); + launchUrl(Uri.parse('https://max.me.uk')); }, )), ])), @@ -263,7 +236,7 @@ class SettingsScreenState extends State { if (!isTest) ValueListenableBuilder( valueListenable: _versionString, - builder: (BuildContext context, String version, Widget child) { + builder: (BuildContext context, String version, Widget? child) { return Container( padding: const EdgeInsets.only(top: 10), child: Column( @@ -276,7 +249,7 @@ class SettingsScreenState extends State { ValueListenableBuilder( valueListenable: _hasUpgrade, builder: (BuildContext context, bool hasUpgrade, - Widget child) { + Widget? child) { if (hasUpgrade) { return TextButton( onPressed: () { @@ -304,14 +277,17 @@ class SettingsScreenState extends State { // ignore: must_be_immutable class SettingOption extends StatelessWidget { SettingOption(this.text, this.icon, - {Key key, this.onTapCallback, this.switchCallback, this.switchValue}) + {Key? key, + this.onTapCallback, + this.switchCallback, + this.switchValue = false}) : super(key: key); final String text; final IconData icon; - GestureTapCallback onTapCallback; - ValueChanged switchCallback; - bool switchValue; + final GestureTapCallback? onTapCallback; + final ValueChanged? switchCallback; + final bool switchValue; @override Widget build(BuildContext context) { @@ -322,47 +298,21 @@ class SettingOption extends StatelessWidget { double verticalPadding = 0; if (Platform.isLinux || Platform.isMacOS) verticalPadding = 13; Widget setting; - if (switchCallback == null) { - setting = Container( - padding: EdgeInsets.only(top: 15 + verticalPadding), - child: ElevatedButton( - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( - Theme.of(context).backgroundColor), - overlayColor: MaterialStateProperty.all(MyColour.white), - elevation: MaterialStateProperty.all(0)), - onPressed: onTapCallback, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row(children: [ - iconWidget, - Padding( - padding: const EdgeInsets.only(bottom: 1.5), - child: Text(text, - style: Theme.of(context).textTheme.bodyText2), - ) - ]), - Icon(AkarIcons.chevron_right, - size: 20, color: MyColour.black) - ]))); - } else { - switchValue ??= false; - setting = Container( - padding: EdgeInsets.only(left: 16, right: 7, top: verticalPadding), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row(children: [ - iconWidget, - Text(text, style: Theme.of(context).textTheme.bodyText2) - ]), - Switch( - value: switchValue, - onChanged: switchCallback, - activeColor: Theme.of(context).colorScheme.secondary) - ])); - } + // switchValue ??= false; + setting = Container( + padding: EdgeInsets.only(left: 16, right: 7, top: verticalPadding), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row(children: [ + iconWidget, + Text(text, style: Theme.of(context).textTheme.bodyMedium) + ]), + Switch( + value: switchValue, + onChanged: switchCallback, + activeThumbColor: Theme.of(context).colorScheme.secondary) + ])); return setting; } } diff --git a/lib/screens/utils/alert.dart b/lib/screens/utils/alert.dart index 26232033..3461a60a 100644 --- a/lib/screens/utils/alert.dart +++ b/lib/screens/utils/alert.dart @@ -5,7 +5,7 @@ import 'package:notifi/utils/pallete.dart'; import 'package:provider/provider.dart'; Future showAlert(BuildContext context, String title, String description, - {int duration, int gravity, VoidCallback onOkPressed}) { + {required VoidCallback onOkPressed, int? duration, int? gravity}) { return showDialog( context: context, builder: (BuildContext context) { diff --git a/lib/screens/utils/animated_cnt.dart b/lib/screens/utils/animated_cnt.dart index d0ade845..61360e26 100644 --- a/lib/screens/utils/animated_cnt.dart +++ b/lib/screens/utils/animated_cnt.dart @@ -6,7 +6,7 @@ import 'package:notifi/utils/pallete.dart'; import 'package:provider/provider.dart'; class AnimatedCnt extends StatefulWidget { - const AnimatedCnt({Key key}) : super(key: key); + const AnimatedCnt({Key? key}) : super(key: key); @override _AnimatedCntState createState() => _AnimatedCntState(); @@ -14,11 +14,11 @@ class AnimatedCnt extends StatefulWidget { class _AnimatedCntState extends State with TickerProviderStateMixin { - AnimationController _controller; + AnimationController? _controller; @override void dispose() { - if (_controller != null) _controller.dispose(); + _controller?.dispose(); super.dispose(); } @@ -28,7 +28,7 @@ class _AnimatedCntState extends State Provider.of(context, listen: false); return ValueListenableBuilder( valueListenable: notifications.notificationCnt, - builder: (BuildContext context, int notificationCnt, Widget child) { + builder: (BuildContext context, int notificationCnt, Widget? child) { if (notificationCnt != 0) { String numUnread = notificationCnt.toString(); if (notificationCnt > 99) { @@ -47,7 +47,7 @@ class _AnimatedCntState extends State return ScaleTransition( scale: CurvedAnimation( - parent: _controller, + parent: _controller!, curve: Curves.bounceOut, ), child: CircleAvatar( diff --git a/lib/screens/utils/scaffold.dart b/lib/screens/utils/scaffold.dart index cf3509e3..e0876af6 100644 --- a/lib/screens/utils/scaffold.dart +++ b/lib/screens/utils/scaffold.dart @@ -6,11 +6,13 @@ import 'package:notifi/utils/pallete.dart'; import 'package:provider/provider.dart'; class MyScaffold extends StatelessWidget { - const MyScaffold({this.leading, this.body, this.bottomNavigationBar}); + const MyScaffold( + {Key? key, this.leading, this.body, this.bottomNavigationBar}) + : super(key: key); - final Widget leading; - final Widget body; - final Widget bottomNavigationBar; + final Widget? leading; + final Widget? body; + final Widget? bottomNavigationBar; @override Widget build(BuildContext context) { @@ -35,8 +37,9 @@ class MyScaffold extends StatelessWidget { toolbarHeight: 70, automaticallyImplyLeading: false, shape: Border( - bottom: - BorderSide(color: Theme.of(context).indicatorColor)), + bottom: BorderSide( + color: Theme.of(context).tabBarTheme.indicatorColor ?? + Theme.of(context).primaryColor)), title: Padding( padding: const EdgeInsets.only(right: 56), child: Stack( @@ -49,9 +52,7 @@ class MyScaffold extends StatelessWidget { Provider.of(context, listen: false) .scrollToTop(); }, - child: Image.asset('images/bell.png', - height: 50, - filterQuality: FilterQuality.medium), + child: Image.asset('images/bell.png', height: 50), ), ), Positioned( diff --git a/lib/user.dart b/lib/user.dart index f2dac1bd..9925d846 100644 --- a/lib/user.dart +++ b/lib/user.dart @@ -25,14 +25,14 @@ class User with ChangeNotifier { setNotifications(_notifications); } - UserStruct _user; - String flutterToken; - IOWebSocketChannel _ws; - BuildContext _snackContext; + late UserStruct _user; + String? flutterToken; + IOWebSocketChannel? _ws; + late BuildContext _snackContext; Notifications _notifications; - final FlutterLocalNotificationsPlugin _pushNotifications; + final FlutterLocalNotificationsPlugin? _pushNotifications; // ignore: use_setters_to_change_properties void setNotifications(Notifications _notifications) { @@ -40,7 +40,7 @@ class User with ChangeNotifier { } String getCredentials() { - return _user.credentials; + return _user.credentials ?? ''; } Future loadUser() async { @@ -48,13 +48,15 @@ class User with ChangeNotifier { final bool hadUser = await _user.load(); // create new credentials if any are missing - while (_user.isNull()) { + int retryCount = 0; + while (_user.isNull() && !isTest && retryCount < 3) { // Create new credentials as the user does not have any. await setNewUser(); setErr(hasErr: _user.isNull()); if (_user.isNull()) { L.w('Attempting to create user again...'); + retryCount++; await Future.delayed(const Duration(seconds: 5)); } } @@ -88,7 +90,7 @@ class User with ChangeNotifier { _user = newUser; notifyListeners(); - initWSS(); + if (!isTest) initWSS(); } } return !newUser.isNull(); @@ -98,9 +100,10 @@ class User with ChangeNotifier { // ws // //////// Future initWSS({bool shouldDelay = false}) async { + if (isTest) return; await closeWS(shouldDelay: shouldDelay); await connectToWS(); - _ws.sink.add('.'); + _ws!.sink.add('.'); } Future connectToWS() async { @@ -124,17 +127,15 @@ class User with ChangeNotifier { setErr(hasErr: false); bool _hasError = true; - _ws.stream.listen((dynamic streamData) async { + _ws!.stream.listen((dynamic streamData) async { _hasError = false; final List notificationUUIDs = await _handleMessage(streamData); - if (notificationUUIDs != null && _ws != null) { - // confirm received UUIDs with server in chunks - int chunkSize = 20; - int numUUIDs = notificationUUIDs.length; - for (int i = 0; i < numUUIDs; i += chunkSize) { - int end = (i + chunkSize < numUUIDs) ? i + chunkSize : numUUIDs; - _ws.sink.add(jsonEncode(notificationUUIDs.sublist(i, end))); - } + // confirm received UUIDs with server in chunks + int chunkSize = 20; + int numUUIDs = notificationUUIDs.length; + for (int i = 0; i < numUUIDs; i += chunkSize) { + int end = (i + chunkSize < numUUIDs) ? i + chunkSize : numUUIDs; + _ws!.sink.add(jsonEncode(notificationUUIDs.sublist(i, end))); } // ignore: always_specify_types }, onError: (e) async { @@ -147,18 +148,20 @@ class User with ChangeNotifier { }, cancelOnError: false); } - Future closeWS({bool shouldDelay}) async { - if (_ws != null) { - L.i('Closing already open WS...'); - _ws.sink.close(status.normalClosure, 'new code!'); - _ws = null; + Future closeWS({bool shouldDelay = false}) async { + L.i('Closing already open WS...'); + await _ws?.sink.close(status.normalClosure, 'new code!'); + _ws = null; + if (!isTest) { await Future.delayed(Duration(seconds: shouldDelay ? 10 : 2)); } } Future _newUserReq(Map data) async { L.i('Creating new credentials...'); - final d.Dio dio = d.Dio(); + final d.Dio dio = d.Dio(d.BaseOptions( + connectTimeout: const Duration(seconds: 30), + receiveTimeout: const Duration(seconds: 30))); Response response; try { response = await dio.post(codeEndpoint, @@ -166,12 +169,10 @@ class User with ChangeNotifier { options: d.Options(headers: { 'Sec-Key': dotenv.env['SERVER_KEY'], }, contentType: d.Headers.formUrlEncodedContentType)); - } on DioError catch (e, _) { + } on DioException catch (e, _) { // ignore: always_specify_types - final d.Response resp = e.response; - if (resp != null) { - L.e('Problem fetching user code: ${resp.statusCode} ${resp}'); - } + final d.Response? resp = e.response; + L.e('Problem fetching user code: ${resp?.statusCode} ${resp}'); return UserStruct(); } @@ -211,8 +212,8 @@ class User with ChangeNotifier { // parse notifications from websocket message final List msgUUIDs = []; - NotificationUI lastNotification; - int lastID; + NotificationUI? lastNotification; + int lastID = -1; for (int i = 0; i < notifications.length; i++) { Map jsonMessage; try { @@ -223,24 +224,21 @@ class User with ChangeNotifier { return []; } - if (jsonMessage != null) { - final NotificationUI notification = - NotificationUI.fromJson(jsonMessage); + final NotificationUI notification = NotificationUI.fromJson(jsonMessage); - // store notification - final int id = await _notifications.add(notification); + // store notification + final int id = await _notifications.add(notification); - lastNotification = notification; - lastID = id; + lastNotification = notification; + lastID = id; - msgUUIDs.add(notification.uuid); - } + msgUUIDs.add(notification.uuid); } if (lastID != -1) { // send push notification if (!Platform.isAndroid && _pushNotifications != null) { - sendLocalNotification(_pushNotifications, lastID, lastNotification); + sendLocalNotification(_pushNotifications!, lastID, lastNotification!); } _notifications.scrollToTop(); @@ -250,9 +248,9 @@ class User with ChangeNotifier { for (int i = 0; i < msgUUIDs.length; i++) { if (i == msgUUIDs.length - 1) { _notifications.tableKey.currentState - .insertItem(0, duration: const Duration(seconds: 1)); + ?.insertItem(0, duration: const Duration(seconds: 1)); } else { - _notifications.tableKey.currentState.insertItem(0); + _notifications.tableKey.currentState?.insertItem(0); } } } @@ -269,12 +267,13 @@ class User with ChangeNotifier { _snackContext = context; } - bool _tmpErr; + bool _tmpErr = false; - void setErr({bool hasErr}) { + void setErr({required bool hasErr}) { // wait for 1 second to make sure hasErr hasn't changed. // To prevent from stuttering. _tmpErr = hasErr; + if (isTest) return; Future.delayed(const Duration(seconds: 2), () { if (_tmpErr == hasErr) { if (_tmpErr) { @@ -293,19 +292,19 @@ class UserStruct { UserStruct({this.uuid, this.credentialKey, this.credentials}) { if (!Platform.isLinux) { _storage = const FlutterSecureStorage(); - if (!isTest) _key = 'notifi-${dotenv.env['KEY_STORE']}'; + _key = 'notifi-${dotenv.env['KEY_STORE'] ?? 'test'}'; } } - FlutterSecureStorage _storage; - String _key; + FlutterSecureStorage? _storage; + late String _key; - String uuid; - String credentialKey; - String credentials; + String? uuid; + String? credentialKey; + String? credentials; bool isNull() { - return uuid == null || credentialKey == null || credentials == null; + return credentialKey == null; } Future store() async { @@ -317,7 +316,7 @@ class UserStruct { } try { - await _storage.write(key: _key, value: _toJson()); + await _storage?.write(key: _key, value: _toJson()); } catch (e) { L.e(e.toString()); return false; @@ -338,7 +337,9 @@ class UserStruct { } } else { try { - userJsonString = await _storage.read(key: _key); + final String? res = await _storage?.read(key: _key); + if (res == null) return false; + userJsonString = res; } catch (e) { L.e(e.toString()); return false; @@ -361,10 +362,10 @@ class UserStruct { String _toJson() { if (isNull()) throw 'Cannot encode unset user'; - return jsonEncode({ - 'UUID': uuid, - 'credentials': credentials, - 'credentialKey': credentialKey, + return jsonEncode({ + 'UUID': uuid ?? '', + 'credentials': credentials ?? '', + 'credentialKey': credentialKey ?? '', }); } } diff --git a/lib/utils/local_notifications.dart b/lib/utils/local_notifications.dart index 8cf8f67c..75e2469a 100644 --- a/lib/utils/local_notifications.dart +++ b/lib/utils/local_notifications.dart @@ -7,8 +7,8 @@ Future initPushNotifications() async { InitializationSettings settings = InitializationSettings( android: AndroidInitializationSettings('app_icon'), - iOS: IOSInitializationSettings(defaultPresentAlert: false), - macOS: MacOSInitializationSettings( + iOS: DarwinInitializationSettings(defaultPresentAlert: false), + macOS: DarwinInitializationSettings( defaultPresentAlert: false, ), linux: LinuxInitializationSettings( @@ -31,8 +31,8 @@ Future initPushNotifications() async { void sendLocalNotification(FlutterLocalNotificationsPlugin localNotification, int id, NotificationUI notification) { - const IOSNotificationDetails iOS = IOSNotificationDetails(); - const MacOSNotificationDetails macOS = MacOSNotificationDetails(); + const DarwinNotificationDetails iOS = DarwinNotificationDetails(); + const DarwinNotificationDetails macOS = DarwinNotificationDetails(); const NotificationDetails platformChannelSpecifics = NotificationDetails(iOS: iOS, macOS: macOS); diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 5ab6cccb..fbd0adf2 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -10,7 +10,7 @@ import 'package:notifi/utils/pallete.dart'; import 'package:path_provider/path_provider.dart'; import 'package:path/path.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:toast/toast.dart'; +import 'package:fluttertoast/fluttertoast.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:uuid/uuid.dart'; import 'package:intl/intl.dart' as i; @@ -64,7 +64,7 @@ class MenuBarIcon { Future loadDotEnv() async { if (isTest) { - await dotenv.testLoad(); + await dotenv.load(); return true; } else { await dotenv.load(); @@ -98,23 +98,32 @@ String get httpEndpoint { } Future openUrl(String url) async { - if (await canLaunch(url)) { + if (await canLaunchUrl(Uri.parse(url))) { await invokeMacMethod('close_window'); - await launch(url, forceSafariVC: false); + await launchUrl(Uri.parse(url)); } else { L.w("Can't open: $url"); } } -void showToast(String msg, BuildContext context, {int duration, int gravity}) { - Toast.show(msg, context, duration: duration, gravity: gravity); +void showToast(String msg, BuildContext context, + {int? duration, int? gravity}) { + Fluttertoast.showToast( + msg: msg, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.BOTTOM, + backgroundColor: Colors.black, + textColor: Colors.white, + fontSize: 16.0); } Future getDeviceUUID() async { if (Platform.isLinux || Globals.isIntegration) { return Uuid().v4(); } - return platform.invokeMethod('UUID'); + return platform + .invokeMethod('UUID') + .then((String? value) => value.toString()); } bool get shouldUseFirebase { @@ -146,15 +155,15 @@ class L { bool shouldPinWindow(SharedPreferences sp) { try { - return sp.getBool('pin-window') || false; + return sp.getBool('pin-window') ?? false; } catch (_) { return false; } } -void copyText(String text, BuildContext context) async { +Future copyText(String text, BuildContext context) async { await Clipboard.setData(ClipboardData(text: text)); - Toast.show('📋 $text', context, gravity: Toast.BOTTOM); + Fluttertoast.showToast(msg: '📋 $text', gravity: ToastGravity.BOTTOM); } bool hasTextOverflow(String text, TextStyle style, @@ -164,7 +173,7 @@ bool hasTextOverflow(String text, TextStyle style, maxLines: maxLines, textDirection: TextDirection.ltr, textWidthBasis: TextWidthBasis.longestLine, - )..layout(minWidth: 0, maxWidth: maxWidth); + )..layout(maxWidth: maxWidth); // not really sure why I have to -1 return textPainter.didExceedMaxLines; } @@ -187,7 +196,7 @@ double windowWidth(BuildContext context) { Future getFirebaseToken() async { try { - return await FirebaseMessaging.instance.getToken(); + return await FirebaseMessaging.instance.getToken() ?? ''; } catch (e) { L.e(e.toString()); } @@ -213,8 +222,8 @@ Future linuxDoesAutoLogin() async { } bool isTablet() { - MediaQueryData data = - MediaQueryData.fromWindow(WidgetsBinding.instance.window); + final MediaQueryData data = MediaQueryData.fromView( + WidgetsBinding.instance.platformDispatcher.views.first); return data.size.shortestSide > 600; } @@ -241,17 +250,17 @@ TextTheme getTextTheme() { } } return TextTheme( - headline1: TextStyle( + displayLarge: TextStyle( inherit: false, textBaseline: TextBaseline.alphabetic, fontFamily: 'Inconsolata', fontSize: defaultFontSize, fontWeight: FontWeight.w600), - subtitle1: TextStyle( + titleMedium: TextStyle( color: MyColour.grey, fontSize: subtitle1FontSize, fontFamily: 'Inconsolata'), - bodyText1: TextStyle( + bodyLarge: TextStyle( inherit: false, textBaseline: TextBaseline.alphabetic, fontFamily: 'Inconsolata', @@ -259,7 +268,7 @@ TextTheme getTextTheme() { fontSize: bodyText1FontSize, letterSpacing: 0.2, height: 1.2), - bodyText2: TextStyle( + bodyMedium: TextStyle( fontSize: defaultFontSize, color: MyColour.black, fontWeight: FontWeight.w500, diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 9a5ae0b9..e9bc12cf 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -8,6 +8,7 @@ #include #include +#include #include void fl_register_plugins(FlPluginRegistry* registry) { @@ -17,6 +18,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) flutter_app_icon_badge_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterAppIconBadgePlugin"); flutter_app_icon_badge_plugin_register_with_registrar(flutter_app_icon_badge_registrar); + g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); + flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index b1c96538..ce1e13c4 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -5,9 +5,13 @@ list(APPEND FLUTTER_PLUGIN_LIST desktop_window flutter_app_icon_badge + flutter_secure_storage_linux url_launcher_linux ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -16,3 +20,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index c2601549..eeb14f53 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,31 +5,31 @@ import FlutterMacOS import Foundation +import app_settings import desktop_window import firebase_core import firebase_messaging import flutter_app_icon_badge import flutter_local_notifications -import flutter_secure_storage -import launch_at_login -import package_info -import package_info_plus_macos -import path_provider_macos -import share_plus_macos -import shared_preferences_macos -import sqflite +import flutter_secure_storage_darwin +import local_auth_darwin +import package_info_plus +import path_provider_foundation +import share_plus +import shared_preferences_foundation +import sqflite_darwin import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + AppSettingsPlugin.register(with: registry.registrar(forPlugin: "AppSettingsPlugin")) DesktopWindowPlugin.register(with: registry.registrar(forPlugin: "DesktopWindowPlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin")) FlutterAppIconBadgePlugin.register(with: registry.registrar(forPlugin: "FlutterAppIconBadgePlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) - FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) - LaunchAtLoginPlugin.register(with: registry.registrar(forPlugin: "LaunchAtLoginPlugin")) - FLTPackageInfoPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlugin")) - FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) + FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin")) + LocalAuthPlugin.register(with: registry.registrar(forPlugin: "LocalAuthPlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) diff --git a/macos/Podfile b/macos/Podfile index 2c1ae274..acbbe357 100644 --- a/macos/Podfile +++ b/macos/Podfile @@ -1,4 +1,4 @@ -platform :osx, '10.13' +platform :osx, '10.15' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' @@ -37,5 +37,8 @@ end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_macos_build_settings(target) + target.build_configurations.each do |config| + config.build_settings['MACOSX_DEPLOYMENT_TARGET'] = '10.15' + end end end diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index 8633d694..fc194c57 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ @@ -189,6 +189,7 @@ 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, 31A77A0EB81A61893825BE9A /* [CP] Embed Pods Frameworks */, + 5B8DFA13DE7A98F38CD3E424 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -207,7 +208,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 0930; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 33CC10EC2044A3C60003C045 = { @@ -276,6 +277,7 @@ }; 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -289,7 +291,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n/bin/sh \"$FLUTTER_ROOT\"/.pub-cache/hosted/pub.dartlang.org/launch_at_login-0.0.3/lib/assets/copy-login-helper.sh\n"; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed"; }; 33CC111E2044C6BF0003C045 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; @@ -311,6 +313,23 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire\n"; }; + 5B8DFA13DE7A98F38CD3E424 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; E8FE23BADB7FE358ED65CE2D /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -408,7 +427,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -434,7 +453,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.15; MARKETING_VERSION = 0.1.1; PRODUCT_BUNDLE_IDENTIFIER = it.notifi.notifi; PROVISIONING_PROFILE_SPECIFIER = "match Development it.notifi.notifi macos"; @@ -494,7 +513,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -542,7 +561,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -568,7 +587,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.15; MARKETING_VERSION = 0.1.1; PRODUCT_BUNDLE_IDENTIFIER = it.notifi.notifi; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -595,7 +614,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.15; MARKETING_VERSION = 0.1.1; PRODUCT_BUNDLE_IDENTIFIER = it.notifi.notifi; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index e5c1cbda..72e49b3d 100644 --- a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift index 417b9323..db5158a4 100644 --- a/macos/Runner/AppDelegate.swift +++ b/macos/Runner/AppDelegate.swift @@ -11,7 +11,7 @@ extension NSImage.Name { static let error = NSImage.Name("menu_error_icon") } -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { var statusBarItem: NSStatusItem! let popover = NSPopover() diff --git a/pubspec.yaml b/pubspec.yaml index dbb5ad0f..8ccc9075 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,45 +5,42 @@ publish_to: 'none' version: 0.0.1 environment: - sdk: ">=2.7.0 <3.0.0" + sdk: ">=2.12.0 <4.0.0" dependencies: - akar_icons_flutter: 1.1.9 - app_settings: ^4.1.0 + akar_icons_flutter: ^1.1.21 + app_settings: ^7.0.0 cached_network_image: ^3.1.0 desktop_window: ^0.4.0 - dio: ^4.0.0 - firebase_core: ^1.12.0 - firebase_messaging: ^11.2.6 + dio: ^5.9.0 + firebase_core: ^4.3.0 + firebase_messaging: ^16.1.0 flutter: sdk: flutter flutter_app_icon_badge: ^2.0.0 - flutter_dotenv: ^5.0.0 + flutter_dotenv: ^6.0.0 flutter_emoji: ^2.2.1+1 - flutter_local_notifications: ^9.0.2 - flutter_secure_storage: - git: - url: https://github.com/openresearch/flutter_secure_storage - ref: feature/macos_support - flutter_slidable: ^1.2.0 - intl: ^0.17.0 + flutter_local_notifications: ^19.5.0 + flutter_secure_storage: ^10.0.0 + flutter_slidable: ^4.0.3 + fluttertoast: ^9.0.0 + intl: ^0.20.2 + json_annotation: any json_serializable: ^6.0.1 - launch_at_login: ^0.0.3 launch_review: ^3.0.1 - local_auth: ^1.1.8 - package_info: ^2.0.0 - package_info_plus: ^1.0.4 + local_auth: ^3.0.0 + package_info_plus: ^9.0.0 + path: any path_provider: ^2.0.2 provider: ^6.0.0 - share_plus: ^4.0.3 + share_plus: ^12.0.1 shared_preferences: ^2.0.6 sqflite: ^2.0.0+3 sqflite_common_ffi: ^2.0.0+1 timeago: ^3.0.2 - toast: ^0.1.5 url_launcher: ^6.0.9 - uuid: ^3.0.4 - web_socket_channel: ^2.1.0 + uuid: ^4.5.2 + web_socket_channel: ^3.0.3 dev_dependencies: flutter_driver: @@ -51,6 +48,8 @@ dev_dependencies: flutter_native_splash: ^2.1.2+1 flutter_test: sdk: flutter + integration_test: + sdk: flutter test: ^1.16.8 flutter: diff --git a/test/golden-asserts/notification/is_expanded.png b/test/golden-asserts/notification/is_expanded.png index 03b312d5..ffcd83ba 100644 Binary files a/test/golden-asserts/notification/is_expanded.png and b/test/golden-asserts/notification/is_expanded.png differ diff --git a/test/golden-asserts/notification/is_read.png b/test/golden-asserts/notification/is_read.png index 789a3821..08bf678d 100644 Binary files a/test/golden-asserts/notification/is_read.png and b/test/golden-asserts/notification/is_read.png differ diff --git a/test/golden-asserts/notification/title_0-message_0-links_0-images_0.png b/test/golden-asserts/notification/title_0-message_0-links_0-images_0.png index e4f65556..d4d4c64f 100644 Binary files a/test/golden-asserts/notification/title_0-message_0-links_0-images_0.png and b/test/golden-asserts/notification/title_0-message_0-links_0-images_0.png differ diff --git a/test/golden-asserts/notification/title_0-message_0-links_0-images_1.png b/test/golden-asserts/notification/title_0-message_0-links_0-images_1.png index 58f52a0a..42eec0d3 100644 Binary files a/test/golden-asserts/notification/title_0-message_0-links_0-images_1.png and b/test/golden-asserts/notification/title_0-message_0-links_0-images_1.png differ diff --git a/test/golden-asserts/notification/title_0-message_0-links_1-images_0.png b/test/golden-asserts/notification/title_0-message_0-links_1-images_0.png index 341a6b6a..17f13e0b 100644 Binary files a/test/golden-asserts/notification/title_0-message_0-links_1-images_0.png and b/test/golden-asserts/notification/title_0-message_0-links_1-images_0.png differ diff --git a/test/golden-asserts/notification/title_0-message_0-links_1-images_1.png b/test/golden-asserts/notification/title_0-message_0-links_1-images_1.png index a7a481c0..9d04752b 100644 Binary files a/test/golden-asserts/notification/title_0-message_0-links_1-images_1.png and b/test/golden-asserts/notification/title_0-message_0-links_1-images_1.png differ diff --git a/test/golden-asserts/notification/title_0-message_1-links_0-images_0.png b/test/golden-asserts/notification/title_0-message_1-links_0-images_0.png index f4104b68..3e91dd7b 100644 Binary files a/test/golden-asserts/notification/title_0-message_1-links_0-images_0.png and b/test/golden-asserts/notification/title_0-message_1-links_0-images_0.png differ diff --git a/test/golden-asserts/notification/title_0-message_1-links_0-images_1.png b/test/golden-asserts/notification/title_0-message_1-links_0-images_1.png index 8492e05a..ba44edd9 100644 Binary files a/test/golden-asserts/notification/title_0-message_1-links_0-images_1.png and b/test/golden-asserts/notification/title_0-message_1-links_0-images_1.png differ diff --git a/test/golden-asserts/notification/title_0-message_1-links_1-images_0.png b/test/golden-asserts/notification/title_0-message_1-links_1-images_0.png index cb970609..8379254e 100644 Binary files a/test/golden-asserts/notification/title_0-message_1-links_1-images_0.png and b/test/golden-asserts/notification/title_0-message_1-links_1-images_0.png differ diff --git a/test/golden-asserts/notification/title_0-message_1-links_1-images_1.png b/test/golden-asserts/notification/title_0-message_1-links_1-images_1.png index aa121932..9f0aee27 100644 Binary files a/test/golden-asserts/notification/title_0-message_1-links_1-images_1.png and b/test/golden-asserts/notification/title_0-message_1-links_1-images_1.png differ diff --git a/test/golden-asserts/notification/title_0-message_2-links_0-images_0.png b/test/golden-asserts/notification/title_0-message_2-links_0-images_0.png index 1f1181a8..49db4951 100644 Binary files a/test/golden-asserts/notification/title_0-message_2-links_0-images_0.png and b/test/golden-asserts/notification/title_0-message_2-links_0-images_0.png differ diff --git a/test/golden-asserts/notification/title_0-message_2-links_0-images_1.png b/test/golden-asserts/notification/title_0-message_2-links_0-images_1.png index 773acccf..fa521251 100644 Binary files a/test/golden-asserts/notification/title_0-message_2-links_0-images_1.png and b/test/golden-asserts/notification/title_0-message_2-links_0-images_1.png differ diff --git a/test/golden-asserts/notification/title_0-message_2-links_1-images_0.png b/test/golden-asserts/notification/title_0-message_2-links_1-images_0.png index 4523ec64..af7bbaf2 100644 Binary files a/test/golden-asserts/notification/title_0-message_2-links_1-images_0.png and b/test/golden-asserts/notification/title_0-message_2-links_1-images_0.png differ diff --git a/test/golden-asserts/notification/title_0-message_2-links_1-images_1.png b/test/golden-asserts/notification/title_0-message_2-links_1-images_1.png index 299c1d84..d37f2a2d 100644 Binary files a/test/golden-asserts/notification/title_0-message_2-links_1-images_1.png and b/test/golden-asserts/notification/title_0-message_2-links_1-images_1.png differ diff --git a/test/golden-asserts/notification/title_1-message_0-links_0-images_0.png b/test/golden-asserts/notification/title_1-message_0-links_0-images_0.png index 85fa65ab..3b996f5b 100644 Binary files a/test/golden-asserts/notification/title_1-message_0-links_0-images_0.png and b/test/golden-asserts/notification/title_1-message_0-links_0-images_0.png differ diff --git a/test/golden-asserts/notification/title_1-message_0-links_0-images_1.png b/test/golden-asserts/notification/title_1-message_0-links_0-images_1.png index 6db61ffa..30c0e1e1 100644 Binary files a/test/golden-asserts/notification/title_1-message_0-links_0-images_1.png and b/test/golden-asserts/notification/title_1-message_0-links_0-images_1.png differ diff --git a/test/golden-asserts/notification/title_1-message_0-links_1-images_0.png b/test/golden-asserts/notification/title_1-message_0-links_1-images_0.png index 2a20c1d3..af6df806 100644 Binary files a/test/golden-asserts/notification/title_1-message_0-links_1-images_0.png and b/test/golden-asserts/notification/title_1-message_0-links_1-images_0.png differ diff --git a/test/golden-asserts/notification/title_1-message_0-links_1-images_1.png b/test/golden-asserts/notification/title_1-message_0-links_1-images_1.png index df091440..eeba0bee 100644 Binary files a/test/golden-asserts/notification/title_1-message_0-links_1-images_1.png and b/test/golden-asserts/notification/title_1-message_0-links_1-images_1.png differ diff --git a/test/golden-asserts/notification/title_1-message_1-links_0-images_0.png b/test/golden-asserts/notification/title_1-message_1-links_0-images_0.png index ea5fcadc..01a3edbf 100644 Binary files a/test/golden-asserts/notification/title_1-message_1-links_0-images_0.png and b/test/golden-asserts/notification/title_1-message_1-links_0-images_0.png differ diff --git a/test/golden-asserts/notification/title_1-message_1-links_0-images_1.png b/test/golden-asserts/notification/title_1-message_1-links_0-images_1.png index e88991c3..137a92b1 100644 Binary files a/test/golden-asserts/notification/title_1-message_1-links_0-images_1.png and b/test/golden-asserts/notification/title_1-message_1-links_0-images_1.png differ diff --git a/test/golden-asserts/notification/title_1-message_1-links_1-images_0.png b/test/golden-asserts/notification/title_1-message_1-links_1-images_0.png index 1ff5fb7d..147162c3 100644 Binary files a/test/golden-asserts/notification/title_1-message_1-links_1-images_0.png and b/test/golden-asserts/notification/title_1-message_1-links_1-images_0.png differ diff --git a/test/golden-asserts/notification/title_1-message_1-links_1-images_1.png b/test/golden-asserts/notification/title_1-message_1-links_1-images_1.png index 378664f4..80bbf211 100644 Binary files a/test/golden-asserts/notification/title_1-message_1-links_1-images_1.png and b/test/golden-asserts/notification/title_1-message_1-links_1-images_1.png differ diff --git a/test/golden-asserts/notification/title_1-message_2-links_0-images_0.png b/test/golden-asserts/notification/title_1-message_2-links_0-images_0.png index 3f93c52b..25116aa9 100644 Binary files a/test/golden-asserts/notification/title_1-message_2-links_0-images_0.png and b/test/golden-asserts/notification/title_1-message_2-links_0-images_0.png differ diff --git a/test/golden-asserts/notification/title_1-message_2-links_0-images_1.png b/test/golden-asserts/notification/title_1-message_2-links_0-images_1.png index d81f850b..cb33e1ab 100644 Binary files a/test/golden-asserts/notification/title_1-message_2-links_0-images_1.png and b/test/golden-asserts/notification/title_1-message_2-links_0-images_1.png differ diff --git a/test/golden-asserts/notification/title_1-message_2-links_1-images_0.png b/test/golden-asserts/notification/title_1-message_2-links_1-images_0.png index 05bade6f..815002e6 100644 Binary files a/test/golden-asserts/notification/title_1-message_2-links_1-images_0.png and b/test/golden-asserts/notification/title_1-message_2-links_1-images_0.png differ diff --git a/test/golden-asserts/notification/title_1-message_2-links_1-images_1.png b/test/golden-asserts/notification/title_1-message_2-links_1-images_1.png index 139bd09a..f83e5b2a 100644 Binary files a/test/golden-asserts/notification/title_1-message_2-links_1-images_1.png and b/test/golden-asserts/notification/title_1-message_2-links_1-images_1.png differ diff --git a/test/golden-asserts/screen/no-notifications.png b/test/golden-asserts/screen/no-notifications.png index fe89a247..cfe17248 100644 Binary files a/test/golden-asserts/screen/no-notifications.png and b/test/golden-asserts/screen/no-notifications.png differ diff --git a/test/golden-asserts/screen/notification.png b/test/golden-asserts/screen/notification.png index 8b4adb29..810d769f 100644 Binary files a/test/golden-asserts/screen/notification.png and b/test/golden-asserts/screen/notification.png differ diff --git a/test/golden-asserts/screen/settings.png b/test/golden-asserts/screen/settings.png index edcc1b60..6946685e 100644 Binary files a/test/golden-asserts/screen/settings.png and b/test/golden-asserts/screen/settings.png differ diff --git a/test/widget_test.dart b/test/widget_test.dart index bc8fcfc5..bd87dd11 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -14,7 +14,6 @@ import 'package:notifi/user.dart'; import 'package:notifi/utils/utils.dart'; import 'package:provider/provider.dart'; import 'package:provider/single_child_widget.dart'; -import 'package:sqflite/sqflite.dart'; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; final String lorem = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ' @@ -29,7 +28,12 @@ final String lorem = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ' final double width = 1200; final TextTheme textTheme = getTextTheme(); -void main() { +void main() async { + TestWidgetsFlutterBinding.ensureInitialized(); + sqfliteFfiInit(); + databaseFactory = databaseFactoryFfi; + await loadDotEnv(); + const String time = '2006-01-02 15:04:05'; // original 2400.0, 1800.0 const Size physicalSizeTestValue = Size(2200, 1200); @@ -59,7 +63,8 @@ void main() { }); testWidgets('Test Settings Navigation', (WidgetTester tester) async { - tester.binding.window.physicalSizeTestValue = Size(2200, 4200); + tester.view.physicalSize = Size(2200, 4200); + addTearDown(tester.view.resetPhysicalSize); await pumpWidgetWithNotification(tester, null); // open settings @@ -93,30 +98,27 @@ void main() { const List links = ['', 'https://max.me.uk/']; const List images = ['', 'https://max.me.uk/someimage.jpg']; - String longTtl, longMsg; + String longTtl = lorem; + String longMsg = lorem; int cnt = 0; // got from printing _messageKey.currentContext.size.width double width = 666.33; // get long title - // ignore: literal_only_boolean_expressions - while (true) { - cnt += 1; - String str = lorem.substring(0, cnt); - if (hasTextOverflow(str, textTheme.headline1, maxWidth: width - 15)) { - // 15 == iconsize + for (int i = 1; i <= lorem.length; i++) { + String str = lorem.substring(0, i); + if (hasTextOverflow(str, textTheme.displayLarge!, maxWidth: width - 15)) { longTtl = str; + cnt = i; break; } } // get long message - // ignore: literal_only_boolean_expressions - while (true) { - cnt += 1; - String str = lorem.substring(0, cnt); - if (hasTextOverflow(str, textTheme.bodyText1, + for (int i = cnt + 1; i <= lorem.length; i++) { + String str = lorem.substring(0, i); + if (hasTextOverflow(str, textTheme.bodyLarge!, maxWidth: width, maxLines: 3)) { longMsg = str; break; @@ -138,8 +140,8 @@ void main() { ); final String name = 'title_$a-message_$b-links_$c-images_$d'; testWidgets(name, (WidgetTester tester) async { - tester.binding.window.physicalSizeTestValue = - physicalSizeTestValue; + tester.view.physicalSize = physicalSizeTestValue; + addTearDown(tester.view.resetPhysicalSize); await pumpWidgetWithNotification(tester, n); await tester.pump(); @@ -157,7 +159,6 @@ void main() { final NotificationUI n = NotificationUI( title: titles[0], time: time, - message: '', uuid: '', ); await pumpWidgetWithNotification(tester, n); @@ -183,7 +184,8 @@ void main() { time: time, uuid: '', ); - await pumpWidgetWithNotification(tester, n); + await pumpWidgetWithNotification(tester, n, + surfaceSize: const Size(400, 800)); await tester.pump(); await tester.pumpAndSettle(); @@ -225,7 +227,8 @@ void main() { }; inputsToBeExpected.forEach((String name, NotificationUI notification) { testWidgets(name, (WidgetTester tester) async { - await pumpWidgetWithNotification(tester, notification); + await pumpWidgetWithNotification(tester, notification, + surfaceSize: const Size(400, 800)); await tester.pump(); expect(find.byIcon(AkarIcons.enlarge), findsOneWidget); @@ -239,25 +242,26 @@ void main() { 'title': NotificationUI( time: time, uuid: '', - title: longTtl.substring(0, longTtl.length - 1), + title: 'Short Title', ), 'message': NotificationUI( time: time, uuid: '', title: 'foo', - message: longMsg.substring(0, longMsg.length - 1), + message: 'Short Message', ), 'title-message': NotificationUI( time: time, uuid: '', - title: longTtl.substring(0, longTtl.length - 1), - message: longMsg.substring(0, longMsg.length - 1), + title: 'Short Title', + message: 'Short Message', ), }; inputsToBeExpected.forEach((String name, NotificationUI notification) { testWidgets(name, (WidgetTester tester) async { - await pumpWidgetWithNotification(tester, notification); - await tester.pump(); + await pumpWidgetWithNotification(tester, notification, + surfaceSize: const Size(1200, 1200)); + await tester.pumpAndSettle(); expect(find.byIcon(AkarIcons.enlarge), findsNothing); }); @@ -267,7 +271,7 @@ void main() { } Future goldenAssert(Finder finder, String imagePath) async { - if (!Platform.isMacOS) { + if (!Platform.isMacOS || Platform.environment.containsKey('GITHUB_ACTIONS')) { // ignore: avoid_print print('Skipping $imagePath assert'); return; @@ -278,39 +282,59 @@ Future goldenAssert(Finder finder, String imagePath) async { } Future pumpWidgetWithNotification( - WidgetTester tester, NotificationUI notification) async { + WidgetTester tester, NotificationUI? notification, + {Size? surfaceSize}) async { WidgetsFlutterBinding.ensureInitialized(); // CHANNEL MOCKS - const MethodChannel('plugins.flutter.io/path_provider') - .setMockMethodCallHandler((MethodCall methodCall) async { - if (methodCall.method == 'getApplicationDocumentsDirectory') { - return ''; - } - if (methodCall.method == 'getLibraryDirectory') { - return ''; + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( + const MethodChannel('plugins.flutter.io/path_provider'), + (MethodCall methodCall) async { + if (methodCall.method == 'getApplicationDocumentsDirectory' || + methodCall.method == 'getLibraryDirectory' || + methodCall.method == 'getApplicationSupportDirectory' || + methodCall.method == 'getTemporaryDirectory') { + return '.'; } + return null; }); - const MethodChannel('com.tekartik.sqflite') - .setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger + .setMockMethodCallHandler(const MethodChannel('com.tekartik.sqflite'), + (MethodCall methodCall) async { if (methodCall.method == 'openDatabase') { return await databaseFactoryFfi.openDatabase(inMemoryDatabasePath); } if (methodCall.method == 'getDatabasesPath') { return ''; } + return null; }); - const MethodChannel('plugins.it_nomads.com/flutter_secure_storage') - .setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( + const MethodChannel('plugins.it_nomads.com/flutter_secure_storage'), + (MethodCall methodCall) async { if (methodCall.method == 'read') { return '{"UUID": "foo", "credentials": "bar", "credentialKey": "baz"}'; } + if (methodCall.method == 'write') { + return null; + } + return null; }); - const MethodChannel('vibration') - .setMockMethodCallHandler((MethodCall methodCall) async {}); + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( + const MethodChannel('vibration'), (MethodCall methodCall) async { + return null; + }); + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( + const MethodChannel('max.me.uk/notifications'), + (MethodCall methodCall) async { + if (methodCall.method == 'UUID') { + return 'foo'; + } + return null; + }); // finished MOCKS final DBProvider db = DBProvider('test.db'); @@ -320,25 +344,36 @@ Future pumpWidgetWithNotification( notifications.add(notification); } - await loadDotEnv(); + if (surfaceSize != null) { + tester.view.physicalSize = surfaceSize; + addTearDown(tester.view.resetPhysicalSize); + } await tester.pumpWidget(MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => TableNotifier()), ChangeNotifierProxyProvider( create: (BuildContext context) => Notifications(notifications, db, - Provider.of(context, listen: false), - canBadge: false), + Provider.of(context, listen: false)), update: (BuildContext context, TableNotifier tableNotifier, - Notifications user) => - user..setTableNotifier(tableNotifier), + Notifications? n) { + final Notifications notificationsInstance = n ?? + Notifications(notifications, db, + Provider.of(context, listen: false)); + notificationsInstance.setTableNotifier(tableNotifier); + return notificationsInstance; + }, ), ChangeNotifierProxyProvider( create: (BuildContext context) => User(Provider.of(context, listen: false), null), update: - (BuildContext context, Notifications notifications, User user) => - user..setNotifications(notifications), + (BuildContext context, Notifications notifications, User? user) { + final User userInstance = user ?? + User(Provider.of(context, listen: false), null); + userInstance.setNotifications(notifications); + return userInstance; + }, ), ], child: const MyApp(), diff --git a/test_driver/app.dart b/test_driver/app.dart index a925a69d..555b6ca1 100644 --- a/test_driver/app.dart +++ b/test_driver/app.dart @@ -5,4 +5,4 @@ import '../lib/main.dart' as app; void main() async { enableFlutterDriverExtension(); await app.mainImpl(integration: true); -} \ No newline at end of file +} diff --git a/test_driver/app_test.dart b/test_driver/app_test.dart index d9a6fc0a..cddfb8ca 100644 --- a/test_driver/app_test.dart +++ b/test_driver/app_test.dart @@ -4,10 +4,9 @@ import 'package:flutter_driver/flutter_driver.dart'; import 'package:test/test.dart'; import 'package:http/http.dart' as http; - void main() { group('Integration Tests', () { - FlutterDriver driver; + late FlutterDriver driver; // Connect to the Flutter driver before running any tests. setUpAll(() async { @@ -48,7 +47,6 @@ void main() { SerializableFinder newCredentials = find.byValueKey('new-credentials'); SerializableFinder ok = find.byValueKey('ok'); - await driver.waitFor(credentials); String initialCreds = await driver.getText(credentials); @@ -83,10 +81,10 @@ void main() { String creds = await driver.getText(credentials); // get host from .env - String host; + late String host; String file = await File('.env').readAsString(); - file.split('\n').forEach((String ln){ - if(ln.startsWith('HOST=')){ + file.split('\n').forEach((String ln) { + if (ln.startsWith('HOST=')) { host = ln.replaceAll('HOST=', ''); } });