mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-10-02 14:19:45 +02:00
refactor(ui): consolidate file feedback and shared display helpers
This commit is contained in:
1 parent
c5789df87d
commit
377710ec22
17 files changed
+77
-164
No files matched your search
@@ -29,6 +29,7 @@ import 'package:spotiflac_android/screens/track_metadata_screen.dart';
|
||||
import 'package:spotiflac_android/services/downloaded_embedded_cover_resolver.dart';
|
||||
import 'package:spotiflac_android/widgets/collection_scaffold.dart';
|
||||
import 'package:spotiflac_android/widgets/cached_cover_image.dart';
|
||||
import 'package:spotiflac_android/widgets/app_snack_bar.dart';
|
||||
import 'package:spotiflac_android/widgets/album_track_tile.dart';
|
||||
import 'package:spotiflac_android/widgets/animation_utils.dart';
|
||||
import 'package:spotiflac_android/widgets/destructive_selection_button.dart';
|
||||
@@ -188,15 +189,7 @@ class _DownloadedAlbumScreenState extends ConsumerState<DownloadedAlbumScreen>
|
||||
startItem: track,
|
||||
);
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
context.l10n.snackbarCannotOpenFile(context.friendlyError(e)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (mounted) showCannotOpenFileSnackBar(context, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:spotiflac_android/theme/app_tokens.dart';
|
||||
import 'package:spotiflac_android/widgets/track_card.dart';
|
||||
import 'package:spotiflac_android/widgets/app_bottom_sheet.dart';
|
||||
import 'package:spotiflac_android/widgets/app_alert_dialog.dart';
|
||||
import 'package:spotiflac_android/widgets/app_snack_bar.dart';
|
||||
import 'package:spotiflac_android/theme/mornye_theme.dart';
|
||||
import 'package:spotiflac_android/theme/mornye_icons.dart';
|
||||
import 'package:share_plus/share_plus.dart' show ShareParams, SharePlus, XFile;
|
||||
@@ -1057,13 +1058,7 @@ class _CollectionTrackTile extends ConsumerWidget {
|
||||
.playTrackList(folderTracks, startIndex: trackIndex);
|
||||
} catch (error) {
|
||||
if (!context.mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
context.l10n.snackbarCannotOpenFile(context.friendlyError(error)),
|
||||
),
|
||||
),
|
||||
);
|
||||
showCannotOpenFileSnackBar(context, error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import 'package:spotiflac_android/screens/selection_mode_mixin.dart';
|
||||
import 'package:spotiflac_android/widgets/collection_scaffold.dart';
|
||||
import 'package:spotiflac_android/widgets/album_track_tile.dart';
|
||||
import 'package:spotiflac_android/widgets/animation_utils.dart';
|
||||
import 'package:spotiflac_android/widgets/app_snack_bar.dart';
|
||||
import 'package:spotiflac_android/widgets/destructive_selection_button.dart';
|
||||
import 'package:spotiflac_android/widgets/selection_action_button.dart';
|
||||
import 'package:spotiflac_android/widgets/selection_bottom_bar.dart';
|
||||
@@ -154,15 +155,7 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen>
|
||||
.read(playbackProvider.notifier)
|
||||
.playLocalLibraryQueue(_sortedTracksCache, startItem: track);
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
context.l10n.snackbarCannotOpenFile(context.friendlyError(e)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (mounted) showCannotOpenFileSnackBar(context, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ import 'package:spotiflac_android/services/notification_service.dart';
|
||||
import 'package:spotiflac_android/services/app_remote_config_service.dart';
|
||||
import 'package:spotiflac_android/services/update_checker.dart';
|
||||
import 'package:spotiflac_android/widgets/app_announcement_dialog.dart';
|
||||
import 'package:spotiflac_android/widgets/app_snack_bar.dart';
|
||||
import 'package:spotiflac_android/widgets/update_dialog.dart';
|
||||
import 'package:spotiflac_android/widgets/animation_utils.dart';
|
||||
import 'package:spotiflac_android/widgets/settings_group.dart';
|
||||
@@ -277,15 +278,7 @@ class _MainShellState extends ConsumerState<MainShell>
|
||||
'Failed to repair SAF access from startup: $e',
|
||||
);
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
context.l10n.snackbarCannotOpenFile(
|
||||
context.friendlyError(e),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
showCannotOpenFileSnackBar(context, e);
|
||||
}
|
||||
} finally {
|
||||
if (dialogContext.mounted) {
|
||||
|
||||
@@ -35,6 +35,7 @@ import 'package:spotiflac_android/utils/string_utils.dart';
|
||||
import 'package:spotiflac_android/utils/synced_lyrics_scroll.dart';
|
||||
import 'package:spotiflac_android/widgets/app_bottom_sheet.dart';
|
||||
import 'package:spotiflac_android/widgets/app_loading_indicator.dart';
|
||||
import 'package:spotiflac_android/widgets/app_snack_bar.dart';
|
||||
import 'package:spotiflac_android/widgets/expressive_button.dart';
|
||||
import 'package:spotiflac_android/widgets/expressive_icon_button.dart';
|
||||
import 'package:spotiflac_android/widgets/aligned_lyric_pronunciation.dart';
|
||||
@@ -2021,13 +2022,7 @@ class _NowPlayingScreenState extends ConsumerState<NowPlayingScreen> {
|
||||
await openFile(source);
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
context.l10n.snackbarCannotOpenFile(context.friendlyError(e)),
|
||||
),
|
||||
),
|
||||
);
|
||||
showCannotOpenFileSnackBar(context, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import 'package:spotiflac_android/theme/mornye_theme.dart';
|
||||
import 'package:spotiflac_android/widgets/app_bottom_sheet.dart';
|
||||
import 'package:spotiflac_android/widgets/app_sliver_header.dart';
|
||||
import 'package:spotiflac_android/widgets/app_search_field.dart';
|
||||
import 'package:spotiflac_android/widgets/app_snack_bar.dart';
|
||||
import 'package:spotiflac_android/widgets/library_search_results.dart';
|
||||
import 'package:spotiflac_android/widgets/mornye_chrome.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -261,21 +262,6 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
final List<String> _filterModes = ['all', 'albums', 'singles', 'playlists'];
|
||||
bool _isPageControllerInitialized = false;
|
||||
bool _wasTabVisible = false;
|
||||
static const List<String> _months = [
|
||||
'Jan',
|
||||
'Feb',
|
||||
'Mar',
|
||||
'Apr',
|
||||
'May',
|
||||
'Jun',
|
||||
'Jul',
|
||||
'Aug',
|
||||
'Sep',
|
||||
'Oct',
|
||||
'Nov',
|
||||
'Dec',
|
||||
];
|
||||
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
final FocusNode _searchFocusNode = FocusNode();
|
||||
String _searchQuery = '';
|
||||
|
||||
@@ -847,7 +847,7 @@ extension _QueueTabItemWidgets on _QueueTabState {
|
||||
final isSelected = _selectedIds.contains(item.id);
|
||||
final date = item.addedAt;
|
||||
final dateStr =
|
||||
'${_QueueTabState._months[date.month - 1]} ${date.day}, ${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}';
|
||||
'${shortMonthNames[date.month - 1]} ${date.day}, ${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}';
|
||||
|
||||
final isDownloaded = item.source == LibraryItemSource.downloaded;
|
||||
final quality = item.qualityForMode(_libraryQualityLabelMode);
|
||||
|
||||
@@ -25,15 +25,7 @@ extension _QueueTabNavigation on _QueueTabState {
|
||||
coverUrl: coverUrl,
|
||||
);
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
context.l10n.snackbarCannotOpenFile(context.friendlyError(e)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (mounted) showCannotOpenFileSnackBar(context, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,15 +55,7 @@ extension _QueueTabNavigation on _QueueTabState {
|
||||
externalPath: item.filePath,
|
||||
);
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
context.l10n.snackbarCannotOpenFile(context.friendlyError(e)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (mounted) showCannotOpenFileSnackBar(context, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -262,11 +262,11 @@ class AboutPage extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> _launchUrl(String url) async {
|
||||
final uri = Uri.parse(url);
|
||||
await launchUrl(uri, mode: LaunchMode.inAppBrowserView);
|
||||
}
|
||||
Future<void> _launchUrl(String url) async {
|
||||
final uri = Uri.parse(url);
|
||||
await launchUrl(uri, mode: LaunchMode.inAppBrowserView);
|
||||
}
|
||||
|
||||
class _AppHeaderCard extends StatelessWidget {
|
||||
@@ -386,7 +386,7 @@ class _ContributorItem extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () => _launchGitHub(githubUsername),
|
||||
onTap: () => _launchUrl('https://github.com/$githubUsername'),
|
||||
splashColor: colorScheme.primary.withValues(alpha: 0.12),
|
||||
highlightColor: colorScheme.primary.withValues(alpha: 0.08),
|
||||
child: Padding(
|
||||
@@ -457,11 +457,6 @@ class _ContributorItem extends StatelessWidget {
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _launchGitHub(String username) async {
|
||||
final uri = Uri.parse('https://github.com/$username');
|
||||
await launchUrl(uri, mode: LaunchMode.inAppBrowserView);
|
||||
}
|
||||
}
|
||||
|
||||
class _Translator {
|
||||
@@ -619,7 +614,9 @@ class _TranslatorChip extends StatelessWidget {
|
||||
color: colorScheme.secondaryContainer,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: InkWell(
|
||||
onTap: () => _launchCrowdin(translator.crowdinUsername),
|
||||
onTap: () => _launchUrl(
|
||||
'https://crowdin.com/profile/${translator.crowdinUsername}',
|
||||
),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
@@ -656,11 +653,6 @@ class _TranslatorChip extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _launchCrowdin(String username) async {
|
||||
final uri = Uri.parse('https://crowdin.com/profile/$username');
|
||||
await launchUrl(uri, mode: LaunchMode.inAppBrowserView);
|
||||
}
|
||||
}
|
||||
|
||||
class _AboutSettingsItem extends StatelessWidget {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:spotiflac_android/widgets/app_alert_dialog.dart';
|
||||
import 'package:spotiflac_android/widgets/app_snack_bar.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
@@ -257,15 +258,7 @@ class _SetupScreenState extends ConsumerState<SetupScreen> {
|
||||
result = await PlatformBridge.pickSafTree();
|
||||
} catch (e) {
|
||||
_log.w('Failed to open Android SAF picker: $e');
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
context.l10n.snackbarCannotOpenFile(context.friendlyError(e)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (mounted) showCannotOpenFileSnackBar(context, e);
|
||||
}
|
||||
if (result != null) {
|
||||
final treeUri = result['tree_uri'] as String? ?? '';
|
||||
|
||||
@@ -220,15 +220,7 @@ extension _TrackMetadataFileActions on _TrackMetadataScreenState {
|
||||
explicit: isExplicit,
|
||||
);
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
context.l10n.snackbarCannotOpenFile(context.friendlyError(e)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (context.mounted) showCannotOpenFileSnackBar(context, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,7 +276,7 @@ extension _TrackMetadataFileActions on _TrackMetadataScreenState {
|
||||
}
|
||||
|
||||
String _formatFullDate(DateTime date) {
|
||||
return '${date.day} ${_TrackMetadataScreenState._months[date.month - 1]} ${date.year}, '
|
||||
return '${date.day} ${shortMonthNames[date.month - 1]} ${date.year}, '
|
||||
'${date.hour.toString().padLeft(2, '0')}:'
|
||||
'${date.minute.toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
@@ -350,10 +350,10 @@ extension _TrackMetadataConvertAndCueSplit on _TrackMetadataScreenState {
|
||||
final cueInfo = await PlatformBridge.parseCueSheet(cuePath);
|
||||
|
||||
if (!mounted) return;
|
||||
_hideCurrentSnackBar();
|
||||
ScaffoldMessenger.of(this.context).hideCurrentSnackBar();
|
||||
|
||||
if (cueInfo.containsKey('error')) {
|
||||
_showSnackBarMessage(_l10nCueSplitNoAudioFile);
|
||||
_showSnackBarMessage(this.context.l10n.cueSplitNoAudioFile);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -365,7 +365,7 @@ extension _TrackMetadataConvertAndCueSplit on _TrackMetadataScreenState {
|
||||
final tracksRaw = cueInfo['tracks'] as List<dynamic>? ?? [];
|
||||
|
||||
if (audioPath.isEmpty) {
|
||||
_showSnackBarMessage(_l10nCueSplitNoAudioFile);
|
||||
_showSnackBarMessage(this.context.l10n.cueSplitNoAudioFile);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -374,7 +374,7 @@ extension _TrackMetadataConvertAndCueSplit on _TrackMetadataScreenState {
|
||||
.toList();
|
||||
|
||||
if (tracks.isEmpty) {
|
||||
_showSnackBarMessage(_l10nCueSplitFailed);
|
||||
_showSnackBarMessage(this.context.l10n.cueSplitFailed);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -514,8 +514,8 @@ extension _TrackMetadataConvertAndCueSplit on _TrackMetadataScreenState {
|
||||
);
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
_hideCurrentSnackBar();
|
||||
_showSnackBarMessage(_l10nCueSplitFailed);
|
||||
ScaffoldMessenger.of(this.context).hideCurrentSnackBar();
|
||||
_showSnackBarMessage(this.context.l10n.cueSplitFailed);
|
||||
_log.e('Failed to parse CUE sheet: $e');
|
||||
}
|
||||
}
|
||||
@@ -668,7 +668,9 @@ extension _TrackMetadataConvertAndCueSplit on _TrackMetadataScreenState {
|
||||
}
|
||||
|
||||
if (!mounted) return;
|
||||
_showLongSnackBarMessage(_l10nCueSplitSplitting(1, tracks.length));
|
||||
_showLongSnackBarMessage(
|
||||
context.l10n.cueSplitSplitting(1, tracks.length),
|
||||
);
|
||||
|
||||
String? coverPath;
|
||||
try {
|
||||
@@ -699,8 +701,10 @@ extension _TrackMetadataConvertAndCueSplit on _TrackMetadataScreenState {
|
||||
coverPath: coverPath,
|
||||
onProgress: (current, total) {
|
||||
if (mounted) {
|
||||
_hideCurrentSnackBar();
|
||||
_showLongSnackBarMessage(_l10nCueSplitSplitting(current, total));
|
||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||
_showLongSnackBarMessage(
|
||||
context.l10n.cueSplitSplitting(current, total),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -741,18 +745,20 @@ extension _TrackMetadataConvertAndCueSplit on _TrackMetadataScreenState {
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
_hideCurrentSnackBar();
|
||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||
if (finalOutputPaths != null && finalOutputPaths.isNotEmpty) {
|
||||
_showSnackBarMessage(_l10nCueSplitSuccess(finalOutputPaths.length));
|
||||
_showSnackBarMessage(
|
||||
context.l10n.cueSplitSuccess(finalOutputPaths.length),
|
||||
);
|
||||
} else {
|
||||
_showSnackBarMessage(_l10nCueSplitFailed);
|
||||
_showSnackBarMessage(context.l10n.cueSplitFailed);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
_log.e('CUE split failed: $e');
|
||||
if (mounted) {
|
||||
_hideCurrentSnackBar();
|
||||
_showSnackBarMessage(_l10nCueSplitFailed);
|
||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||
_showSnackBarMessage(context.l10n.cueSplitFailed);
|
||||
}
|
||||
} finally {
|
||||
if (safTempAudioPath != null) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import 'package:spotiflac_android/widgets/app_action_button.dart';
|
||||
import 'package:spotiflac_android/widgets/app_choice_chip.dart';
|
||||
import 'package:spotiflac_android/widgets/app_switch.dart';
|
||||
import 'package:spotiflac_android/widgets/app_bottom_sheet.dart';
|
||||
import 'package:spotiflac_android/widgets/app_snack_bar.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
@@ -183,21 +184,6 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen>
|
||||
static final RegExp _invalidFileNameChars = RegExp(r'[<>:"/\\|?*\x00-\x1f]');
|
||||
static final RegExp _multiUnderscore = RegExp(r'_+');
|
||||
static final RegExp _leadingOrTrailingDots = RegExp(r'^\.+|\.+$');
|
||||
static const List<String> _months = [
|
||||
'Jan',
|
||||
'Feb',
|
||||
'Mar',
|
||||
'Apr',
|
||||
'May',
|
||||
'Jun',
|
||||
'Jul',
|
||||
'Aug',
|
||||
'Sep',
|
||||
'Oct',
|
||||
'Nov',
|
||||
'Dec',
|
||||
];
|
||||
|
||||
String get _coverCacheKey => _itemId;
|
||||
|
||||
@override
|
||||
|
||||
@@ -154,21 +154,6 @@ extension _TrackMetadataDisplay on _TrackMetadataScreenState {
|
||||
).showSnackBar(SnackBar(content: Text(_cueVirtualTrackGuidance(context))));
|
||||
}
|
||||
|
||||
void _hideCurrentSnackBar() {
|
||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||
}
|
||||
|
||||
String get _l10nCueSplitFailed => context.l10n.cueSplitFailed;
|
||||
String get _l10nCueSplitNoAudioFile => context.l10n.cueSplitNoAudioFile;
|
||||
|
||||
String _l10nCueSplitSplitting(int current, int total) {
|
||||
return context.l10n.cueSplitSplitting(current, total);
|
||||
}
|
||||
|
||||
String _l10nCueSplitSuccess(int count) {
|
||||
return context.l10n.cueSplitSuccess(count);
|
||||
}
|
||||
|
||||
void _showSnackBarMessage(String message) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:spotiflac_android/l10n/l10n.dart';
|
||||
import 'package:spotiflac_android/models/track.dart';
|
||||
import 'package:spotiflac_android/providers/download_queue_provider.dart';
|
||||
import 'package:spotiflac_android/providers/local_library_provider.dart';
|
||||
import 'package:spotiflac_android/providers/playback_provider.dart';
|
||||
import 'package:spotiflac_android/utils/file_access.dart';
|
||||
import 'package:spotiflac_android/widgets/app_snack_bar.dart';
|
||||
|
||||
/// Plays [track] from download history or the local library when a matching
|
||||
/// file already exists on disk.
|
||||
@@ -71,15 +71,7 @@ Future<bool> playLocalIfAvailable(
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
context.l10n.snackbarCannotOpenFile(context.friendlyError(e)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (context.mounted) showCannotOpenFileSnackBar(context, e);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,22 @@ String formatBytes(int bytes) {
|
||||
return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(2)} GB';
|
||||
}
|
||||
|
||||
/// English month abbreviations indexed by `DateTime.month - 1`.
|
||||
const List<String> shortMonthNames = [
|
||||
'Jan',
|
||||
'Feb',
|
||||
'Mar',
|
||||
'Apr',
|
||||
'May',
|
||||
'Jun',
|
||||
'Jul',
|
||||
'Aug',
|
||||
'Sep',
|
||||
'Oct',
|
||||
'Nov',
|
||||
'Dec',
|
||||
];
|
||||
|
||||
/// "m:ss" clock formatting for track durations.
|
||||
String formatClock(num seconds) {
|
||||
final total = seconds.round();
|
||||
|
||||
@@ -1,7 +1,19 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:spotiflac_android/l10n/l10n.dart';
|
||||
import 'package:spotiflac_android/theme/mornye_theme.dart';
|
||||
import 'package:spotiflac_android/widgets/mornye_chrome.dart';
|
||||
|
||||
/// Reports a failure to open a local file with a plain Material snackbar.
|
||||
void showCannotOpenFileSnackBar(BuildContext context, Object error) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
context.l10n.snackbarCannotOpenFile(context.friendlyError(error)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Transient messages use the same glass as confirmation dialogs in Mornye.
|
||||
ScaffoldFeatureController<SnackBar, SnackBarClosedReason> showAppSnackBar(
|
||||
BuildContext context, {
|
||||
|
||||
Reference in new issue
Block a user