diff --git a/lib/base/audio_handler.dart b/lib/base/audio_handler.dart index 29fe7fdf5..b54d05c3b 100644 --- a/lib/base/audio_handler.dart +++ b/lib/base/audio_handler.dart @@ -60,6 +60,8 @@ class NamidaAudioVideoHandler extends BasicAudioHandler { late final equalizer = AndroidEqualizer(); late final loudnessEnhancer = AndroidLoudnessEnhancer(); + bool get _willPlayWhenReady => playWhenReady.value; + RxBaseCore get currentItemDuration => _currentItemDuration; final _currentItemDuration = Rxn(); @@ -429,7 +431,7 @@ class NamidaAudioVideoHandler extends BasicAudioHandler { ); }, onRapidDetected: () { - if (isPlaying.value) { + if (playWhenReady.value) { _pausedTemporarily = true; pause(); } @@ -542,8 +544,7 @@ class NamidaAudioVideoHandler extends BasicAudioHandler { final hadPermissionBefore = await Permission.manageExternalStorage.isGranted; if (checkInterrupted()) return; if (hadPermissionBefore) { - final wasPlayWhenReady = willPlayWhenReady; - onPauseRaw().then((_) => setPlayWhenReady(wasPlayWhenReady)); + onPauseRaw(); cancelPlayErrorSkipTimer(); playErrorRemainingSecondsToSkip.value = 7; @@ -600,7 +601,6 @@ class NamidaAudioVideoHandler extends BasicAudioHandler { _nextSeekSetAudioCache = null; _nextSeekSetVideoCache = null; - final wasPlayWhenReady = willPlayWhenReady; setAudioOnlyPlayback(false); currentVideoStream.value = stream; @@ -612,7 +612,7 @@ class NamidaAudioVideoHandler extends BasicAudioHandler { currentCachedVideo.value = videoItem; await setVideoSource(source: AudioVideoSource.file(cachedFile.path), isFile: true); } else if (stream != null) { - if (wasPlayWhenReady) await onPauseRaw(); + if (!_willPlayWhenReady) await onPauseRaw(); final positionToRestore = currentPositionMS.value.milliseconds; final bool expired = mainStreams?.hasExpired() ?? true; @@ -679,7 +679,7 @@ class NamidaAudioVideoHandler extends BasicAudioHandler { ? await setSource( activeAudioSource, item: curritem, - startPlaying: () => willPlayWhenReady, + startPlaying: () => _willPlayWhenReady, initialPosition: positionToRestore, videoOptions: videoOptions, keepOldVideoSource: false, @@ -717,7 +717,7 @@ class NamidaAudioVideoHandler extends BasicAudioHandler { } } - if (wasPlayWhenReady) onPlayRaw(); + if (_willPlayWhenReady) onPlayRaw(); } } @@ -731,8 +731,6 @@ class NamidaAudioVideoHandler extends BasicAudioHandler { _nextSeekSetAudioCache = null; _nextSeekSetVideoCache = null; - final wasPlayWhenReady = willPlayWhenReady; - currentAudioStream.value = stream; mainStreams ??= YoutubeInfoController.video.fetchVideoStreamsSync(videoId) ?? YoutubeInfoController.current.currentYTStreams.value; @@ -742,13 +740,13 @@ class NamidaAudioVideoHandler extends BasicAudioHandler { await setSource( AudioVideoSource.file(cachedAudio.path), item: currentItem.value, - startPlaying: () => willPlayWhenReady, + startPlaying: () => _willPlayWhenReady, keepOldVideoSource: true, cachedAudioPath: cachedAudio.path, ); refreshNotification(); } else if (stream != null) { - if (wasPlayWhenReady) await super.onPauseRaw(); + if (!_willPlayWhenReady) await super.onPauseRaw(); final positionToRestore = currentPositionMS.value.milliseconds; @@ -766,7 +764,7 @@ class NamidaAudioVideoHandler extends BasicAudioHandler { ), initialPosition: positionToRestore, item: currentItem.value, - startPlaying: () => willPlayWhenReady, + startPlaying: () => _willPlayWhenReady, keepOldVideoSource: true, ); refreshNotification(); @@ -799,7 +797,7 @@ class NamidaAudioVideoHandler extends BasicAudioHandler { } } - if (wasPlayWhenReady) onPlayRaw(); + if (_willPlayWhenReady) onPlayRaw(); } } @@ -1212,7 +1210,7 @@ class NamidaAudioVideoHandler extends BasicAudioHandler { final extraReasons = [playabilty.reason, ...?playabilty.messages].whereType(); final extraReasonsText = extraReasons.isEmpty ? '' : ' | ${extraReasons.join(' | ')}'; snackyy(title: lang.ERROR, message: 'Empty audio streams. playabilty: `${playabilty.status.name}`$extraReasonsText', top: false, isError: true); - if (willPlayWhenReady) skipItem(); + if (_willPlayWhenReady) skipItem(); } return; } else { @@ -1740,7 +1738,7 @@ class NamidaAudioVideoHandler extends BasicAudioHandler { // ------------------------------------------------------------ Future togglePlayPause() { - if (isPlaying.value) { + if (playWhenReady.value) { return pause(); } else { return play(); @@ -1754,7 +1752,6 @@ class NamidaAudioVideoHandler extends BasicAudioHandler { await currentItem.value?._execute( selectable: (finalItem) => plsSeek(), youtubeID: (finalItem) async { - final wasPlayWhenReady = willPlayWhenReady; File? cachedAudioFile = _nextSeekSetAudioCache?.getFileIfPlaying(finalItem.id); File? cachedVideoFile = _nextSeekSetVideoCache?.getFileIfPlaying(finalItem.id); if (cachedAudioFile != null || cachedVideoFile != null) { @@ -1781,7 +1778,7 @@ class NamidaAudioVideoHandler extends BasicAudioHandler { item: currentItem.value, keepOldVideoSource: true, cachedAudioPath: cachedAudioFile.path, - startPlaying: () => willPlayWhenReady, + startPlaying: () => _willPlayWhenReady, videoOptions: _isAudioOnlyPlayback ? null : VideoSourceOptions( @@ -1807,7 +1804,7 @@ class NamidaAudioVideoHandler extends BasicAudioHandler { item: currentItem.value, keepOldVideoSource: true, cachedAudioPath: cachedAudioFile.path, - startPlaying: () => willPlayWhenReady, + startPlaying: () => _willPlayWhenReady, ); _isCurrentAudioFromCache = true; @@ -1815,7 +1812,7 @@ class NamidaAudioVideoHandler extends BasicAudioHandler { // <=======> await plsSeek(); - if (wasPlayWhenReady) onPlayRaw(); + if (_willPlayWhenReady) onPlayRaw(); } else { await plsSeek(); } @@ -1880,7 +1877,7 @@ class NamidaAudioVideoHandler extends BasicAudioHandler { _headsetButtonClickTimer?.cancel(); if (_headsetClicksCount == 1) { - _headsetButtonClickTimer = _createHeadsetClicksTimer(willPlayWhenReady ? pause : play); + _headsetButtonClickTimer = _createHeadsetClicksTimer(_willPlayWhenReady ? pause : play); } else if (_headsetClicksCount == 2) { _headsetButtonClickTimer = _createHeadsetClicksTimer(skipToNext); } else if (_headsetClicksCount == 3) { diff --git a/lib/controller/navigator_controller.dart b/lib/controller/navigator_controller.dart index 01496fa4b..de9bcfbbc 100644 --- a/lib/controller/navigator_controller.dart +++ b/lib/controller/navigator_controller.dart @@ -490,6 +490,20 @@ class NamidaNavigator { } } +enum SnackDisplayDuration { + flash(500), + short(1000), + mediumLow(1500), + medium(2000), + mediumHigh(2500), + long(3000), + veryLong(4000), + eternal(5000); + + final int milliseconds; + const SnackDisplayDuration(this.milliseconds); +} + SnackbarController? snackyy({ IconData? icon, String title = '', @@ -499,7 +513,7 @@ SnackbarController? snackyy({ EdgeInsets margin = const EdgeInsets.symmetric(horizontal: 24.0, vertical: 8.0), bool altDesign = false, int animationDurationMS = 600, - int displaySeconds = 2, + SnackDisplayDuration displayDuration = SnackDisplayDuration.medium, double borderRadius = 12.0, Color? leftBarIndicatorColor, Color? borderColor, @@ -608,7 +622,7 @@ SnackbarController? snackyy({ ); final snackbar = NamSnackBar( margin: margin, - duration: Duration(seconds: displaySeconds), + duration: Duration(milliseconds: displayDuration.milliseconds), animationDuration: Duration(milliseconds: animationDurationMS), alignment: Alignment.centerLeft, top: top, diff --git a/lib/controller/platform/namida_channel/namida_channel_android.dart b/lib/controller/platform/namida_channel/namida_channel_android.dart index 31fb9c339..020702a2c 100644 --- a/lib/controller/platform/namida_channel/namida_channel_android.dart +++ b/lib/controller/platform/namida_channel/namida_channel_android.dart @@ -33,8 +33,9 @@ class _NamidaChannelAndroid extends NamidaChannel { @override Future showToast({ required String message, - int seconds = 5, + required SnackDisplayDuration duration, }) async { + final seconds = (duration.milliseconds / 1000).ceil(); _channel.invokeMethod( 'showToast', { diff --git a/lib/controller/platform/namida_channel/namida_channel_base.dart b/lib/controller/platform/namida_channel/namida_channel_base.dart index b4170c447..654a66844 100644 --- a/lib/controller/platform/namida_channel/namida_channel_base.dart +++ b/lib/controller/platform/namida_channel/namida_channel_base.dart @@ -16,7 +16,7 @@ abstract class NamidaChannel { Future setCanEnterPip(bool canEnter); - Future showToast({required String message, int seconds = 5}); + Future showToast({required String message, required SnackDisplayDuration duration}); Future getPlatformSdk(); diff --git a/lib/controller/platform/namida_channel/namida_channel_windows.dart b/lib/controller/platform/namida_channel/namida_channel_windows.dart index 9303cba63..bc9f216b4 100644 --- a/lib/controller/platform/namida_channel/namida_channel_windows.dart +++ b/lib/controller/platform/namida_channel/namida_channel_windows.dart @@ -16,10 +16,10 @@ class _NamidaChannelWindows extends NamidaChannel { @override Future showToast({ required String message, - int seconds = 5, + required SnackDisplayDuration duration, }) async { // -- use in-app toast - snackyy(message: message, displaySeconds: seconds); + snackyy(message: message, displayDuration: duration); } @override diff --git a/lib/controller/player_controller.dart b/lib/controller/player_controller.dart index 6a6b80c75..30e6d8766 100644 --- a/lib/controller/player_controller.dart +++ b/lib/controller/player_controller.dart @@ -42,6 +42,8 @@ class Player { Map> get audioCacheMap => _audioHandler.audioCacheMap; + RxBaseCore get playWhenReady => _audioHandler.playWhenReady; + Selectable? get currentTrack { final item = _audioHandler.currentItem.value; return item is Selectable ? item : null; @@ -112,7 +114,6 @@ class Player { RxBaseCore get currentSpeed => _audioHandler.currentSpeed; RxBaseCore get currentItemDuration => _audioHandler.currentItemDuration; RxBaseCore get isPlaying => _audioHandler.isPlaying; - bool get isPlayingR => _audioHandler.isPlaying.valueR; bool get isBufferingR => _audioHandler.currentState.valueR == ProcessingState.buffering; bool get isLoadingR => _audioHandler.currentState.valueR == ProcessingState.loading; RxBaseCore get isFetchingInfo => _audioHandler.isFetchingInfo; @@ -372,6 +373,8 @@ class Player { icon: shouldInsertNext ? Broken.redo : Broken.add_circle, message: '${addins.capitalizeFirst()} ${finalTracks.displayTrackKeyword}', top: false, + displayDuration: SnackDisplayDuration.mediumLow, + animationDurationMS: 400, ); } return true; @@ -395,6 +398,8 @@ class Player { icon: shouldInsertNext ? Broken.redo : Broken.add_circle, message: '${addins.capitalizeFirst()} ${finalVideos.length.displayVideoKeyword}', top: false, + displayDuration: SnackDisplayDuration.mediumLow, + animationDurationMS: 400, ); } return true; @@ -425,8 +430,8 @@ class Player { } Future removeFromQueue(int index) async { - // why [isPlaying] ? imagine removing while paused - await _audioHandler.removeFromQueue(index, isPlaying.value && _audioHandler.defaultShouldStartPlayingOnNextPrev); + // why [playWhenReady] ? imagine removing while paused + await _audioHandler.removeFromQueue(index, playWhenReady.value && _audioHandler.defaultShouldStartPlayingOnNextPrev); } Future replaceAllTracksInQueue(Playable oldTrack, Playable newTrack) async { diff --git a/lib/controller/video_controller.dart b/lib/controller/video_controller.dart index 372a56131..3671efe70 100644 --- a/lib/controller/video_controller.dart +++ b/lib/controller/video_controller.dart @@ -347,7 +347,7 @@ class VideoController { if (returnEarly) return erabaretaVideo; if (erabaretaVideo == null) { - if ((vpsInSettings == VideoPlaybackSource.local) == false) { + if (vpsInSettings == VideoPlaybackSource.local) { videoBlockedByType.value = VideoFetchBlockedBy.playbackSource; } else if (!ConnectivityController.inst.hasConnection) { videoBlockedByType.value = VideoFetchBlockedBy.noNetwork; diff --git a/lib/core/functions.dart b/lib/core/functions.dart index 8571ceffd..4d2a2edbd 100644 --- a/lib/core/functions.dart +++ b/lib/core/functions.dart @@ -170,7 +170,7 @@ class NamidaOnTaps { snackyy( title: lang.UNDO_CHANGES, message: lang.UNDO_CHANGES_DELETED_QUEUE, - displaySeconds: 3, + displayDuration: SnackDisplayDuration.long, button: ( lang.UNDO, () async => await QueueController.inst.reAddQueue(oldQueue), @@ -183,7 +183,7 @@ class NamidaOnTaps { snackyy( title: lang.UNDO_CHANGES, message: lang.UNDO_CHANGES_DELETED_TRACK, - displaySeconds: 3, + displayDuration: SnackDisplayDuration.long, button: ( lang.UNDO, whatDoYouWant, diff --git a/lib/main.dart b/lib/main.dart index e7438ad5e..c91fefbc0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -260,7 +260,7 @@ void _initErrorInterpreters() { void _initLifeCycle() { NamidaChannel.inst.addOnDestroy('main', () async { final mode = settings.player.killAfterDismissingApp.value; - if (mode == KillAppMode.always || (mode == KillAppMode.ifNotPlaying && !Player.inst.isPlaying.value)) { + if (mode == KillAppMode.always || (mode == KillAppMode.ifNotPlaying && !Player.inst.playWhenReady.value)) { await Player.inst.pause(); await Player.inst.dispose(); } @@ -424,7 +424,7 @@ Future requestIgnoreBatteryOptimizations() async { snackyy( message: lang.IGNORE_BATTERY_OPTIMIZATIONS_SUBTITLE, - displaySeconds: 5, + displayDuration: SnackDisplayDuration.eternal, top: false, isError: true, button: ( diff --git a/lib/packages/lyrics_lrc_parsed_view.dart b/lib/packages/lyrics_lrc_parsed_view.dart index d42aba902..d02ffe65f 100644 --- a/lib/packages/lyrics_lrc_parsed_view.dart +++ b/lib/packages/lyrics_lrc_parsed_view.dart @@ -274,10 +274,10 @@ class LyricsLRCParsedViewState extends State { onPressed: Player.inst.previous, ), ObxO( - rx: Player.inst.isPlaying, - builder: (context, isPlaying) => NamidaIconButton( + rx: Player.inst.playWhenReady, + builder: (context, playWhenReady) => NamidaIconButton( horizontalPadding: 18.0, - icon: isPlaying ? Broken.pause : Broken.play, + icon: playWhenReady ? Broken.pause : Broken.play, iconSize: 32.0, onPressed: Player.inst.togglePlayPause, ), @@ -393,7 +393,7 @@ class LyricsLRCParsedViewState extends State { onPointerUp: (event) { _scrollTimer = Timer(const Duration(seconds: 3), () { _canAnimateScroll = true; - if (Player.inst.isPlaying.value) { + if (Player.inst.playWhenReady.value) { _updateHighlightedLine(Player.inst.nowPlayingPosition.value, forceAnimate: true); } if (_updateOpacityForEmptyLines && currentLRC != null && _checkIfTextEmpty(_currentLine)) { diff --git a/lib/packages/miniplayer_base.dart b/lib/packages/miniplayer_base.dart index eb4027700..d19ff2554 100644 --- a/lib/packages/miniplayer_base.dart +++ b/lib/packages/miniplayer_base.dart @@ -976,10 +976,10 @@ class _NamidaMiniPlayerBaseState extends State { icon: Padding( padding: EdgeInsets.all(6.0 * cp * rcp), child: ObxO( - rx: Player.inst.isPlaying, - builder: (context, isPlaying) => AnimatedSwitcher( + rx: Player.inst.playWhenReady, + builder: (context, playWhenReady) => AnimatedSwitcher( duration: const Duration(milliseconds: 200), - child: isPlaying + child: playWhenReady ? Icon( Broken.pause, size: iconSize, diff --git a/lib/ui/dialogs/general_popup_dialog.dart b/lib/ui/dialogs/general_popup_dialog.dart index 18658dca7..dccf1756f 100644 --- a/lib/ui/dialogs/general_popup_dialog.dart +++ b/lib/ui/dialogs/general_popup_dialog.dart @@ -457,7 +457,7 @@ Future showGeneralPopupDialog( snackyy( title: lang.UNDO_CHANGES, message: lang.UNDO_CHANGES_DELETED_PLAYLIST, - displaySeconds: 3, + displayDuration: SnackDisplayDuration.long, button: ( lang.UNDO, () async => await PlaylistController.inst.reAddPlaylist(pl, pl.modifiedDate, artworkBytes: artworkBytes), diff --git a/lib/ui/dialogs/track_info_dialog.dart b/lib/ui/dialogs/track_info_dialog.dart index 2ed19dbeb..789f7f0db 100644 --- a/lib/ui/dialogs/track_info_dialog.dart +++ b/lib/ui/dialogs/track_info_dialog.dart @@ -54,7 +54,7 @@ Future showTrackInfoDialog( bool shouldShowTheField(bool isUnknown) => !isUnknown || (settings.showUnknownFieldsInTrackInfoDialog.value && isUnknown); void showPreviewTrackDialog() async { - final wasPlaying = Player.inst.isPlaying.value; + final wasPlaying = Player.inst.playWhenReady.value; if (wasPlaying) { Player.inst.pause(); } diff --git a/lib/ui/pages/about_page.dart b/lib/ui/pages/about_page.dart index 94703d334..57b262b29 100644 --- a/lib/ui/pages/about_page.dart +++ b/lib/ui/pages/about_page.dart @@ -9,6 +9,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_mailer/flutter_mailer.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:http/http.dart' as http; +import 'package:jiffy/jiffy.dart'; import 'package:markdown/src/ast.dart' as md; import 'package:share_plus/share_plus.dart'; @@ -60,14 +61,8 @@ class _AboutPageState extends State { String _getDateDifferenceText() { final buildDate = NamidaDeviceInfo.buildDate; if (buildDate == null) return ''; - final diff = DateTime.now().toUtc().difference(buildDate).abs(); - final diffDays = diff.inDays; - if (diffDays > 0) return "(${diffDays.displayDayKeyword})"; - final diffHours = diff.inHours; - if (diffHours > 0) return "($diffHours ${lang.HOURS})"; - final diffMins = diff.inMinutes; - if (diffMins > 0) return "($diffMins ${lang.MINUTES})"; - return ''; + final differenceText = Jiffy.parseFromDateTime(buildDate).fromNow(withPrefixAndSuffix: true); + return "($differenceText)"; } Future _checkNewVersion(String current) async { diff --git a/lib/ui/widgets/custom_widgets.dart b/lib/ui/widgets/custom_widgets.dart index 3d749d4df..be82eed07 100644 --- a/lib/ui/widgets/custom_widgets.dart +++ b/lib/ui/widgets/custom_widgets.dart @@ -1049,6 +1049,7 @@ class StackedIcon extends StatelessWidget { final String? secondaryText; final Color? baseIconColor; final Color? secondaryIconColor; + final Color? shadowColor; final double? iconSize; final double? secondaryIconSize; final double blurRadius; @@ -1062,6 +1063,7 @@ class StackedIcon extends StatelessWidget { this.secondaryIcon, this.baseIconColor, this.secondaryIconColor, + this.shadowColor, this.secondaryText, this.iconSize, this.secondaryIconSize = 14.0, @@ -1090,16 +1092,23 @@ class StackedIcon extends StatelessWidget { Positioned( bottom: 0, right: 0, - child: Container( + child: DecoratedBox( decoration: BoxDecoration( borderRadius: BorderRadius.circular(6), boxShadow: [ - BoxShadow(color: context.theme.scaffoldBackgroundColor, spreadRadius: 0, blurRadius: blurRadius), + BoxShadow( + color: shadowColor ?? context.theme.scaffoldBackgroundColor, + spreadRadius: 0, + blurRadius: blurRadius, + ), ], ), child: smallChild ?? (secondaryText != null - ? Text(secondaryText!, style: context.textTheme.displaySmall?.copyWith(color: _getColory(context, secondaryIconColor))) + ? Text( + secondaryText!, + style: context.textTheme.displaySmall?.copyWith(color: _getColory(context, secondaryIconColor)), + ) : Icon( secondaryIcon, size: secondaryIconSize, diff --git a/lib/ui/widgets/library/queue_tile.dart b/lib/ui/widgets/library/queue_tile.dart index 701f011b2..43c9b5e42 100644 --- a/lib/ui/widgets/library/queue_tile.dart +++ b/lib/ui/widgets/library/queue_tile.dart @@ -51,7 +51,7 @@ class QueueTile extends StatelessWidget { snackyy( title: lang.UNDO_CHANGES, message: lang.UNDO_CHANGES_DELETED_QUEUE, - displaySeconds: 3, + displayDuration: SnackDisplayDuration.long, button: ( lang.UNDO, () async => await QueueController.inst.reAddQueue(oldQueue), diff --git a/lib/ui/widgets/settings/indexer_settings.dart b/lib/ui/widgets/settings/indexer_settings.dart index 3da18eb86..78abf7f9a 100644 --- a/lib/ui/widgets/settings/indexer_settings.dart +++ b/lib/ui/widgets/settings/indexer_settings.dart @@ -200,7 +200,7 @@ class IndexerSettings extends SettingSubpageProvider { snackyy( title: lang.MINIMUM_ONE_ITEM, message: lang.MINIMUM_ONE_FOLDER_SUBTITLE, - displaySeconds: 4, + displayDuration: SnackDisplayDuration.veryLong, ); } else { NamidaNavigator.inst.navigateDialog( diff --git a/lib/ui/widgets/video_widget.dart b/lib/ui/widgets/video_widget.dart index f920a1a6a..1a1623f9d 100644 --- a/lib/ui/widgets/video_widget.dart +++ b/lib/ui/widgets/video_widget.dart @@ -4,16 +4,7 @@ import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; - import 'package:flutter_volume_controller/flutter_volume_controller.dart' show FlutterVolumeController; -import 'package:screen_brightness/screen_brightness.dart'; -import 'package:youtipie/class/result_wrapper/playlist_result_base.dart'; -import 'package:youtipie/class/streams/audio_stream.dart'; -import 'package:youtipie/class/streams/endscreens/endscreen_item_base.dart'; -import 'package:youtipie/class/streams/video_streams_result.dart'; -import 'package:youtipie/core/enum.dart'; -import 'package:youtipie/youtipie.dart'; - import 'package:namida/class/route.dart'; import 'package:namida/class/track.dart'; import 'package:namida/class/video.dart'; @@ -44,6 +35,13 @@ import 'package:namida/youtube/seek_ready_widget.dart'; import 'package:namida/youtube/widgets/video_info_dialog.dart'; import 'package:namida/youtube/widgets/yt_thumbnail.dart'; import 'package:namida/youtube/yt_utils.dart'; +import 'package:screen_brightness/screen_brightness.dart'; +import 'package:youtipie/class/result_wrapper/playlist_result_base.dart'; +import 'package:youtipie/class/streams/audio_stream.dart'; +import 'package:youtipie/class/streams/endscreens/endscreen_item_base.dart'; +import 'package:youtipie/class/streams/video_streams_result.dart'; +import 'package:youtipie/core/enum.dart'; +import 'package:youtipie/youtipie.dart'; class NamidaVideoControls extends StatefulWidget { final bool showControls; @@ -1277,7 +1275,13 @@ class NamidaVideoControlsState extends State with TickerPro final isAudio = widget.isLocal ? VideoController.inst.currentVideo.valueR == null : settings.youtube.isAudioOnlyMode.valueR; String? qt; - if (!isAudio) { + IconData icon; + IconData? secondaryIcon; + if (isAudio) { + icon = Broken.musicnote; + } else { + icon = Broken.setting; + if (widget.isLocal) { final video = VideoController.inst.currentVideo.valueR; qt = video == null ? null : '${video.resolution}p${video.framerateText()}'; @@ -1287,6 +1291,15 @@ class NamidaVideoControlsState extends State with TickerPro final cached = Player.inst.currentCachedVideo.valueR; if (cached != null) qt = "${cached.resolution}p${cached.framerateText()}"; } + + final dataSaverMode = ConnectivityController.inst.hasHighConnectionR + ? settings.youtube.dataSaverMode.valueR + : settings.youtube.dataSaverModeMobile.valueR; + if (Player.inst.currentVideoStream.valueR == null && + Player.inst.currentCachedVideo.valueR == null && + !dataSaverMode.canFetchNetworkVideoStream) { + secondaryIcon = Broken.magicpen; + } } } @@ -1299,37 +1312,21 @@ class NamidaVideoControlsState extends State with TickerPro ), const SizedBox(width: 4.0), ], - Obx( - (context) { - IconData icon; - IconData? secondaryIcon; - if (isAudio) { - icon = Broken.musicnote; - } else { - icon = Broken.setting; - final dataSaverMode = ConnectivityController.inst.hasHighConnectionR - ? settings.youtube.dataSaverMode.valueR - : settings.youtube.dataSaverModeMobile.valueR; - if (!dataSaverMode.canFetchNetworkVideoStream && Player.inst.currentVideoStream.valueR == null) { - secondaryIcon = Broken.magicpen; - } - } - - return secondaryIcon == null - ? Icon( - icon, - color: itemsColor, - size: 16.0, - ) - : StackedIcon( - baseIcon: icon, - secondaryIcon: secondaryIcon, - iconSize: 16.0, - secondaryIconSize: 8.0, - disableColor: true, - ); - }, - ), + secondaryIcon == null + ? Icon( + icon, + color: itemsColor, + size: 16.0, + ) + : StackedIcon( + baseIcon: icon, + secondaryIcon: secondaryIcon, + iconSize: 16.0, + secondaryIconSize: 8.0, + baseIconColor: itemsColor, + secondaryIconColor: itemsColor, + shadowColor: itemsColor.invert(), + ), ], ); }, @@ -1604,56 +1601,32 @@ class NamidaVideoControlsState extends State with TickerPro blur: 2.5, child: ColoredBox( color: Colors.black.withValues(alpha: 0.3), - child: Obx( - (context) { - final currentPosition = Player.inst.nowPlayingPositionR; - final currentTotalDur = Player.inst.currentItemDuration.valueR?.inMilliseconds ?? 0; - final reachedLastPosition = currentPosition != 0 && (currentPosition - currentTotalDur).abs() < 100; // 100ms allowance - - return reachedLastPosition - ? NamidaIconButton( - icon: null, - padding: const EdgeInsets.all(14.0), - onPressed: () async { - await Player.inst.seek(Duration.zero); - await Player.inst.play(); - _startTimer(); - }, - child: Icon( - Broken.refresh, + child: NamidaIconButton( + icon: null, + padding: const EdgeInsets.all(14.0), + onPressed: () { + Player.inst.togglePlayPause(); + _startTimer(); + }, + child: ObxO( + rx: Player.inst.playWhenReady, + builder: (context, playWhenReady) => AnimatedSwitcher( + duration: const Duration(milliseconds: 200), + child: playWhenReady + ? Icon( + Broken.pause, size: 40.0, color: itemsColor, - key: const Key('replay'), - ), - ) - : NamidaIconButton( - icon: null, - padding: const EdgeInsets.all(14.0), - onPressed: () { - Player.inst.togglePlayPause(); - _startTimer(); - }, - child: ObxO( - rx: Player.inst.isPlaying, - builder: (context, isPlaying) => AnimatedSwitcher( - duration: const Duration(milliseconds: 200), - child: isPlaying - ? Icon( - Broken.pause, - size: 40.0, - color: itemsColor, - key: const Key('paused'), - ) - : Icon( - Broken.play, - size: 40.0, - color: itemsColor, - key: const Key('playing'), - ), - ), + key: const Key('paused'), + ) + : Icon( + Broken.play, + size: 40.0, + color: itemsColor, + key: const Key('playing'), ), - ); - }, + ), + ), ), ), ), diff --git a/lib/youtube/controller/youtube_account_controller.dart b/lib/youtube/controller/youtube_account_controller.dart index b63f8e2b9..2aeaac719 100644 --- a/lib/youtube/controller/youtube_account_controller.dart +++ b/lib/youtube/controller/youtube_account_controller.dart @@ -244,7 +244,7 @@ class YoutubeAccountController { message: msg, title: title, isError: true, - displaySeconds: 3, + displayDuration: SnackDisplayDuration.long, button: manageSubscriptionButton ? ( lang.MANAGE, @@ -260,7 +260,7 @@ class YoutubeAccountController { } static void _showInfo(String msg, {String? title}) { - snackyy(message: msg, title: title ?? '', displaySeconds: 3); + snackyy(message: msg, title: title ?? '', displayDuration: SnackDisplayDuration.long); } static Future signIn({required LoginPageConfiguration pageConfig, required bool forceSignIn}) async { diff --git a/lib/youtube/controller/youtube_info_controller.dart b/lib/youtube/controller/youtube_info_controller.dart index b5e594712..aab4b090c 100644 --- a/lib/youtube/controller/youtube_info_controller.dart +++ b/lib/youtube/controller/youtube_info_controller.dart @@ -102,7 +102,7 @@ class _YTReportingLog extends Logger { message: msg, title: title, isError: true, - displaySeconds: 3, + displayDuration: SnackDisplayDuration.long, top: false, ); } diff --git a/lib/youtube/pages/user/youtube_manage_subscription_page.dart b/lib/youtube/pages/user/youtube_manage_subscription_page.dart index 139424ada..c332d6cc8 100644 --- a/lib/youtube/pages/user/youtube_manage_subscription_page.dart +++ b/lib/youtube/pages/user/youtube_manage_subscription_page.dart @@ -28,7 +28,7 @@ class _YoutubeManageSubscriptionPageState extends State { ], ); - final playPauseButtonChild = Obx( - (context) { - final isLoading = Player.inst.shouldShowLoadingIndicatorR; - return Stack( - alignment: Alignment.center, - children: [ - if (isLoading) - IgnorePointer( - child: NamidaOpacity( - key: Key("${currentId}_button_loading"), - enabled: true, - opacity: 0.3, - child: ThreeArchedCircle( - key: Key("${currentId}_button_loading_child"), - color: defaultIconColor, - size: 36.0, + final playPauseButtonChild = ObxO( + rx: Player.inst.playWhenReady, + builder: (context, playWhenReady) => Obx( + (context) { + final isLoading = Player.inst.shouldShowLoadingIndicatorR; + return Stack( + alignment: Alignment.center, + children: [ + if (isLoading) + IgnorePointer( + child: NamidaOpacity( + key: Key("${currentId}_button_loading"), + enabled: true, + opacity: 0.3, + child: ThreeArchedCircle( + key: Key("${currentId}_button_loading_child"), + color: defaultIconColor, + size: 36.0, + ), ), ), + NamidaIconButton( + verticalPadding: 4.0, + horizontalPadding: 4.0, + onPressed: Player.inst.togglePlayPause, + icon: null, + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 200), + child: playWhenReady + ? Icon( + Broken.pause, + color: defaultIconColor, + key: const Key('pause'), + ) + : Icon( + Broken.play, + color: defaultIconColor, + key: const Key('play'), + ), + ), ), - NamidaIconButton( - verticalPadding: 4.0, - horizontalPadding: 4.0, - onPressed: () { - Player.inst.togglePlayPause(); - }, - icon: null, - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 200), - child: Player.inst.isPlayingR - ? Icon( - Broken.pause, - color: defaultIconColor, - key: const Key('pause'), - ) - : Icon( - Broken.play, - color: defaultIconColor, - key: const Key('play'), - ), - ), - ), - ], - ); - }, + ], + ); + }, + ), ); final nextButton = NamidaIconButton( verticalPadding: 4.0, diff --git a/lib/youtube/yt_utils.comments.dart b/lib/youtube/yt_utils.comments.dart index 3d6f5ba77..23fb6e94d 100644 --- a/lib/youtube/yt_utils.comments.dart +++ b/lib/youtube/yt_utils.comments.dart @@ -213,7 +213,7 @@ class _YTUtilsCommentActions { } void _showError() { - snackyy(message: lang.FAILED, isError: true, displaySeconds: 2); + snackyy(message: lang.FAILED, isError: true); } String? _getCurrentAutorIfActive() { diff --git a/lib/youtube/yt_utils.dart b/lib/youtube/yt_utils.dart index dacfe4f61..6cf3711c7 100644 --- a/lib/youtube/yt_utils.dart +++ b/lib/youtube/yt_utils.dart @@ -759,7 +759,7 @@ class YTUtils { snackyy( title: lang.UNDO_CHANGES, message: lang.UNDO_CHANGES_DELETED_TRACK, - displaySeconds: 3, + displayDuration: SnackDisplayDuration.long, button: (lang.UNDO, whatDoYouWant), ); } diff --git a/pubspec.yaml b/pubspec.yaml index 6c2b8c798..5c1c14468 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: namida description: A Beautiful and Feature-rich Music Player, With YouTube & Video Support Built in Flutter publish_to: "none" -version: 4.9.68-beta+250131201 +version: 4.9.69-beta+250201132 environment: sdk: ">=3.6.0 <4.0.0" @@ -43,6 +43,7 @@ dependencies: share_plus: ^10.0.2 calendar_date_picker2: ^1.0.2 wakelock_plus: ^1.1.1 + screen_brightness: ^2.1.1 flutter_local_notifications: "19.0.0-dev.4" flutter_sharing_intent: git: