From 92da25b2822250c12b2948488b9dc83751fbd4f5 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Thu, 4 Jan 2024 16:51:57 -0300 Subject: [PATCH 01/11] fix: Server offline icon --- lib/utils/widgets/tree_view.dart | 3 ++- lib/widgets/events/events_screen.dart | 1 + macos/Flutter/ephemeral/Flutter-Generated.xcconfig | 4 ++-- macos/Flutter/ephemeral/flutter_export_environment.sh | 4 ++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/utils/widgets/tree_view.dart b/lib/utils/widgets/tree_view.dart index d30965ee..1b3c0f1b 100644 --- a/lib/utils/widgets/tree_view.dart +++ b/lib/utils/widgets/tree_view.dart @@ -14,6 +14,7 @@ Widget buildCheckbox({ required bool isError, required String text, required double gapCheckboxText, + IconData offlineIcon = Icons.videocam_off_outlined, String? secondaryText, double checkboxScale = 0.8, FlexFit textFit = FlexFit.loose, @@ -27,7 +28,7 @@ Widget buildCheckbox({ ? Tooltip( message: loc.offline, child: Icon( - Icons.videocam_off_outlined, + offlineIcon, size: 16.0, color: theme.colorScheme.error, ), diff --git a/lib/widgets/events/events_screen.dart b/lib/widgets/events/events_screen.dart index 5f5a4a45..3068de63 100644 --- a/lib/widgets/events/events_screen.dart +++ b/lib/widgets/events/events_screen.dart @@ -427,6 +427,7 @@ class EventsDevicesPicker extends StatelessWidget { secondaryText: isOffline ? null : '${server.devices.length}', gapCheckboxText: gapCheckboxText, textFit: FlexFit.tight, + offlineIcon: Icons.domain_disabled_outlined, ), children: () { if (isOffline) { diff --git a/macos/Flutter/ephemeral/Flutter-Generated.xcconfig b/macos/Flutter/ephemeral/Flutter-Generated.xcconfig index 84016a2e..8c4d5569 100644 --- a/macos/Flutter/ephemeral/Flutter-Generated.xcconfig +++ b/macos/Flutter/ephemeral/Flutter-Generated.xcconfig @@ -1,6 +1,6 @@ // This is a generated file; do not edit or check into version control. -FLUTTER_ROOT=/home/bruno/flutter -FLUTTER_APPLICATION_PATH=/home/bruno/Documents/paid_projects/fork/unity +FLUTTER_ROOT=C:\Users\bruno\Documents\flutter\flutter +FLUTTER_APPLICATION_PATH=C:\Users\bruno\Documents\flutter\paid_projects\unity COCOAPODS_PARALLEL_CODE_SIGN=true FLUTTER_BUILD_DIR=build FLUTTER_BUILD_NAME=3.0.014 diff --git a/macos/Flutter/ephemeral/flutter_export_environment.sh b/macos/Flutter/ephemeral/flutter_export_environment.sh index 2c49bb40..1b196c77 100755 --- a/macos/Flutter/ephemeral/flutter_export_environment.sh +++ b/macos/Flutter/ephemeral/flutter_export_environment.sh @@ -1,7 +1,7 @@ #!/bin/sh # This is a generated file; do not edit or check into version control. -export "FLUTTER_ROOT=/home/bruno/flutter" -export "FLUTTER_APPLICATION_PATH=/home/bruno/Documents/paid_projects/fork/unity" +export "FLUTTER_ROOT=C:\Users\bruno\Documents\flutter\flutter" +export "FLUTTER_APPLICATION_PATH=C:\Users\bruno\Documents\flutter\paid_projects\unity" export "COCOAPODS_PARALLEL_CODE_SIGN=true" export "FLUTTER_BUILD_DIR=build" export "FLUTTER_BUILD_NAME=3.0.014" From 3072bd5d1d60f8f682210fd530b21d069b7cc52b Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Thu, 4 Jan 2024 17:32:00 -0300 Subject: [PATCH 02/11] fix: Update downloads filename --- lib/l10n/app_en.arb | 2 +- lib/providers/downloads_provider.dart | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 6c1da887..ff2256b7 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -253,7 +253,7 @@ } }, "howToDownload": "Go to the \"Events History\" screen to download events.", - "downloadTitle": "{event} on {device} under server {server} at {date}", + "downloadTitle": "{event} on {device} ({server}) at {date}", "@downloadTitle": { "placeholders": { "event": { diff --git a/lib/providers/downloads_provider.dart b/lib/providers/downloads_provider.dart index f4d585d8..0f935485 100644 --- a/lib/providers/downloads_provider.dart +++ b/lib/providers/downloads_provider.dart @@ -209,9 +209,7 @@ class DownloadsManager extends UnityProvider { /// Downloads the given [event] Future download(Event event) async { assert(event.mediaURL != null, 'There must be an url to be downloaded'); - - // safe for release - if (event.mediaURL == null) return; + if (event.mediaURL == null) return; // safe for release final home = HomeProvider.instance ..loading(UnityLoadingReason.downloadEvent); @@ -224,7 +222,8 @@ class DownloadsManager extends UnityProvider { notifyListeners(); final dir = SettingsProvider.instance.downloadsDirectory; - final fileName = 'event_${event.id}${event.deviceID}${event.server.ip}.mp4'; + final fileName = + 'event_${event.id}_${event.deviceID}_${event.server.name}.mp4'; final downloadPath = path.join(dir, fileName); await Dio().downloadUri( From a38e2f96f0b0eaeec1e7e736655b816ba613f6c1 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Thu, 4 Jan 2024 17:37:42 -0300 Subject: [PATCH 03/11] fix: Do not resize macOS window every time --- lib/utils/window.dart | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/utils/window.dart b/lib/utils/window.dart index 95d9d785..30e69eb6 100644 --- a/lib/utils/window.dart +++ b/lib/utils/window.dart @@ -38,15 +38,14 @@ Future configureWindow() async { await windowManager.waitUntilReadyToShow( const WindowOptions( minimumSize: kDebugMode ? Size(100, 100) : kInitialWindowSize, - // minimumSize: kInitialWindowSize, skipTaskbar: false, titleBarStyle: TitleBarStyle.hidden, windowButtonVisibility: true, ), () async { - if ((isDesktopPlatform && Platform.isMacOS) || kDebugMode) { - await windowManager.setSize(kInitialWindowSize); - } + // if ((isDesktopPlatform && Platform.isMacOS) || kDebugMode) { + // await windowManager.setSize(kInitialWindowSize); + // } await windowManager.show(); }, ); From 58958dde398c881869fdf14d4348ce17875364a8 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Thu, 4 Jan 2024 18:00:53 -0300 Subject: [PATCH 04/11] ui: Improve sidebars --- lib/widgets/collapsable_sidebar.dart | 30 ++-- .../desktop/desktop_device_grid.dart | 4 +- lib/widgets/events/events_screen.dart | 28 ++-- lib/widgets/events/events_screen_desktop.dart | 132 +++++++++--------- 4 files changed, 102 insertions(+), 92 deletions(-) diff --git a/lib/widgets/collapsable_sidebar.dart b/lib/widgets/collapsable_sidebar.dart index cad72655..58e52545 100644 --- a/lib/widgets/collapsable_sidebar.dart +++ b/lib/widgets/collapsable_sidebar.dart @@ -21,8 +21,8 @@ import 'package:bluecherry_client/utils/widgets/squared_icon_button.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -const kSidebarConstraints = BoxConstraints(maxWidth: 220.0); -const kCompactSidebarConstraints = BoxConstraints(maxWidth: 46.0); +const kSidebarConstraints = BoxConstraints(maxWidth: 256.0); +const kCompactSidebarConstraints = BoxConstraints(maxWidth: 48.0); typedef SidebarBuilder = Widget Function( BuildContext context, @@ -96,12 +96,21 @@ class _CollapsableSidebarState extends State return AnimatedBuilder( animation: collapseAnimation, builder: (context, child) { + final isTransitioning = collapseAnimation.value > 0.0 && + collapseAnimation.value < 1.0 && + collapseController.status != AnimationStatus.completed && + collapseController.status != AnimationStatus.dismissed; final collapsed = collapseController.isCompleted; - final collapseButton = Padding( + final collapseButton = Container( + alignment: isTransitioning + ? (widget.left + ? AlignmentDirectional.topStart + : AlignmentDirectional.topEnd) + : AlignmentDirectional.center, padding: collapsed ? EdgeInsetsDirectional.zero : widget.left - ? const EdgeInsetsDirectional.only(start: 5.0) + ? const EdgeInsetsDirectional.symmetric(horizontal: 5.0) : const EdgeInsetsDirectional.only(end: 5.0), child: SquaredIconButton( key: collapseButtonKey, @@ -117,9 +126,7 @@ class _CollapsableSidebarState extends State end: 0.5, )) .animate(collapseAnimation), - child: const Icon( - Icons.keyboard_arrow_right, - ), + child: const Icon(Icons.keyboard_arrow_right), ), onPressed: () { if (collapseController.isCompleted) { @@ -139,11 +146,10 @@ class _CollapsableSidebarState extends State child: () { if (collapseAnimation.value > 0.35) { return Container( - alignment: widget.left - ? AlignmentDirectional.topStart - : AlignmentDirectional.topEnd, - padding: const EdgeInsetsDirectional.symmetric(horizontal: 4.0), - child: widget.builder(context, true, collapseButton), + padding: const EdgeInsetsDirectional.symmetric(horizontal: 6.0), + child: Center( + child: widget.builder(context, true, collapseButton), + ), ); } diff --git a/lib/widgets/device_grid/desktop/desktop_device_grid.dart b/lib/widgets/device_grid/desktop/desktop_device_grid.dart index 8e0ac6b0..c72e0bb2 100644 --- a/lib/widgets/device_grid/desktop/desktop_device_grid.dart +++ b/lib/widgets/device_grid/desktop/desktop_device_grid.dart @@ -69,7 +69,7 @@ class _DesktopDeviceGridState extends State { SquaredIconButton( icon: Icon( Icons.cyclone, - size: 18.0, + size: 20.0, color: settings.layoutCyclingEnabled ? theme.colorScheme.primary : IconTheme.of(context).color, @@ -78,7 +78,7 @@ class _DesktopDeviceGridState extends State { onPressed: settings.toggleCycling, ), SquaredIconButton( - icon: const Icon(Icons.camera_outdoor, size: 18.0), + icon: const Icon(Icons.camera_outdoor, size: 20.0), tooltip: loc.addExternalStream, onPressed: () => AddExternalStreamDialog.show(context), ), diff --git a/lib/widgets/events/events_screen.dart b/lib/widgets/events/events_screen.dart index 3068de63..35b202a9 100644 --- a/lib/widgets/events/events_screen.dart +++ b/lib/widgets/events/events_screen.dart @@ -32,6 +32,7 @@ import 'package:bluecherry_client/utils/extensions.dart'; import 'package:bluecherry_client/utils/methods.dart'; import 'package:bluecherry_client/utils/widgets/squared_icon_button.dart'; import 'package:bluecherry_client/utils/widgets/tree_view.dart'; +import 'package:bluecherry_client/widgets/collapsable_sidebar.dart'; import 'package:bluecherry_client/widgets/desktop_buttons.dart'; import 'package:bluecherry_client/widgets/downloads_manager.dart'; import 'package:bluecherry_client/widgets/error_warning.dart'; @@ -200,12 +201,10 @@ class EventsScreenState extends State { ); } - return Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox( - width: 220, - child: Card( - margin: EdgeInsetsDirectional.zero, - shape: const RoundedRectangleBorder(), + return Material( + child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ + ConstrainedBox( + constraints: kSidebarConstraints, child: SafeArea( child: DropdownButtonHideUnderline( child: Column(children: [ @@ -254,10 +253,19 @@ class EventsScreenState extends State { ), ), ), - ), - const VerticalDivider(width: 1), - Expanded(child: EventsScreenDesktop(events: filteredEvents)), - ]); + Expanded( + child: Card( + margin: EdgeInsets.zero, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadiusDirectional.only( + topStart: Radius.circular(12.0), + ), + ), + child: EventsScreenDesktop(events: filteredEvents), + ), + ), + ]), + ); }); } diff --git a/lib/widgets/events/events_screen_desktop.dart b/lib/widgets/events/events_screen_desktop.dart index 8146c699..f6217a57 100644 --- a/lib/widgets/events/events_screen_desktop.dart +++ b/lib/widgets/events/events_screen_desktop.dart @@ -54,75 +54,71 @@ class EventsScreenDesktop extends StatelessWidget { ); } - return Material( - child: SafeArea( - child: CustomScrollView(slivers: [ - SliverPersistentHeader( - delegate: _TableHeader(eventsAmount: events.length), - pinned: true, - ), - SliverFixedExtentList.builder( - itemCount: events.length, - itemExtent: 48.0, - addAutomaticKeepAlives: false, - addRepaintBoundaries: false, - findChildIndexCallback: (key) { - final k = key as ValueKey; - return events.indexed - .firstWhereOrNull((e) => e.$2 == k.value) - ?.$1; - }, - itemBuilder: (context, index) { - final event = events.elementAt(index); - - return InkWell( - key: ValueKey(event), - onTap: event.mediaURL == null - ? null - : () { - debugPrint('Displaying event $event'); - Navigator.of(context).pushNamed( - '/events', - arguments: {'event': event, 'upcoming': events}, - ); - }, - child: Padding( - padding: - const EdgeInsetsDirectional.symmetric(horizontal: 20.0), - child: Row(children: [ - Container( - width: 40.0, - height: 40.0, - alignment: AlignmentDirectional.center, - child: DownloadIndicator(event: event), - ), - _buildTilePart(child: Text(event.server.name), flex: 2), - _buildTilePart(child: Text(event.deviceName)), - _buildTilePart( - child: Text(event.type.locale(context).uppercaseFirst()), - ), - _buildTilePart( - child: Text(event.duration - .humanReadableCompact(context) - .uppercaseFirst()), - ), - _buildTilePart( - child: - Text(event.priority.locale(context).uppercaseFirst()), - ), - _buildTilePart( - child: Text( - '${settings.formatDate(event.updated)} ${settings.formatTime(event.updated).toUpperCase()}', - ), - flex: 2, + return SafeArea( + child: CustomScrollView(slivers: [ + SliverPersistentHeader( + delegate: _TableHeader(eventsAmount: events.length), + pinned: true, + ), + SliverFixedExtentList.builder( + itemCount: events.length, + itemExtent: 48.0, + addAutomaticKeepAlives: false, + addRepaintBoundaries: false, + findChildIndexCallback: (key) { + final k = key as ValueKey; + return events.indexed.firstWhereOrNull((e) => e.$2 == k.value)?.$1; + }, + itemBuilder: (context, index) { + final event = events.elementAt(index); + + return InkWell( + key: ValueKey(event), + onTap: event.mediaURL == null + ? null + : () { + debugPrint('Displaying event $event'); + Navigator.of(context).pushNamed( + '/events', + arguments: {'event': event, 'upcoming': events}, + ); + }, + child: Padding( + padding: + const EdgeInsetsDirectional.symmetric(horizontal: 20.0), + child: Row(children: [ + Container( + width: 40.0, + height: 40.0, + alignment: AlignmentDirectional.center, + child: DownloadIndicator(event: event), + ), + _buildTilePart(child: Text(event.server.name), flex: 2), + _buildTilePart(child: Text(event.deviceName)), + _buildTilePart( + child: Text(event.type.locale(context).uppercaseFirst()), + ), + _buildTilePart( + child: Text(event.duration + .humanReadableCompact(context) + .uppercaseFirst()), + ), + _buildTilePart( + child: + Text(event.priority.locale(context).uppercaseFirst()), + ), + _buildTilePart( + child: Text( + '${settings.formatDate(event.updated)} ${settings.formatTime(event.updated).toUpperCase()}', ), - ]), - ), - ); - }, - ), - ]), - ), + flex: 2, + ), + ]), + ), + ); + }, + ), + ]), ); } } From 69017cb972e8410c45b74b080335af2430752483 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Thu, 4 Jan 2024 18:08:15 -0300 Subject: [PATCH 05/11] fix: Do not limit events when start and end times are provided --- lib/api/events.dart | 4 ++-- lib/widgets/events/events_screen.dart | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/api/events.dart b/lib/api/events.dart index 7783d67e..f01b9088 100644 --- a/lib/api/events.dart +++ b/lib/api/events.dart @@ -32,7 +32,7 @@ extension EventsExtension on API { return compute(_getEvents, { 'server': server, - 'limit': await eventsLimit, + 'limit': (startTime != null && endTime != null) ? -1 : await eventsLimit, 'startTime': startTime, 'endTime': endTime, 'device_id': device?.id, @@ -46,10 +46,10 @@ extension EventsExtension on API { return []; } - final limit = (data['limit'] as int?) ?? -1; var startTime = data['startTime'] as DateTime?; final endTime = data['endTime'] as DateTime?; final deviceId = data['device_id'] as int?; + final limit = (data['limit'] as int?) ?? -1; if (startTime != null && endTime != null) { if (startTime == endTime) { diff --git a/lib/widgets/events/events_screen.dart b/lib/widgets/events/events_screen.dart index 35b202a9..bd76c38b 100644 --- a/lib/widgets/events/events_screen.dart +++ b/lib/widgets/events/events_screen.dart @@ -292,10 +292,15 @@ class EventsScreenState extends State { firstDate: DateTime(1970), lastDate: DateTime.now(), initialEntryMode: DatePickerEntryMode.calendarOnly, + initialDateRange: startTime == null || endTime == null + ? null + : DateTimeRange(start: startTime!, end: endTime!), ); if (range != null) { - startTime = range.start; - endTime = range.end; + setState(() { + startTime = range.start; + endTime = range.end; + }); onSelect?.call(); } }, From 36307d6bb1dee9eaaad471739cd427b37556800f Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Thu, 4 Jan 2024 18:23:13 -0300 Subject: [PATCH 06/11] fix: Do not fetch when open the Events Browser screen --- lib/widgets/events/events_screen.dart | 6 ------ lib/widgets/events/events_screen_desktop.dart | 13 +++++++------ 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/lib/widgets/events/events_screen.dart b/lib/widgets/events/events_screen.dart index bd76c38b..950119ba 100644 --- a/lib/widgets/events/events_screen.dart +++ b/lib/widgets/events/events_screen.dart @@ -78,12 +78,6 @@ class EventsScreenState extends State { ...server.devices.map((d) => d.streamURL) }; - @override - void initState() { - super.initState(); - WidgetsBinding.instance.addPostFrameCallback((_) => fetch()); - } - /// Fetches the events from the servers. Future fetch() async { events.clear(); diff --git a/lib/widgets/events/events_screen_desktop.dart b/lib/widgets/events/events_screen_desktop.dart index f6217a57..87413af1 100644 --- a/lib/widgets/events/events_screen_desktop.dart +++ b/lib/widgets/events/events_screen_desktop.dart @@ -64,7 +64,6 @@ class EventsScreenDesktop extends StatelessWidget { itemCount: events.length, itemExtent: 48.0, addAutomaticKeepAlives: false, - addRepaintBoundaries: false, findChildIndexCallback: (key) { final k = key as ValueKey; return events.indexed.firstWhereOrNull((e) => e.$2 == k.value)?.$1; @@ -137,10 +136,12 @@ class _TableHeader extends SliverPersistentHeaderDelegate { final loc = AppLocalizations.of(context); final theme = Theme.of(context); - return Material( - child: Card( + return Padding( + padding: const EdgeInsets.all(12.0), + child: Material( + borderRadius: BorderRadius.circular(8.0), child: Container( - height: 50, + height: maxExtent, margin: const EdgeInsetsDirectional.symmetric(horizontal: 15.0), child: DefaultTextStyle( style: theme.textTheme.headlineSmall ?? const TextStyle(), @@ -189,10 +190,10 @@ class _TableHeader extends SliverPersistentHeaderDelegate { } @override - double get maxExtent => 50; + double get maxExtent => 62; @override - double get minExtent => 50; + double get minExtent => maxExtent; @override bool shouldRebuild(covariant _TableHeader oldDelegate) { From 441c0cc3e01166fea09fb76d592e814d86925304 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Thu, 4 Jan 2024 21:26:22 -0300 Subject: [PATCH 07/11] chore: Documentation and code clean --- lib/utils/methods.dart | 27 +++++++++--------- lib/widgets/collapsable_sidebar.dart | 7 +++-- .../device_grid/desktop/desktop_sidebar.dart | 2 +- .../device_grid/desktop/layout_manager.dart | 7 ++--- .../desktop/timeline_sidebar.dart | 17 +++++------ .../events_timeline/events_playback.dart | 3 +- lib/widgets/misc.dart | 28 ++++++++++--------- 7 files changed, 44 insertions(+), 47 deletions(-) diff --git a/lib/utils/methods.dart b/lib/utils/methods.dart index a289ef4c..6bb34687 100644 --- a/lib/utils/methods.dart +++ b/lib/utils/methods.dart @@ -32,9 +32,11 @@ class DeviceOrientations { /// Private constructor. DeviceOrientations._(); - Future set( - List orientations, - ) { + /// Maintain a stack of the last set of orientations, to switch back to the + /// most recent one. + final List> _stack = []; + + Future set(List orientations) { _stack.add(orientations); debugPrint(orientations.toString()); return SystemChrome.setPreferredOrientations(orientations); @@ -45,9 +47,6 @@ class DeviceOrientations { debugPrint(_stack.toString()); await SystemChrome.setPreferredOrientations(_stack.last); } - - /// Maintain a stack of the last set of orientations, to switch back to the most recent one. - final List> _stack = []; } /// Wraps [child] in a [Tooltip] if the app meets [condition] @@ -68,7 +67,7 @@ Widget wrapTooltipIf( return child; } -/// Wraps [child] in an [Expanded] if the app meets [condition] +/// Wraps [child] in an [Expanded] if [condition] is true. Widget wrapExpandedIf( bool condition, { required Widget child, @@ -80,14 +79,11 @@ Widget wrapExpandedIf( return child; } -T? showIf(bool condition, {required T child}) { - if (condition) return child; - - return null; -} - /// Returns true if the app is running on a desktop platform. This is useful /// for determining whether to show desktop-specific UI elements. +/// +/// This does not check if the runtime is native or web. Use [isDesktopPlatform] +/// for that instead. bool get isDesktop { return [ TargetPlatform.windows, @@ -105,6 +101,9 @@ bool get isDesktopPlatform { /// Returns true if the app is running on a mobile platform. This is useful /// for determining whether to show mobile-specific UI elements. +/// +/// This does not check if the runtime is native or web. Use [isMobilePlatform] +/// for that instead. bool get isMobile { return [ TargetPlatform.android, @@ -120,7 +119,7 @@ bool get isMobilePlatform { return Platform.isAndroid || Platform.isIOS; } -/// Whether the current platform is iOS or macOS +/// Whether the current platform is iOS or macOS. bool get isCupertino { final cupertinoPlatforms = [TargetPlatform.iOS, TargetPlatform.macOS]; final navigatorContext = navigatorKey.currentContext; diff --git a/lib/widgets/collapsable_sidebar.dart b/lib/widgets/collapsable_sidebar.dart index 58e52545..8a624efb 100644 --- a/lib/widgets/collapsable_sidebar.dart +++ b/lib/widgets/collapsable_sidebar.dart @@ -106,7 +106,7 @@ class _CollapsableSidebarState extends State ? (widget.left ? AlignmentDirectional.topStart : AlignmentDirectional.topEnd) - : AlignmentDirectional.center, + : AlignmentDirectional.topCenter, padding: collapsed ? EdgeInsetsDirectional.zero : widget.left @@ -145,9 +145,10 @@ class _CollapsableSidebarState extends State ).evaluate(collapseAnimation), child: () { if (collapseAnimation.value > 0.35) { - return Container( + return Padding( padding: const EdgeInsetsDirectional.symmetric(horizontal: 6.0), - child: Center( + child: Align( + alignment: AlignmentDirectional.topCenter, child: widget.builder(context, true, collapseButton), ), ); diff --git a/lib/widgets/device_grid/desktop/desktop_sidebar.dart b/lib/widgets/device_grid/desktop/desktop_sidebar.dart index 84f9a955..fe2df170 100644 --- a/lib/widgets/device_grid/desktop/desktop_sidebar.dart +++ b/lib/widgets/device_grid/desktop/desktop_sidebar.dart @@ -393,7 +393,7 @@ class _DesktopDeviceSelectorTileState extends State { minWidth: size.width, ), items: [ - PopupLabel( + PopupMenuLabel( label: Padding( padding: padding .add(const EdgeInsetsDirectional.symmetric(vertical: 6.0)), diff --git a/lib/widgets/device_grid/desktop/layout_manager.dart b/lib/widgets/device_grid/desktop/layout_manager.dart index 039ce76a..cd0ecdc1 100644 --- a/lib/widgets/device_grid/desktop/layout_manager.dart +++ b/lib/widgets/device_grid/desktop/layout_manager.dart @@ -37,10 +37,7 @@ import 'package:provider/provider.dart'; class LayoutManager extends StatefulWidget { final Widget collapseButton; - const LayoutManager({ - super.key, - required this.collapseButton, - }); + const LayoutManager({super.key, required this.collapseButton}); @override State createState() => _LayoutManagerState(); @@ -273,7 +270,7 @@ class _LayoutTileState extends State { minWidth: size.width, ), items: [ - PopupLabel( + PopupMenuLabel( label: Padding( padding: padding.add(const EdgeInsets.symmetric(vertical: 6.0)), child: Text( diff --git a/lib/widgets/events_timeline/desktop/timeline_sidebar.dart b/lib/widgets/events_timeline/desktop/timeline_sidebar.dart index 280546fd..725727f9 100644 --- a/lib/widgets/events_timeline/desktop/timeline_sidebar.dart +++ b/lib/widgets/events_timeline/desktop/timeline_sidebar.dart @@ -50,18 +50,15 @@ class TimelineSidebar extends StatelessWidget { top: Radius.circular(12.0), ), ), - margin: const EdgeInsetsDirectional.only(end: 4.0), + margin: const EdgeInsetsDirectional.only(end: 4.0, top: 4.0, start: 4.0), child: CollapsableSidebar( + left: false, builder: (context, collapsed, collapseButton) { - if (collapsed) { - return Padding( - padding: const EdgeInsetsDirectional.only(top: 4.0), - child: Align( - alignment: AlignmentDirectional.topEnd, - child: collapseButton, - ), - ); - } + collapseButton = Padding( + padding: const EdgeInsetsDirectional.only(top: 4.0), + child: collapseButton, + ); + if (collapsed) return collapseButton; return Column(children: [ SubHeader( diff --git a/lib/widgets/events_timeline/events_playback.dart b/lib/widgets/events_timeline/events_playback.dart index 4ce65616..bd1490e1 100644 --- a/lib/widgets/events_timeline/events_playback.dart +++ b/lib/widgets/events_timeline/events_playback.dart @@ -23,6 +23,7 @@ import 'package:bluecherry_client/models/device.dart'; import 'package:bluecherry_client/models/event.dart'; import 'package:bluecherry_client/providers/downloads_provider.dart'; import 'package:bluecherry_client/utils/extensions.dart'; +import 'package:bluecherry_client/utils/methods.dart'; import 'package:bluecherry_client/widgets/events/events_screen.dart'; import 'package:bluecherry_client/widgets/events_timeline/desktop/timeline.dart'; import 'package:bluecherry_client/widgets/events_timeline/desktop/timeline_sidebar.dart'; @@ -225,7 +226,7 @@ extension DevicesMapExtension on MapEntry> { final mediaUrl = downloads.isEventDownloaded(event.id) ? Uri.file( downloads.getDownloadedPathForEvent(event.id), - windows: Platform.isWindows, + windows: isDesktopPlatform && Platform.isWindows, ).toString() : event.mediaURL!.toString(); diff --git a/lib/widgets/misc.dart b/lib/widgets/misc.dart index 8819df2e..85cf6315 100644 --- a/lib/widgets/misc.dart +++ b/lib/widgets/misc.dart @@ -96,7 +96,6 @@ class GestureDetectorWithReducedDoubleTapTime extends StatelessWidget { } } -// I'm tired of buggy implementation in class CorrectedListTile extends StatelessWidget { final VoidCallback? onTap; final IconData iconData; @@ -250,6 +249,11 @@ class SubHeader extends StatelessWidget { } } +/// A helper function that returns a [UnityDrawerButton] if the parent +/// [Scaffold] has a drawer. +/// +/// This is useful for when you want to display a drawer button in the appbar +/// but only if the parent [Scaffold] has a drawer. // ignore: non_constant_identifier_names Widget? MaybeUnityDrawerButton( BuildContext context, { @@ -347,17 +351,15 @@ List outlinedText({ return result.toList(); } -List outlinedIcon() => outlinedText( - strokeWidth: 0.75, - strokeColor: Colors.black.withOpacity(0.75), - ); +List outlinedIcon() { + return outlinedText( + strokeWidth: 0.75, + strokeColor: Colors.black.withOpacity(0.75), + ); +} -class PopupLabel extends PopupMenuEntry { - const PopupLabel({ - super.key, - required this.label, - this.height = 42.0, - }); +class PopupMenuLabel extends PopupMenuEntry { + const PopupMenuLabel({super.key, required this.label, this.height = 42.0}); @override final double height; @@ -368,10 +370,10 @@ class PopupLabel extends PopupMenuEntry { bool represents(void value) => false; @override - State createState() => _PopupLabelState(); + State createState() => _PopupMenuLabelState(); } -class _PopupLabelState extends State { +class _PopupMenuLabelState extends State { @override Widget build(BuildContext context) => widget.label; } From 579e88cfb6e501906201e455b905c250ecb10a5b Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Thu, 4 Jan 2024 21:44:44 -0300 Subject: [PATCH 08/11] ui: Center Filter button with the sidebar --- .../events_timeline/desktop/timeline.dart | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/widgets/events_timeline/desktop/timeline.dart b/lib/widgets/events_timeline/desktop/timeline.dart index 9b5692e3..39e809ab 100644 --- a/lib/widgets/events_timeline/desktop/timeline.dart +++ b/lib/widgets/events_timeline/desktop/timeline.dart @@ -28,6 +28,7 @@ import 'package:bluecherry_client/utils/constants.dart'; import 'package:bluecherry_client/utils/extensions.dart'; import 'package:bluecherry_client/utils/methods.dart'; import 'package:bluecherry_client/utils/widgets/squared_icon_button.dart'; +import 'package:bluecherry_client/widgets/collapsable_sidebar.dart'; import 'package:bluecherry_client/widgets/device_grid/device_grid.dart' show calculateCrossAxisCount; import 'package:bluecherry_client/widgets/events/events_screen.dart'; @@ -605,14 +606,20 @@ class _TimelineEventsViewState extends State { }()), const Spacer(), Expanded( - child: Center( - child: FilledButton( - onPressed: home.isLoadingFor( - UnityLoadingReason.fetchingEventsHistory, - ) - ? null - : widget.onFetch, - child: Text(loc.filter), + child: Align( + alignment: AlignmentDirectional.centerEnd, + child: SizedBox( + width: kSidebarConstraints.maxWidth, + child: Center( + child: FilledButton( + onPressed: home.isLoadingFor( + UnityLoadingReason.fetchingEventsHistory, + ) + ? null + : widget.onFetch, + child: Text(loc.filter), + ), + ), ), ), ), From 2882ad85a94c200bf8aab4609538b14214916cde Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Thu, 4 Jan 2024 21:48:26 -0300 Subject: [PATCH 09/11] chore: Use TextButton.icon --- lib/widgets/downloads_manager.dart | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/lib/widgets/downloads_manager.dart b/lib/widgets/downloads_manager.dart index 45261347..d5539ec3 100644 --- a/lib/widgets/downloads_manager.dart +++ b/lib/widgets/downloads_manager.dart @@ -299,7 +299,7 @@ class _DownloadTileState extends State { ]), ), ), - TextButton( + TextButton.icon( onPressed: isDownloaded ? () { context @@ -307,14 +307,11 @@ class _DownloadTileState extends State { .delete(widget.downloadPath!); } : null, - child: Row(children: [ - const Icon(Icons.delete, size: 20.0), - const SizedBox(width: 8.0), - Text(loc.delete), - ]), + icon: const Icon(Icons.delete, size: 20.0), + label: Text(loc.delete), ), if (isDesktop) - TextButton( + TextButton.icon( onPressed: isDownloaded ? () { launchFileExplorer( @@ -322,11 +319,8 @@ class _DownloadTileState extends State { ); } : null, - child: Row(children: [ - const Icon(Icons.folder, size: 20.0), - const SizedBox(width: 8.0), - Text(loc.showInFiles), // show in explorer - ]), + icon: const Icon(Icons.folder, size: 20.0), + label: Text(loc.showInFiles), ), ], ), From b7f2152da276c0ccc67dfda0d140a9d43f4c1e4d Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Fri, 5 Jan 2024 15:45:17 -0300 Subject: [PATCH 10/11] feat: Developer settings --- lib/utils/logging.dart | 13 ++++++--- lib/widgets/settings/desktop/updates.dart | 34 +++++++++++++++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/lib/utils/logging.dart b/lib/utils/logging.dart index 092f7fc0..f8c06f56 100644 --- a/lib/utils/logging.dart +++ b/lib/utils/logging.dart @@ -20,12 +20,18 @@ void handleError(dynamic error, dynamic stackTrace) { writeErrorToFile(error, stackTrace); } +Future getLogFile() async { + final dir = await getApplicationSupportDirectory(); + final file = File(path.join(dir.path, 'logs.txt')); + + return file; +} + Future writeErrorToFile(dynamic error, dynamic stackTrace) async { final time = DateTime.now().toIso8601String(); final errorLog = '\n[$time]Error: $error\n[$time]Stack trace: $stackTrace'; - final dir = await getApplicationSupportDirectory(); - final file = File(path.join(dir.path, 'logs.txt')); + final file = await getLogFile(); await file.writeAsString(errorLog, mode: FileMode.append); Logger.root.log(Level.INFO, 'Wrote log file to ${file.path}'); @@ -33,8 +39,7 @@ Future writeErrorToFile(dynamic error, dynamic stackTrace) async { Future writeLogToFile(String text) async { final time = DateTime.now().toIso8601String(); - final dir = await getApplicationSupportDirectory(); - final file = File(path.join(dir.path, 'logs.txt')); + final file = await getLogFile(); await file.writeAsString('\n[$time] $text', mode: FileMode.append); Logger.root.log(Level.INFO, 'Wrote log file to ${file.path}'); diff --git a/lib/widgets/settings/desktop/updates.dart b/lib/widgets/settings/desktop/updates.dart index 9758dc62..f32b4181 100644 --- a/lib/widgets/settings/desktop/updates.dart +++ b/lib/widgets/settings/desktop/updates.dart @@ -21,6 +21,8 @@ import 'dart:io'; import 'package:bluecherry_client/providers/settings_provider.dart'; import 'package:bluecherry_client/providers/update_provider.dart'; +import 'package:bluecherry_client/utils/logging.dart'; +import 'package:bluecherry_client/utils/window.dart'; import 'package:bluecherry_client/widgets/settings/desktop/settings.dart'; import 'package:bluecherry_client/widgets/settings/shared/update.dart'; import 'package:flutter/foundation.dart'; @@ -97,6 +99,38 @@ class BetaFeatures extends StatelessWidget { } }, ), + ExpansionTile( + leading: CircleAvatar( + backgroundColor: Colors.transparent, + foregroundColor: theme.iconTheme.color, + child: const Icon(Icons.developer_mode), + ), + title: const Text('Developer options'), + subtitle: + const Text('Most of these options are for debugging purposes'), + children: [ + FutureBuilder( + future: getLogFile(), + builder: (context, snapshot) { + return ListTile( + contentPadding: const EdgeInsetsDirectional.only( + start: 68.0, + end: 26.0, + ), + leading: const Icon(Icons.bug_report), + title: const Text('Open log file'), + subtitle: + snapshot.data != null ? Text(snapshot.data!.path) : null, + trailing: const Icon(Icons.navigate_next), + dense: false, + onTap: () { + launchFileExplorer(snapshot.data!.path); + }, + ); + }, + ), + ], + ), ]); } } From 80b365b46163515c1c395d45281b337521b4e94f Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Fri, 5 Jan 2024 16:35:04 -0300 Subject: [PATCH 11/11] ci: Build with stable channel --- .github/workflows/build.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 44576516..d8ab9e3b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,7 +20,7 @@ jobs: submodules: recursive - uses: subosito/flutter-action@v2.8.0 with: - channel: "master" + channel: "stable" # cache: true # TODO: Signing Android application. # - name: Create Key Store @@ -81,7 +81,7 @@ jobs: submodules: recursive - uses: subosito/flutter-action@v2.8.0 with: - channel: "master" + channel: "stable" architecture: x64 # cache: true @@ -123,7 +123,7 @@ jobs: submodules: recursive - uses: subosito/flutter-action@v2.8.0 with: - channel: "master" + channel: "stable" architecture: x64 # cache: true @@ -164,7 +164,7 @@ jobs: submodules: recursive - uses: subosito/flutter-action@v2.8.0 with: - channel: "master" + channel: "stable" # cache: true - run: git config --system core.longpaths true - run: flutter gen-l10n @@ -218,7 +218,7 @@ jobs: - name: Install Flutter uses: subosito/flutter-action@v2.8.0 with: - channel: "master" + channel: "stable" # cache: true - name: Initiate Flutter