Skip to content

Commit

Permalink
feat: new seekbar with functional ux
Browse files Browse the repository at this point in the history
+ tap to seek
+ always ready to seek (yt miniplayer)
+ drag up to cancel
+ more buttons for video (copy url, repeat mode)
  • Loading branch information
MSOB7YY committed Feb 5, 2024
1 parent 59a920c commit 2bc89dc
Show file tree
Hide file tree
Showing 15 changed files with 896 additions and 388 deletions.
5 changes: 5 additions & 0 deletions lib/base/audio_handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1106,6 +1106,11 @@ class NamidaAudioVideoHandler<Q extends Playable> extends BasicAudioHandler<Q> {
);
}

@override
void onPlaybackCompleted() {
VideoController.inst.normalControlskey.currentState?.showControlsBriefly();
}

@override
Future<void> setSkipSilenceEnabled(bool enabled) async {
if (defaultPlayerConfig.skipSilence) await super.setSkipSilenceEnabled(enabled);
Expand Down
12 changes: 12 additions & 0 deletions lib/controller/player_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import 'package:namida/core/icon_fonts/broken_icons.dart';
import 'package:namida/core/namida_converter_ext.dart';
import 'package:namida/core/translations/language.dart';
import 'package:namida/youtube/class/youtube_id.dart';
import 'package:namida/youtube/controller/youtube_controller.dart';

class Player {
static Player get inst => _instance;
Expand Down Expand Up @@ -53,6 +54,17 @@ class Player {
NamidaVideo? get currentCachedVideo => _audioHandler.currentCachedVideo.value;
AudioCacheDetails? get currentCachedAudio => _audioHandler.currentCachedAudio.value;

Duration get getCurrentVideoDuration {
Duration? playerDuration = Player.inst.currentItemDuration;
if (playerDuration == null || playerDuration == Duration.zero) {
playerDuration = Player.inst.currentAudioStream?.durationMS?.milliseconds ??
Player.inst.currentVideoStream?.durationMS?.milliseconds ??
(nowPlayingVideoID == null ? VideoController.inst.currentVideo.value?.durationMS.milliseconds : YoutubeController.inst.currentYoutubeMetadataVideo.value?.duration) ??
Duration.zero;
}
return playerDuration;
}

bool get isAudioOnlyPlayback => _audioHandler.isAudioOnlyPlayback;
bool get isCurrentAudioFromCache => _audioHandler.isCurrentAudioFromCache;

Expand Down
14 changes: 14 additions & 0 deletions lib/controller/settings_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ class SettingsController {
final killPlayerAfterDismissingAppMode = KillAppMode.ifNotPlaying.obs;
final floatingActionButton = FABType.none.obs;
final ytInitialHomePage = YTHomePages.playlists.obs;
final ytTapToSeek = YTSeekActionMode.expandedMiniplayer.obs;
final ytDragToSeek = YTSeekActionMode.all.obs;

final RxMap<TrackTilePosition, TrackTileItem> trackItem = {
TrackTilePosition.row1Item1: TrackTileItem.title,
Expand Down Expand Up @@ -507,6 +509,8 @@ class SettingsController {
killPlayerAfterDismissingAppMode.value = KillAppMode.values.getEnum(json['killPlayerAfterDismissingAppMode']) ?? killPlayerAfterDismissingAppMode.value;
floatingActionButton.value = FABType.values.getEnum(json['floatingActionButton']) ?? floatingActionButton.value;
ytInitialHomePage.value = YTHomePages.values.getEnum(json['ytInitialHomePage']) ?? ytInitialHomePage.value;
ytTapToSeek.value = YTSeekActionMode.values.getEnum(json['ytTapToSeek']) ?? ytTapToSeek.value;
ytDragToSeek.value = YTSeekActionMode.values.getEnum(json['ytDragToSeek']) ?? ytDragToSeek.value;

trackItem.value = _getEnumMap(
json['trackItem'],
Expand Down Expand Up @@ -715,6 +719,8 @@ class SettingsController {
'killPlayerAfterDismissingAppMode': killPlayerAfterDismissingAppMode.value.convertToString,
'floatingActionButton': floatingActionButton.value.convertToString,
'ytInitialHomePage': ytInitialHomePage.value.convertToString,
'ytTapToSeek': ytTapToSeek.value.convertToString,
'ytDragToSeek': ytDragToSeek.value.convertToString,
'mostPlayedTimeRange': mostPlayedTimeRange.value.convertToString,
'mostPlayedCustomDateRange': mostPlayedCustomDateRange.value.toJson(),
'mostPlayedCustomisStartOfDay': mostPlayedCustomisStartOfDay.value,
Expand Down Expand Up @@ -913,6 +919,8 @@ class SettingsController {
KillAppMode? killPlayerAfterDismissingAppMode,
FABType? floatingActionButton,
YTHomePages? ytInitialHomePage,
YTSeekActionMode? ytTapToSeek,
YTSeekActionMode? ytDragToSeek,
MostPlayedTimeRange? mostPlayedTimeRange,
DateRange? mostPlayedCustomDateRange,
bool? mostPlayedCustomisStartOfDay,
Expand Down Expand Up @@ -1443,6 +1451,12 @@ class SettingsController {
if (ytInitialHomePage != null) {
this.ytInitialHomePage.value = ytInitialHomePage;
}
if (ytTapToSeek != null) {
this.ytTapToSeek.value = ytTapToSeek;
}
if (ytDragToSeek != null) {
this.ytDragToSeek.value = ytDragToSeek;
}
if (mostPlayedTimeRange != null) {
this.mostPlayedTimeRange.value = mostPlayedTimeRange;
}
Expand Down
6 changes: 3 additions & 3 deletions lib/controller/video_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ class NamidaVideoWidget extends StatelessWidget {
await _verifyAndEnterFullScreen();
},
child: NamidaVideoControls(
widgetKey: fullscreen ? VideoController.inst.fullScreenControlskey : VideoController.inst.normalControlskey,
key: fullscreen ? VideoController.inst.fullScreenControlskey : VideoController.inst.normalControlskey,
isLocal: isLocal,
onMinimizeTap: () {
if (fullscreen) {
Expand Down Expand Up @@ -122,7 +122,7 @@ class VideoController {
final videoZoomAdditionalScale = 0.0.obs;

void updateShouldShowControls(double animationValue) {
final isExpanded = animationValue == 1.0;
final isExpanded = animationValue >= 0.95;
if (isExpanded) {
// YoutubeController.inst.startDimTimer(); // bad experience honestly
} else {
Expand All @@ -136,7 +136,7 @@ class VideoController {
}) async {
final aspect = Player.inst.videoPlayerInfo?.aspectRatio;
VideoController.inst.fullScreenVideoWidget ??= NamidaVideoControls(
widgetKey: VideoController.inst.fullScreenControlskey,
key: VideoController.inst.fullScreenControlskey,
isLocal: isLocal,
onMinimizeTap: () {
VideoController.inst.fullScreenVideoWidget = null;
Expand Down
6 changes: 6 additions & 0 deletions lib/core/enums.dart
Original file line number Diff line number Diff line change
Expand Up @@ -356,3 +356,9 @@ enum PlaylistAddDuplicateAction {
addAllAndRemoveOldOnes,
addOnlyMissing,
}

enum YTSeekActionMode {
minimizedMiniplayer,
expandedMiniplayer,
all,
}
8 changes: 8 additions & 0 deletions lib/core/extensions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import 'dart:async';
import 'dart:io';
import 'dart:math' as math;

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
Expand Down Expand Up @@ -132,6 +133,13 @@ extension StringListJoiner on Iterable<String?> {
}
}

extension ListieListieUtils<T> on List<T> {
T get random {
final index = math.Random().nextInt(length);
return this[index];
}
}

extension DisplayKeywords on int {
String get displayTrackKeyword => displayKeyword(lang.TRACK, lang.TRACKS);
String get displayDayKeyword => displayKeyword(lang.DAY, lang.DAYS);
Expand Down
9 changes: 9 additions & 0 deletions lib/core/namida_converter_ext.dart
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,10 @@ extension PlaylistAddDuplicateActionUtils on PlaylistAddDuplicateAction {
String toText() => _NamidaConverters.inst.getTitle(this);
}

extension YTSeekActionModeUtils on YTSeekActionMode {
String toText() => _NamidaConverters.inst.getTitle(this);
}

extension WidgetsPagess on Widget {
NamidaRoute toNamidaRoute() {
String name = '';
Expand Down Expand Up @@ -1495,6 +1499,11 @@ class _NamidaConverters {
PlaylistAddDuplicateAction.addAllAndRemoveOldOnes: lang.ADD_ALL_AND_REMOVE_OLD_ONES,
PlaylistAddDuplicateAction.addOnlyMissing: lang.ADD_ONLY_MISSING,
},
YTSeekActionMode: {
YTSeekActionMode.minimizedMiniplayer: lang.MINIMIZED_MINIPLAYER,
YTSeekActionMode.expandedMiniplayer: lang.EXPANDED_MINIPLAYER,
YTSeekActionMode.all: lang.ALL,
},
};

// ====================================================
Expand Down
5 changes: 5 additions & 0 deletions lib/core/translations/keys.dart
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ abstract class LanguageKeys {
String get DOWNLOADS_METADATA_TAGS => _getKey('DOWNLOADS_METADATA_TAGS');
String get DOWNLOADS_METADATA_TAGS_SUBTITLE => _getKey('DOWNLOADS_METADATA_TAGS_SUBTITLE');
String get DO_NOTHING => _getKey('DO_NOTHING');
String get DRAG_TO_SEEK => _getKey('DRAG_TO_SEEK');
String get DUCK_AUDIO => _getKey('DUCK_AUDIO');
String get DUPLICATED_ITEMS_ADDING => _getKey('DUPLICATED_ITEMS_ADDING');
String get DUPLICATED_TRACKS => _getKey('DUPLICATED_TRACKS');
Expand Down Expand Up @@ -208,6 +209,7 @@ abstract class LanguageKeys {
String get EXCLUDED_FODLERS => _getKey('EXCLUDED_FODLERS');
String get EXIT_APP_SUBTITLE => _getKey('EXIT_APP_SUBTITLE');
String get EXIT => _getKey('EXIT');
String get EXPANDED_MINIPLAYER => _getKey('EXPANDED_MINIPLAYER');
String get EXPORT_AS_M3U => _getKey('EXPORT_AS_M3U');
String get EXTENSION => _getKey('EXTENSION');
String get EXTERNAL_FILES => _getKey('EXTERNAL_FILES');
Expand Down Expand Up @@ -336,6 +338,7 @@ abstract class LanguageKeys {
String get METADATA_CACHE => _getKey('METADATA_CACHE');
String get METADATA_EDIT_FAILED => _getKey('METADATA_EDIT_FAILED');
String get METADATA_READ_FAILED => _getKey('METADATA_READ_FAILED');
String get MINIMIZED_MINIPLAYER => _getKey('MINIMIZED_MINIPLAYER');
String get MINIMUM_ONE_ITEM => _getKey('MINIMUM_ONE_ITEM');
String get MINIMUM_ONE_ITEM_SUBTITLE => _getKey('MINIMUM_ONE_ITEM_SUBTITLE');
String get MIN_FILE_DURATION => _getKey('MIN_FILE_DURATION');
Expand Down Expand Up @@ -511,6 +514,7 @@ abstract class LanguageKeys {
String get SEARCH => _getKey('SEARCH');
String get SEARCH_YOUTUBE => _getKey('SEARCH_YOUTUBE');
String get SECONDS => _getKey('SECONDS');
String get SEEKBAR => _getKey('SEEKBAR');
String get SEEK_DURATION_INFO => _getKey('SEEK_DURATION_INFO');
String get SEEK_DURATION => _getKey('SEEK_DURATION');
String get SELECT_ALL => _getKey('SELECT_ALL');
Expand Down Expand Up @@ -568,6 +572,7 @@ abstract class LanguageKeys {
String get SYNOPSIS => _getKey('SYNOPSIS');
String get TAG_FIELDS => _getKey('TAG_FIELDS');
String get TAGS => _getKey('TAGS');
String get TAP_TO_SEEK => _getKey('TAP_TO_SEEK');
String get THEME_MODE_DARK => _getKey('THEME_MODE_DARK');
String get THEME_MODE_LIGHT => _getKey('THEME_MODE_LIGHT');
String get THEME_MODE_SYSTEM => _getKey('THEME_MODE_SYSTEM');
Expand Down
26 changes: 1 addition & 25 deletions lib/packages/miniplayer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1018,31 +1018,7 @@ class _NamidaMiniPlayerState extends State<NamidaMiniPlayer> {
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: [
Obx(
() => IconButton(
visualDensity: VisualDensity.compact,
style: const ButtonStyle(tapTargetSize: MaterialTapTargetSize.shrinkWrap),
padding: const EdgeInsets.all(2.0),
tooltip: settings.playerRepeatMode.value.toText().replaceFirst('_NUM_', Player.inst.numberOfRepeats.toString()),
onPressed: () {
final e = settings.playerRepeatMode.value.nextElement(RepeatMode.values);
settings.save(playerRepeatMode: e);
},
icon: Stack(
alignment: Alignment.center,
children: [
Icon(
settings.playerRepeatMode.value.toIcon(),
size: 20.0,
color: context.theme.colorScheme.onSecondaryContainer,
),
if (settings.playerRepeatMode.value == RepeatMode.forNtimes)
Text(
Player.inst.numberOfRepeats.toString(),
style: context.textTheme.displaySmall?.copyWith(color: context.theme.colorScheme.onSecondaryContainer),
),
],
),
const RepeatModeIconButton(),
),
),
GestureDetector(
Expand Down
67 changes: 67 additions & 0 deletions lib/ui/widgets/custom_widgets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3492,3 +3492,70 @@ class QueueUtilsRow extends StatelessWidget {
);
}
}

class RepeatModeIconButton extends StatelessWidget {
final bool compact;
final Color? color;
final VoidCallback? onPressed;

const RepeatModeIconButton({
super.key,
this.compact = false,
this.color,
this.onPressed,
});

void _switchMode() {
final e = settings.playerRepeatMode.value.nextElement(RepeatMode.values);
settings.save(playerRepeatMode: e);
}

@override
Widget build(BuildContext context) {
final iconColor = color ?? context.theme.colorScheme.onSecondaryContainer;
return Obx(
() {
final tooltip = settings.playerRepeatMode.value.toText().replaceFirst('_NUM_', Player.inst.numberOfRepeats.toString());
final child = Stack(
alignment: Alignment.center,
children: [
Icon(
settings.playerRepeatMode.value.toIcon(),
size: 20.0,
color: iconColor,
),
if (settings.playerRepeatMode.value == RepeatMode.forNtimes)
Text(
Player.inst.numberOfRepeats.toString(),
style: context.textTheme.displaySmall?.copyWith(color: iconColor),
),
],
);
return compact
? NamidaIconButton(
tooltip: tooltip,
icon: null,
horizontalPadding: 0.0,
padding: EdgeInsets.zero,
iconSize: 20.0,
onPressed: () {
onPressed?.call();
_switchMode();
},
child: child,
)
: IconButton(
visualDensity: VisualDensity.compact,
style: const ButtonStyle(tapTargetSize: MaterialTapTargetSize.shrinkWrap),
padding: const EdgeInsets.all(2.0),
tooltip: tooltip,
onPressed: () {
onPressed?.call();
_switchMode();
},
icon: child,
);
},
);
}
}
Loading

0 comments on commit 2bc89dc

Please sign in to comment.