From 5e25192cff0a2c4681cf9ef0de1450aaa2e3a027 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Sat, 4 May 2024 20:03:56 -0300 Subject: [PATCH 01/10] fix: Do not throw error if the device key is duplicated. This is specially true when there are devices that are not configured - which fallback to the -1 id. --- lib/screens/events_browser/filter.dart | 1 + lib/widgets/tree_view.dart | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/screens/events_browser/filter.dart b/lib/screens/events_browser/filter.dart index 47aaded2..b7b3ab1f 100644 --- a/lib/screens/events_browser/filter.dart +++ b/lib/screens/events_browser/filter.dart @@ -128,6 +128,7 @@ class _EventsDevicesPickerState extends State { return []; } else { return server.devices + .toSet() .sorted(searchQuery: widget.searchQuery) .map((device) { final enabled = isOffline diff --git a/lib/widgets/tree_view.dart b/lib/widgets/tree_view.dart index 1b3c0f1b..d5802101 100644 --- a/lib/widgets/tree_view.dart +++ b/lib/widgets/tree_view.dart @@ -184,10 +184,10 @@ class KeyProvider { if (originalKey == null) { return _TreeNodeKey(_nextIndex++); } - if (_keys.contains(originalKey)) { - throw ArgumentError('There should not be nodes with the same kays. ' - 'Duplicate value found: $originalKey.'); - } + // if (_keys.contains(originalKey)) { + // throw ArgumentError('There should not be nodes with the same keys. ' + // 'Duplicate value found: $originalKey.'); + // } _keys.add(originalKey); return originalKey; } From fb1a69aee7b14b041bd54599566416a1dfeb5025 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Sat, 4 May 2024 20:07:34 -0300 Subject: [PATCH 02/10] fix: Do not list offline devices on Events tree view when kListOfflineDevices is not enabled. --- lib/screens/events_browser/filter.dart | 7 ++++++- lib/utils/extensions.dart | 7 +++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/screens/events_browser/filter.dart b/lib/screens/events_browser/filter.dart index b7b3ab1f..abc8d18f 100644 --- a/lib/screens/events_browser/filter.dart +++ b/lib/screens/events_browser/filter.dart @@ -19,6 +19,7 @@ import 'package:bluecherry_client/providers/events_provider.dart'; import 'package:bluecherry_client/providers/server_provider.dart'; +import 'package:bluecherry_client/providers/settings_provider.dart'; import 'package:bluecherry_client/utils/extensions.dart'; import 'package:bluecherry_client/widgets/misc.dart'; import 'package:bluecherry_client/widgets/search.dart'; @@ -72,6 +73,7 @@ class _EventsDevicesPickerState extends State { Widget build(BuildContext context) { final serversProvider = context.watch(); final eventsProvider = context.watch(); + final settings = context.watch(); return SingleChildScrollView( child: TreeView( @@ -129,7 +131,10 @@ class _EventsDevicesPickerState extends State { } else { return server.devices .toSet() - .sorted(searchQuery: widget.searchQuery) + .sorted( + searchQuery: widget.searchQuery, + onlyEnabled: !settings.kListOfflineDevices.value, + ) .map((device) { final enabled = isOffline ? false diff --git a/lib/utils/extensions.dart b/lib/utils/extensions.dart index 4c331974..a981708b 100644 --- a/lib/utils/extensions.dart +++ b/lib/utils/extensions.dart @@ -152,9 +152,12 @@ extension DeviceListExtension on Iterable { List sorted({ Iterable? available, String searchQuery = '', + bool onlyEnabled = false, }) { - final list = where((device) => - device.name.toLowerCase().contains(searchQuery.toLowerCase())).toList() + final list = where((device) { + if (onlyEnabled && !device.status) return false; + return device.name.toLowerCase().contains(searchQuery.toLowerCase()); + }).toList() ..sort((a, b) => a.name.compareTo(b.name)); if (available != null) list.sort((a, b) => available.contains(a) ? 0 : 1); From 749dc8342c073e6016e53514ae187186e34bb3d8 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Sat, 4 May 2024 20:16:14 -0300 Subject: [PATCH 03/10] fix: Initialize settings on other threads Settings values are used when downloading events. The SettingsProvider must be initialized in order to make them accessible. --- lib/api/api_helpers.dart | 3 ++- lib/providers/settings_provider.dart | 10 ++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/api/api_helpers.dart b/lib/api/api_helpers.dart index 3076af1c..e39ba9f4 100644 --- a/lib/api/api_helpers.dart +++ b/lib/api/api_helpers.dart @@ -147,8 +147,9 @@ class DevHttpOverrides extends HttpOverrides { /// See also: /// * /// * [compute], used to compute data in another thread - static void configureCertificates() { + static Future configureCertificates() async { ServersProvider.instance = ServersProvider.dump(); + SettingsProvider.ensureInitialized(); HttpOverrides.global = DevHttpOverrides(); } diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index 1ef08eb1..34e52fa2 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -275,8 +275,14 @@ class SettingsProvider extends UnityProvider { ); final kDownloadsDirectory = _SettingsOption( def: '', - getDefault: () async => - (await DownloadsManager.kDefaultDownloadsDirectory).path, + getDefault: () async { + try { + return (await DownloadsManager.kDefaultDownloadsDirectory).path; + } catch (e) { + debugPrint('Error getting default downloads directory: $e'); + return ''; + } + }, key: 'downloads.directory', ); From d9a62c811fcf9335f3cf0dce138f292c44ded06b Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Sat, 4 May 2024 20:25:19 -0300 Subject: [PATCH 04/10] fix: Correctly parse and format date times over positive timezone offsets (e.g. +02:00) --- lib/utils/date.dart | 24 ++++++++++++++++++++---- lib/utils/logging.dart | 3 ++- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/lib/utils/date.dart b/lib/utils/date.dart index 068fe8a0..08ddb4d3 100644 --- a/lib/utils/date.dart +++ b/lib/utils/date.dart @@ -1,5 +1,5 @@ import 'package:bluecherry_client/providers/settings_provider.dart'; -import 'package:flutter/foundation.dart'; +import 'package:bluecherry_client/utils/logging.dart'; import 'package:intl/intl.dart'; /// Convert a date string to a DateTime object, considering the timezone offset. @@ -7,7 +7,17 @@ DateTime timezoneAwareDate(String originalDateString) { final originalDateTime = DateTime.parse(originalDateString); try { - final offsetString = originalDateString.split('-').last; + // Get the offset sign and factors from the date string. + // + // If the timezone is positive (e.g. +02:00), the offset sign is positive. + // Otherwise (e.g. -02:00), the offset sign is negative. + var offsetSign = 1.0; + var offsetFactors = originalDateString.split('+'); + if (offsetFactors.isEmpty) { + offsetFactors = originalDateString.split('-'); + offsetSign = -1.0; + } + final offsetString = offsetFactors.last; final parts = offsetString.split(':'); // Convert hours and minutes strings to integers @@ -15,11 +25,17 @@ DateTime timezoneAwareDate(String originalDateString) { final minutes = int.parse(parts[1]); // Create a Duration object based on the offset sign - final offset = Duration(hours: -hours, minutes: -minutes); + final offset = Duration( + hours: (hours * offsetSign).toInt(), + minutes: (minutes * offsetSign).toInt(), + ); return originalDateTime.add(offset); } catch (e) { - debugPrint('Failed to parse date string: $originalDateString'); + writeLogToFile( + 'Failed to parse date string: $originalDateString', + print: true, + ); return originalDateTime; } } diff --git a/lib/utils/logging.dart b/lib/utils/logging.dart index 197c61f9..602ad4fd 100644 --- a/lib/utils/logging.dart +++ b/lib/utils/logging.dart @@ -58,7 +58,7 @@ Future writeErrorToFile(dynamic error, dynamic stackTrace) async { Logger.root.log(Level.INFO, 'Wrote log file to ${file.path}'); } -Future writeLogToFile(String text) async { +Future writeLogToFile(String text, {bool print = false}) async { if (kIsWeb) return; final time = DateTime.now().toIso8601String(); @@ -66,4 +66,5 @@ Future writeLogToFile(String text) async { await file.writeAsString('\n[$time] $text', mode: FileMode.append); Logger.root.log(Level.INFO, 'Wrote log file to ${file.path}'); + if (print) debugPrint(text); } From 2e09ef253d2cd6977b61a524b6dd335b380093ef Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Sat, 4 May 2024 20:42:05 -0300 Subject: [PATCH 05/10] fix: Ensure downloaded video file exists If not, fallback to the event url and network streaming. --- lib/screens/players/event_player_desktop.dart | 20 ++++++++++++------- lib/utils/date.dart | 1 + 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/lib/screens/players/event_player_desktop.dart b/lib/screens/players/event_player_desktop.dart index 2997b512..4abdad96 100644 --- a/lib/screens/players/event_player_desktop.dart +++ b/lib/screens/players/event_player_desktop.dart @@ -80,10 +80,10 @@ class _EventPlayerDesktopState extends State { bool shouldAutoplay = false; Duration get duration { - if (widget.event.duration > videoController.duration) { - return widget.event.duration; + if (widget.event.duration < videoController.duration) { + return videoController.duration; } - return videoController.duration; + return widget.event.duration; } Device? get device => currentEvent.server.devices.firstWhereOrNull( @@ -131,10 +131,16 @@ class _EventPlayerDesktopState extends State { final downloads = context.read(); final mediaUrl = downloads.isEventDownloaded(event.id) - ? Uri.file( - downloads.getDownloadedPathForEvent(event.id), - windows: Platform.isWindows, - ).toString() + ? () { + if (File(downloads.getDownloadedPathForEvent(event.id)) + .existsSync()) { + return Uri.file( + downloads.getDownloadedPathForEvent(event.id), + windows: Platform.isWindows, + ).toString(); + } + return event.mediaURL.toString(); + }() : event.mediaURL.toString(); debugPrint(mediaUrl); diff --git a/lib/utils/date.dart b/lib/utils/date.dart index 08ddb4d3..6178e096 100644 --- a/lib/utils/date.dart +++ b/lib/utils/date.dart @@ -15,6 +15,7 @@ DateTime timezoneAwareDate(String originalDateString) { var offsetFactors = originalDateString.split('+'); if (offsetFactors.isEmpty) { offsetFactors = originalDateString.split('-'); + if (offsetFactors.length <= 2) return originalDateTime; offsetSign = -1.0; } final offsetString = offsetFactors.last; From 5c9ca581b364d8673f3e90e95e39fa56943dcf81 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Sun, 5 May 2024 15:04:56 -0300 Subject: [PATCH 06/10] feat: Download events with missing files on app start --- lib/providers/downloads_provider.dart | 84 +++++++++++++------ lib/providers/home_provider.dart | 7 +- lib/screens/downloads/downloads_manager.dart | 2 +- lib/screens/players/event_player_desktop.dart | 7 +- lib/utils/logging.dart | 4 +- 5 files changed, 65 insertions(+), 39 deletions(-) diff --git a/lib/providers/downloads_provider.dart b/lib/providers/downloads_provider.dart index eadc6dd4..a97d6207 100644 --- a/lib/providers/downloads_provider.dart +++ b/lib/providers/downloads_provider.dart @@ -130,7 +130,7 @@ class DownloadsManager extends UnityProvider { } /// All the downloaded events - List downloadedEvents = []; + Set downloadedEvents = {}; /// The events that are downloading Map downloading = {}; @@ -145,6 +145,11 @@ class DownloadsManager extends UnityProvider { downloadsCompleter = null; } + downloading.removeWhere((key, value) { + final progress = value.$1; + return progress == 1.0; + }); + // // setProgressBar is only available on Windows and macOS // if (isDesktopPlatform && !Platform.isLinux) { // if (downloading.isEmpty) { @@ -162,7 +167,22 @@ class DownloadsManager extends UnityProvider { }); await tryReadStorage( - () => super.initializeStorage(downloads, kStorageDownloads)); + () => super.initializeStorage(downloads, kStorageDownloads), + ); + + for (final de in downloadedEvents) { + doesEventFileExist(de.event.id).then((exist) async { + if (!exist) { + debugPrint( + 'Event file does not exist: ${de.event.id}. Redownloading'); + final downloadPath = await _downloadEventFile( + de.event, + de.downloadPath, + ); + downloadedEvents.add(de.copyWith(downloadPath: downloadPath)); + } + }); + } } @override @@ -184,13 +204,13 @@ class DownloadsManager extends UnityProvider { final data = await tryReadStorage(() => downloads.read()); downloadedEvents = data[kStorageDownloads] == null - ? [] + ? {} : ((await compute(jsonDecode, data[kStorageDownloads] as String) ?? []) as List) .cast() .map((item) { return DownloadedEvent.fromJson(item.cast()); - }).toList(); + }).toSet(); super.restore(notifyListeners: notifyListeners); } @@ -205,6 +225,11 @@ class DownloadsManager extends UnityProvider { return downloadedEvents.any((de) => de.event.id == eventId); } + Future doesEventFileExist(int eventId) async { + final downloadPath = getDownloadedPathForEvent(eventId); + return File(downloadPath).exists(); + } + String getDownloadedPathForEvent(int eventId) { assert(isEventDownloaded(eventId)); return downloadedEvents @@ -222,16 +247,6 @@ class DownloadsManager extends UnityProvider { assert(event.mediaURL != null, 'There must be an url to be downloaded'); if (event.mediaURL == null) return; // safe for release - final home = HomeProvider.instance - ..loading(UnityLoadingReason.downloadEvent); - - if (downloadsCompleter == null || downloadsCompleter!.isCompleted) { - downloadsCompleter = Completer(); - } - - downloading[event] = (0.0, ''); - notifyListeners(); - final dir = await () async { final settings = SettingsProvider.instance; @@ -249,10 +264,36 @@ class DownloadsManager extends UnityProvider { } return settings.kDownloadsDirectory.value; }(); + final downloadPath = await _downloadEventFile(event, dir); + + downloading.remove(event); + downloadedEvents + ..removeWhere((downloadedEvent) => downloadedEvent.event.id == event.id) + ..add(DownloadedEvent(event: event, downloadPath: downloadPath)); + + if (downloading.isEmpty) { + downloadsCompleter?.complete(); + } + + await save(); + } + + /// Downloads the given [event] and returns the path of the downloaded file + Future _downloadEventFile(Event event, String dir) async { + debugPrint('Downloading event: $event'); + final home = HomeProvider.instance + ..loading(UnityLoadingReason.downloadEvent); + + if (downloadsCompleter == null || downloadsCompleter!.isCompleted) { + downloadsCompleter = Completer(); + } + + downloading[event] = (0.0, ''); + notifyListeners(); + final fileName = 'event_${event.id}_${event.deviceID}_${event.server.name}.mp4'; final downloadPath = path.join(dir, fileName); - await Dio().downloadUri( event.mediaURL!, downloadPath, @@ -267,18 +308,9 @@ class DownloadsManager extends UnityProvider { }, ); - downloading.remove(event); - downloadedEvents.add(DownloadedEvent( - event: event, - downloadPath: downloadPath, - )); - - if (downloading.isEmpty) { - downloadsCompleter?.complete(); - } - home.notLoading(UnityLoadingReason.downloadEvent); - await save(); + + return downloadPath; } /// Deletes any downloaded events at the given [downloadPath] diff --git a/lib/providers/home_provider.dart b/lib/providers/home_provider.dart index 949ee1d1..f0f7a8ef 100644 --- a/lib/providers/home_provider.dart +++ b/lib/providers/home_provider.dart @@ -82,12 +82,7 @@ enum UnityLoadingReason { } class HomeProvider extends ChangeNotifier { - static late HomeProvider _instance; - - HomeProvider() { - _instance = this; - } - + static final _instance = HomeProvider(); static HomeProvider get instance => _instance; UnityTab tab = ServersProvider.instance.hasServers diff --git a/lib/screens/downloads/downloads_manager.dart b/lib/screens/downloads/downloads_manager.dart index f7b762b7..ea02e875 100644 --- a/lib/screens/downloads/downloads_manager.dart +++ b/lib/screens/downloads/downloads_manager.dart @@ -88,7 +88,7 @@ class DownloadsManagerScreen extends StatelessWidget { SliverList.builder( itemCount: downloads.downloadedEvents.length, itemBuilder: (context, index) { - final de = downloads.downloadedEvents[index]; + final de = downloads.downloadedEvents.elementAt(index); return DownloadTile( key: ValueKey(de.event.id), diff --git a/lib/screens/players/event_player_desktop.dart b/lib/screens/players/event_player_desktop.dart index 4abdad96..49fa329c 100644 --- a/lib/screens/players/event_player_desktop.dart +++ b/lib/screens/players/event_player_desktop.dart @@ -126,14 +126,13 @@ class _EventPlayerDesktopState extends State { super.dispose(); } - void setEvent(Event event) { + Future setEvent(Event event) async { currentEvent = event; final downloads = context.read(); final mediaUrl = downloads.isEventDownloaded(event.id) - ? () { - if (File(downloads.getDownloadedPathForEvent(event.id)) - .existsSync()) { + ? await () async { + if (await downloads.doesEventFileExist(event.id)) { return Uri.file( downloads.getDownloadedPathForEvent(event.id), windows: Platform.isWindows, diff --git a/lib/utils/logging.dart b/lib/utils/logging.dart index 602ad4fd..bd9be05a 100644 --- a/lib/utils/logging.dart +++ b/lib/utils/logging.dart @@ -55,7 +55,7 @@ Future writeErrorToFile(dynamic error, dynamic stackTrace) async { final file = await getLogFile(); await file.writeAsString(errorLog, mode: FileMode.append); - Logger.root.log(Level.INFO, 'Wrote log file to ${file.path}'); + Logger.root.log(Level.INFO, 'Wrote log file to "${file.path}"'); } Future writeLogToFile(String text, {bool print = false}) async { @@ -65,6 +65,6 @@ Future writeLogToFile(String text, {bool print = false}) async { final file = await getLogFile(); await file.writeAsString('\n[$time] $text', mode: FileMode.append); - Logger.root.log(Level.INFO, 'Wrote log file to ${file.path}'); + Logger.root.log(Level.INFO, 'Wrote log file to "${file.path}"'); if (print) debugPrint(text); } From 7dfcbd6b97ca8511f66117e84377e616221c0327 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Sun, 5 May 2024 15:07:43 -0300 Subject: [PATCH 07/10] fix: Only log date parsing errors once in a app lifecycle --- lib/utils/date.dart | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/utils/date.dart b/lib/utils/date.dart index 6178e096..da736481 100644 --- a/lib/utils/date.dart +++ b/lib/utils/date.dart @@ -2,6 +2,8 @@ import 'package:bluecherry_client/providers/settings_provider.dart'; import 'package:bluecherry_client/utils/logging.dart'; import 'package:intl/intl.dart'; +Set _loggedErrorredDates = {}; + /// Convert a date string to a DateTime object, considering the timezone offset. DateTime timezoneAwareDate(String originalDateString) { final originalDateTime = DateTime.parse(originalDateString); @@ -33,10 +35,13 @@ DateTime timezoneAwareDate(String originalDateString) { return originalDateTime.add(offset); } catch (e) { - writeLogToFile( - 'Failed to parse date string: $originalDateString', - print: true, - ); + if (!_loggedErrorredDates.contains(originalDateString)) { + writeLogToFile( + 'Failed to parse date string: $originalDateString', + print: true, + ); + _loggedErrorredDates.add(originalDateString); + } return originalDateTime; } } From d6ed333dea14bd993a304a1b655e582a4c56a6a4 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Sun, 5 May 2024 15:20:35 -0300 Subject: [PATCH 08/10] feat: Option to cancel ongoing downloads --- lib/providers/downloads_provider.dart | 44 +++++++++++++------- lib/screens/downloads/downloads_manager.dart | 31 +++++++++----- 2 files changed, 50 insertions(+), 25 deletions(-) diff --git a/lib/providers/downloads_provider.dart b/lib/providers/downloads_provider.dart index a97d6207..4e0ef419 100644 --- a/lib/providers/downloads_provider.dart +++ b/lib/providers/downloads_provider.dart @@ -26,6 +26,7 @@ import 'package:bluecherry_client/providers/app_provider_interface.dart'; import 'package:bluecherry_client/providers/home_provider.dart'; import 'package:bluecherry_client/providers/settings_provider.dart'; import 'package:bluecherry_client/utils/constants.dart'; +import 'package:bluecherry_client/utils/extensions.dart'; import 'package:bluecherry_client/utils/logging.dart'; import 'package:bluecherry_client/utils/storage.dart'; import 'package:dio/dio.dart'; @@ -177,7 +178,7 @@ class DownloadsManager extends UnityProvider { 'Event file does not exist: ${de.event.id}. Redownloading'); final downloadPath = await _downloadEventFile( de.event, - de.downloadPath, + path.dirname(de.downloadPath), ); downloadedEvents.add(de.copyWith(downloadPath: downloadPath)); } @@ -280,7 +281,13 @@ class DownloadsManager extends UnityProvider { /// Downloads the given [event] and returns the path of the downloaded file Future _downloadEventFile(Event event, String dir) async { - debugPrint('Downloading event: $event'); + if (downloading.entries.any((de) => de.key.id == event.id)) { + return downloading.entries + .firstWhere((de) => de.key.id == event.id) + .value + .$2; + } + debugPrint('Downloading event: $event to $dir'); final home = HomeProvider.instance ..loading(UnityLoadingReason.downloadEvent); @@ -323,19 +330,28 @@ class DownloadsManager extends UnityProvider { if (await file.exists()) await file.delete(); } + void cancelEventDownload(Event target) { + final event = downloading.keys.firstWhereOrNull((downloadingEvent) { + return downloadingEvent.id == target.id; + }); + + try { + final file = File(downloading[event]!.$2); + if (file.existsSync()) file.deleteSync(); + } catch (e, s) { + debugPrint('Failed to delete file: $e'); + writeLogToFile( + 'Failed to delete file during cancelDownloading: $e, $s', + ); + } + downloading.remove(event); + notifyListeners(); + } + /// Cancels the ongoing downloads and deletes the downloaded files. - Future cancelDownloading() async { - for (final event in downloading.keys.toList()) { - try { - final file = File(downloading[event]!.$2); - if (file.existsSync()) file.deleteSync(); - } catch (e, s) { - debugPrint('Failed to delete file: $e'); - writeLogToFile( - 'Failed to delete file during cancelDownloading: $e, $s', - ); - } - downloading.remove(event); + Future cancelDownloading([String? downloadPath]) async { + for (final event in downloading.keys) { + cancelEventDownload(event); } downloadsCompleter?.complete(); diff --git a/lib/screens/downloads/downloads_manager.dart b/lib/screens/downloads/downloads_manager.dart index ea02e875..05a7ce6f 100644 --- a/lib/screens/downloads/downloads_manager.dart +++ b/lib/screens/downloads/downloads_manager.dart @@ -299,17 +299,26 @@ class _DownloadTileState extends State { ]), ), ), - TextButton.icon( - onPressed: isDownloaded - ? () { - context - .read() - .delete(widget.downloadPath!); - } - : null, - icon: const Icon(Icons.delete, size: 20.0), - label: Text(loc.delete), - ), + if (isDownloaded) + TextButton.icon( + onPressed: () { + context + .read() + .delete(widget.downloadPath!); + }, + icon: const Icon(Icons.delete, size: 20.0), + label: Text(loc.delete), + ) + else + TextButton.icon( + onPressed: () { + context + .read() + .cancelEventDownload(widget.event); + }, + icon: const Icon(Icons.cancel, size: 20.0), + label: Text(loc.cancel), + ), if (isDesktop) TextButton.icon( onPressed: isDownloaded From 7c834d7b4a97cfe374b8b4c9e420ba0e75ee60ef Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Sun, 5 May 2024 15:25:18 -0300 Subject: [PATCH 09/10] fix: Do not show "Next events" sidebar when there is no upcoming events --- lib/screens/players/event_player_desktop.dart | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/screens/players/event_player_desktop.dart b/lib/screens/players/event_player_desktop.dart index 49fa329c..2b8e5899 100644 --- a/lib/screens/players/event_player_desktop.dart +++ b/lib/screens/players/event_player_desktop.dart @@ -92,6 +92,11 @@ class _EventPlayerDesktopState extends State { String get title => '${currentEvent.deviceName} (${currentEvent.server.name})'; + /// All the events but the current one. + Iterable get upcomingEvents { + return widget.upcomingEvents.where((ue) => ue != widget.event); + } + @override void initState() { super.initState(); @@ -375,7 +380,7 @@ class _EventPlayerDesktopState extends State { ]), ]), ), - if (widget.upcomingEvents.isNotEmpty) + if (upcomingEvents.isNotEmpty) CollapsableSidebar( left: false, builder: (context, collapsed, collapseButton) { @@ -423,10 +428,7 @@ class _EventPlayerDesktopState extends State { key: ValueKey(currentEvent), event: currentEvent, ), - ...widget.upcomingEvents.map((event) { - if (event == currentEvent) { - return const SizedBox.shrink(); - } + ...upcomingEvents.map((event) { return Padding( padding: const EdgeInsetsDirectional.only( top: 6.0), From 631f836493579ccc32acc40714ae9db8a9e7ee09 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Sun, 5 May 2024 15:35:52 -0300 Subject: [PATCH 10/10] fix: Events fetching when downloads directory is not available --- lib/providers/downloads_provider.dart | 11 +++++++---- lib/screens/events_browser/events_screen.dart | 4 +++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/providers/downloads_provider.dart b/lib/providers/downloads_provider.dart index 4e0ef419..1ebb4156 100644 --- a/lib/providers/downloads_provider.dart +++ b/lib/providers/downloads_provider.dart @@ -108,10 +108,13 @@ class DownloadsManager extends UnityProvider { if (dirs?.isNotEmpty ?? false) dir = dirs!.first; } - if (dir == null) { - final downloadsDir = await getDownloadsDirectory(); - if (downloadsDir != null) { - dir = Directory(path.join(downloadsDir.path, 'Bluecherry Client')); + // This method is only available on macOS + if (Platform.isMacOS) { + if (dir == null) { + final downloadsDir = await getDownloadsDirectory(); + if (downloadsDir != null) { + dir = Directory(path.join(downloadsDir.path, 'Bluecherry Client')); + } } } diff --git a/lib/screens/events_browser/events_screen.dart b/lib/screens/events_browser/events_screen.dart index 9bfd7dc1..5a2ead4c 100644 --- a/lib/screens/events_browser/events_screen.dart +++ b/lib/screens/events_browser/events_screen.dart @@ -132,7 +132,9 @@ class EventsScreenState extends State { eventsProvider.endTime == null) { return loc.today; } else if (DateUtils.isSameDay( - eventsProvider.startTime, eventsProvider.endTime)) { + eventsProvider.startTime, + eventsProvider.endTime, + )) { return formatter.format(eventsProvider.startTime!); } else { return loc.fromToDate(