Skip to content

Commit

Permalink
feat: artwork gestures
Browse files Browse the repository at this point in the history
- zoom in to enter lrc fullscreen (always, when lrc is visible)
- zoom in/out to scale artwork (config, when lrc is not visible)
- double tap to toggle lyrics (config, only when lrc is not visible to prevent delaying)
  • Loading branch information
MSOB7YY committed Dec 3, 2023
1 parent 1390d86 commit 2031d74
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 38 deletions.
21 changes: 21 additions & 0 deletions lib/controller/settings_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ class SettingsController {
final RxBool enableLyrics = false.obs;
final Rx<VideoPlaybackSource> videoPlaybackSource = VideoPlaybackSource.auto.obs;
final RxList<String> youtubeVideoQualities = ['480p', '360p', '240p', '144p'].obs;
final RxDouble animatingThumbnailScaleMultiplier = 1.0.obs;
final RxInt animatingThumbnailIntensity = 25.obs;
final RxBool animatingThumbnailInversed = false.obs;
final RxBool enablePartyModeInMiniplayer = false.obs;
Expand Down Expand Up @@ -168,6 +169,8 @@ class SettingsController {
final RxBool dismissibleMiniplayer = false.obs;
final RxBool enableClipboardMonitoring = false.obs;
final RxBool ytIsAudioOnlyMode = false.obs;
final RxBool artworkGestureScale = false.obs;
final RxBool artworkGestureDoubleTapLRC = true.obs;
final RxList<TagField> tagFieldsToEdit = <TagField>[
TagField.trackNumber,
TagField.year,
Expand Down Expand Up @@ -389,6 +392,7 @@ class SettingsController {
videoPlaybackSource.value = VideoPlaybackSource.values.getEnum(json['VideoPlaybackSource']) ?? videoPlaybackSource.value;
youtubeVideoQualities.value = List<String>.from(json['youtubeVideoQualities'] ?? youtubeVideoQualities);

animatingThumbnailScaleMultiplier.value = json['animatingThumbnailScaleMultiplier'] ?? animatingThumbnailScaleMultiplier.value;
animatingThumbnailIntensity.value = json['animatingThumbnailIntensity'] ?? animatingThumbnailIntensity.value;
animatingThumbnailInversed.value = json['animatingThumbnailInversed'] ?? animatingThumbnailInversed.value;
enablePartyModeInMiniplayer.value = json['enablePartyModeInMiniplayer'] ?? enablePartyModeInMiniplayer.value;
Expand Down Expand Up @@ -440,6 +444,8 @@ class SettingsController {
dismissibleMiniplayer.value = json['dismissibleMiniplayer'] ?? dismissibleMiniplayer.value;
enableClipboardMonitoring.value = json['enableClipboardMonitoring'] ?? enableClipboardMonitoring.value;
ytIsAudioOnlyMode.value = json['ytIsAudioOnlyMode'] ?? ytIsAudioOnlyMode.value;
artworkGestureScale.value = json['artworkGestureScale'] ?? artworkGestureScale.value;
artworkGestureDoubleTapLRC.value = json['artworkGestureDoubleTapLRC'] ?? artworkGestureDoubleTapLRC.value;

final listFromStorage = List<String>.from(json['tagFieldsToEdit'] ?? []);
tagFieldsToEdit.value = listFromStorage.isNotEmpty ? List<TagField>.from(listFromStorage.map((e) => TagField.values.getEnum(e))) : tagFieldsToEdit;
Expand Down Expand Up @@ -605,6 +611,7 @@ class SettingsController {
'enableLyrics': enableLyrics.value,
'videoPlaybackSource': videoPlaybackSource.value.convertToString,
'youtubeVideoQualities': youtubeVideoQualities.toList(),
'animatingThumbnailScaleMultiplier': animatingThumbnailScaleMultiplier.value,
'animatingThumbnailIntensity': animatingThumbnailIntensity.value,
'animatingThumbnailInversed': animatingThumbnailInversed.value,
'enablePartyModeInMiniplayer': enablePartyModeInMiniplayer.value,
Expand Down Expand Up @@ -656,6 +663,8 @@ class SettingsController {
'dismissibleMiniplayer': dismissibleMiniplayer.value,
'enableClipboardMonitoring': enableClipboardMonitoring.value,
'ytIsAudioOnlyMode': ytIsAudioOnlyMode.value,
'artworkGestureScale': artworkGestureScale.value,
'artworkGestureDoubleTapLRC': artworkGestureDoubleTapLRC.value,
'tagFieldsToEdit': tagFieldsToEdit.mapped((element) => element.convertToString),
'wakelockMode': wakelockMode.value.convertToString,
'localVideoMatchingType': localVideoMatchingType.value.convertToString,
Expand Down Expand Up @@ -782,6 +791,7 @@ class SettingsController {
bool? enableLyrics,
VideoPlaybackSource? videoPlaybackSource,
List<String>? youtubeVideoQualities,
double? animatingThumbnailScaleMultiplier,
int? animatingThumbnailIntensity,
bool? animatingThumbnailInversed,
bool? enablePartyModeInMiniplayer,
Expand Down Expand Up @@ -841,6 +851,8 @@ class SettingsController {
bool? dismissibleMiniplayer,
bool? enableClipboardMonitoring,
bool? ytIsAudioOnlyMode,
bool? artworkGestureScale,
bool? artworkGestureDoubleTapLRC,
List<TagField>? tagFieldsToEdit,
WakelockMode? wakelockMode,
LocalVideoMatchingType? localVideoMatchingType,
Expand Down Expand Up @@ -1130,6 +1142,9 @@ class SettingsController {
if (videoPlaybackSource != null) {
this.videoPlaybackSource.value = videoPlaybackSource;
}
if (animatingThumbnailScaleMultiplier != null) {
this.animatingThumbnailScaleMultiplier.value = animatingThumbnailScaleMultiplier;
}
if (animatingThumbnailIntensity != null) {
this.animatingThumbnailIntensity.value = animatingThumbnailIntensity;
}
Expand Down Expand Up @@ -1307,6 +1322,12 @@ class SettingsController {
if (ytIsAudioOnlyMode != null) {
this.ytIsAudioOnlyMode.value = ytIsAudioOnlyMode;
}
if (artworkGestureScale != null) {
this.artworkGestureScale.value = artworkGestureScale;
}
if (artworkGestureDoubleTapLRC != null) {
this.artworkGestureDoubleTapLRC.value = artworkGestureDoubleTapLRC;
}
if (tagFieldsToEdit != null) {
tagFieldsToEdit.loop((d, index) {
if (!this.tagFieldsToEdit.contains(d)) {
Expand Down
3 changes: 3 additions & 0 deletions lib/core/translations/keys.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ abstract class LanguageKeys {
String get ARTISTS => _getKey('ARTISTS');
String get ARTWORK => _getKey('ARTWORK');
String get ARTWORKS => _getKey('ARTWORKS');
String get ARTWORK_GESTURES => _getKey('ARTWORK_GESTURES');
String get AUDIO => _getKey('AUDIO');
String get AUDIO_CACHE => _getKey('AUDIO_CACHE');
String get AUDIO_ONLY => _getKey('AUDIO_ONLY');
Expand Down Expand Up @@ -154,6 +155,7 @@ abstract class LanguageKeys {
String get DONE => _getKey('DONE');
String get DONT_ASK_AGAIN => _getKey('DONT_ASK_AGAIN');
String get DONT_RESTORE_POSITION => _getKey('DONT_RESTORE_POSITION');
String get DOUBLE_TAP_TO_TOGGLE_LYRICS => _getKey('DOUBLE_TAP_TO_TOGGLE_LYRICS');
String get DOWNLOAD => _getKey('DOWNLOAD');
String get DOWNLOADING_WILL_OVERRIDE_IT => _getKey('DOWNLOADING_WILL_OVERRIDE_IT');
String get DOWNLOADS => _getKey('DOWNLOADS');
Expand Down Expand Up @@ -483,6 +485,7 @@ abstract class LanguageKeys {
String get SAMPLE_RATE => _getKey('SAMPLE_RATE');
String get SAVE => _getKey('SAVE');
String get SAVED_IN => _getKey('SAVED_IN');
String get SCALE_MULTIPLIER => _getKey('SCALE_MULTIPLIER');
String get SEARCH => _getKey('SEARCH');
String get SEARCH_YOUTUBE => _getKey('SEARCH_YOUTUBE');
String get SECONDS => _getKey('SECONDS');
Expand Down
17 changes: 17 additions & 0 deletions lib/packages/lyrics_lrc_parsed_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,23 @@ class LyricsLRCParsedView extends StatefulWidget {
}

class LyricsLRCParsedViewState extends State<LyricsLRCParsedView> {
void enterFullScreen() {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) {
return LyricsLRCParsedView(
key: Lyrics.inst.lrcViewKeyFullscreen,
totalDuration: widget.totalDuration,
cp: widget.cp,
lrc: currentLRC,
videoOrImage: const SizedBox(),
isFullScreenView: true,
);
},
),
);
}

late final ItemScrollController controller;
late final ItemPositionsListener positionListener;

Expand Down
115 changes: 79 additions & 36 deletions lib/packages/miniplayer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2101,6 +2101,10 @@ class _TrackInfo extends StatelessWidget {
}
}

double _previousScale = 1.0;
final _lrcAdditionalScale = 0.0.obs;
bool _isScalingLRC = false;

class _AnimatingTrackImage extends StatelessWidget {
final Track track;
final double cp;
Expand All @@ -2116,44 +2120,83 @@ class _AnimatingTrackImage extends StatelessWidget {
return Padding(
padding: EdgeInsets.all(12.0 * (1 - cp)),
child: Obx(
() {
final additionalScale = VideoController.inst.videoZoomAdditionalScale.value;
final finalScale = (additionalScale * 0.02) + WaveformController.inst.getCurrentAnimatingScale(Player.inst.nowPlayingPosition);
final isInversed = settings.animatingThumbnailInversed.value;
return AnimatedScale(
duration: const Duration(milliseconds: 100),
scale: isInversed ? 1.22 - finalScale : 1.13 + finalScale,
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: VideoController.inst.shouldShowVideo
? ClipRRect(
borderRadius: BorderRadius.circular((6.0 + 10.0 * cp).multipliedRadius),
child: LyricsWrapper(
key: Key(track.path),
track: track,
cp: cp,
child: GestureDetector(
onTap: () => Player.inst.refreshVideoSeekPosition(),
onDoubleTap: () => VideoController.inst.toggleFullScreenVideoView(),
child: const NamidaVideoWidget(
key: Key('video_widget'),
enableControls: false,
() => GestureDetector(
// -- only when lrc view is not visible, to prevent other gestures delaying.
onDoubleTap: settings.artworkGestureDoubleTapLRC.value && Lyrics.inst.currentLyricsLRC.value == null
? () {
settings.save(enableLyrics: !settings.enableLyrics.value);
Lyrics.inst.updateLyrics(track);
}
: null,
onScaleStart: (details) {
final lrcState = Lyrics.inst.lrcViewKey?.currentState;
final lrcVisible = lrcState != null;
_isScalingLRC = lrcVisible;
_previousScale = lrcVisible ? 1.0 : settings.animatingThumbnailScaleMultiplier.value;
},
onScaleUpdate: (details) {
if (_isScalingLRC || settings.artworkGestureScale.value) {
final m = (details.scale * _previousScale);
if (_isScalingLRC) {
_lrcAdditionalScale.value = m;
} else {
settings.save(animatingThumbnailScaleMultiplier: m.clamp(0.4, 1.5));
}
}
},
onScaleEnd: (details) {
final lrcState = Lyrics.inst.lrcViewKey?.currentState;
if (lrcState != null) {
final pps = details.velocity.pixelsPerSecond;
if (pps.dx > 0 || pps.dy > 0) {
lrcState.enterFullScreen();
}
}
_lrcAdditionalScale.value = 0.0;
},
child: Obx(
() {
final additionalScaleVideo = 0.02 * VideoController.inst.videoZoomAdditionalScale.value;
final additionalScaleLRC = 0.02 * _lrcAdditionalScale.value;
final finalScale = additionalScaleLRC + additionalScaleVideo + WaveformController.inst.getCurrentAnimatingScale(Player.inst.nowPlayingPosition);
final isInversed = settings.animatingThumbnailInversed.value;
final userScaleMultiplier = settings.animatingThumbnailScaleMultiplier.value;
return AnimatedScale(
duration: const Duration(milliseconds: 100),
scale: (isInversed ? 1.22 - finalScale : 1.13 + finalScale) * userScaleMultiplier,
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: VideoController.inst.shouldShowVideo
? ClipRRect(
borderRadius: BorderRadius.circular((6.0 + 10.0 * cp).multipliedRadius),
child: LyricsWrapper(
key: Key(track.path),
track: track,
cp: cp,
child: GestureDetector(
onTap: () => Player.inst.refreshVideoSeekPosition(),
onDoubleTap: () => VideoController.inst.toggleFullScreenVideoView(),
child: const NamidaVideoWidget(
key: Key('video_widget'),
enableControls: false,
),
),
),
)
: LyricsWrapper(
key: Key(track.path),
track: track,
cp: cp,
child: _TrackImage(
track: track,
cp: cp,
),
),
),
)
: LyricsWrapper(
key: Key(track.path),
track: track,
cp: cp,
child: _TrackImage(
track: track,
cp: cp,
),
),
),
);
},
),
);
},
),
),
),
);
}
Expand Down
67 changes: 65 additions & 2 deletions lib/ui/widgets/settings/customization_settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ enum _CustomizationSettingsKeys {
movingParticles,
thumbAnimationIntensity,
thumbInverseAnimation,
artworkGesture,
waveformBarsCount,
displayAudioInfo,
displayArtistBeforeTitle,
Expand Down Expand Up @@ -99,6 +100,7 @@ class CustomizationSettings extends SettingSubpageProvider {
_CustomizationSettingsKeys.movingParticles: [lang.ENABLE_MINIPLAYER_PARTICLES],
_CustomizationSettingsKeys.thumbAnimationIntensity: [lang.ANIMATING_THUMBNAIL_INTENSITY],
_CustomizationSettingsKeys.thumbInverseAnimation: [lang.ANIMATING_THUMBNAIL_INVERSED, lang.ANIMATING_THUMBNAIL_INVERSED_SUBTITLE],
_CustomizationSettingsKeys.artworkGesture: [lang.ARTWORK_GESTURES],
_CustomizationSettingsKeys.waveformBarsCount: [lang.WAVEFORM_BARS_COUNT],
_CustomizationSettingsKeys.displayAudioInfo: [lang.DISPLAY_AUDIO_INFO_IN_MINIPLAYER],
_CustomizationSettingsKeys.displayArtistBeforeTitle: [lang.DISPLAY_ARTIST_BEFORE_TITLE],
Expand Down Expand Up @@ -293,7 +295,7 @@ class CustomizationSettings extends SettingSubpageProvider {
),
_getAlbumCustomizationsTile(),
_getTrackTileCustomizationsTile(context),
_getMiniplayerCustomizationsTile(),
_getMiniplayerCustomizationsTile(context),
],
),
);
Expand Down Expand Up @@ -754,7 +756,7 @@ class CustomizationSettings extends SettingSubpageProvider {
);
}

Widget _getMiniplayerCustomizationsTile() {
Widget _getMiniplayerCustomizationsTile(BuildContext context) {
return getItemWrapper(
key: _CustomizationSettingsKeys.MINIPLAYERCUSTOMIZATION,
child: NamidaExpansionTile(
Expand Down Expand Up @@ -917,6 +919,67 @@ class CustomizationSettings extends SettingSubpageProvider {
),
),
),
getItemWrapper(
key: _CustomizationSettingsKeys.artworkGesture,
child: NamidaExpansionTile(
icon: Broken.gallery,
iconColor: context.defaultIconColor(),
initiallyExpanded: true,
titleText: lang.ARTWORK_GESTURES,
childrenPadding: const EdgeInsets.symmetric(horizontal: 8.0),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
NamidaIconButton(
tooltip: lang.RESTORE_DEFAULTS,
icon: Broken.refresh,
iconSize: 20.0,
onPressed: () {
settings.save(
artworkGestureScale: false,
artworkGestureDoubleTapLRC: true,
animatingThumbnailScaleMultiplier: 1.0,
);
},
),
const SizedBox(width: 4.0),
const Icon(Broken.arrow_down_2, size: 20.0),
const SizedBox(width: 12.0),
],
),
children: [
Obx(
() => CustomSwitchListTile(
visualDensity: VisualDensity.compact,
icon: Broken.maximize,
title: lang.SCALE_MULTIPLIER,
subtitle: "${(settings.animatingThumbnailScaleMultiplier.value * 100).round()}%",
value: settings.artworkGestureScale.value,
onChanged: (value) {
settings.save(artworkGestureScale: !value);
},
),
),
Obx(
() => CustomSwitchListTile(
visualDensity: VisualDensity.compact,
leading: const StackedIcon(
baseIcon: Broken.document,
secondaryIcon: Broken.blend_2,
secondaryIconSize: 12.0,
),
title: lang.DOUBLE_TAP_TO_TOGGLE_LYRICS,
value: settings.artworkGestureDoubleTapLRC.value,
onChanged: (value) {
settings.save(artworkGestureDoubleTapLRC: !value);
},
),
),
const SizedBox(height: 6.0),
],
),
),
const SizedBox(height: 6.0),
getItemWrapper(
key: _CustomizationSettingsKeys.waveformBarsCount,
child: Obx(
Expand Down

0 comments on commit 2031d74

Please sign in to comment.