mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 16:05:18 +00:00
feat: add piped search mode
This commit is contained in:
parent
a9b5a714e4
commit
17a25a501e
@ -89,4 +89,6 @@ abstract class SpotubeIcons {
|
|||||||
static const timer = FeatherIcons.clock;
|
static const timer = FeatherIcons.clock;
|
||||||
static const logs = FeatherIcons.fileText;
|
static const logs = FeatherIcons.fileText;
|
||||||
static const clipboard = FeatherIcons.clipboard;
|
static const clipboard = FeatherIcons.clipboard;
|
||||||
|
static const youtube = FeatherIcons.youtube;
|
||||||
|
static const skip = FeatherIcons.fastForward;
|
||||||
}
|
}
|
||||||
|
@ -247,5 +247,6 @@
|
|||||||
"custom_hours": "Custom Hours",
|
"custom_hours": "Custom Hours",
|
||||||
"logs": "Logs",
|
"logs": "Logs",
|
||||||
"developers": "Developers",
|
"developers": "Developers",
|
||||||
"not_logged_in": "You're not logged in"
|
"not_logged_in": "You're not logged in",
|
||||||
|
"search_mode": "Search Mode"
|
||||||
}
|
}
|
@ -24,6 +24,7 @@ import 'package:spotube/hooks/use_disable_battery_optimizations.dart';
|
|||||||
import 'package:spotube/l10n/l10n.dart';
|
import 'package:spotube/l10n/l10n.dart';
|
||||||
import 'package:spotube/models/logger.dart';
|
import 'package:spotube/models/logger.dart';
|
||||||
import 'package:spotube/models/matched_track.dart';
|
import 'package:spotube/models/matched_track.dart';
|
||||||
|
import 'package:spotube/models/skip_segment.dart';
|
||||||
import 'package:spotube/provider/palette_provider.dart';
|
import 'package:spotube/provider/palette_provider.dart';
|
||||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||||
import 'package:spotube/services/audio_player/audio_player.dart';
|
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||||
@ -101,6 +102,7 @@ Future<void> main(List<String> rawArgs) async {
|
|||||||
connectivity: FlQueryConnectivityPlusAdapter(),
|
connectivity: FlQueryConnectivityPlusAdapter(),
|
||||||
);
|
);
|
||||||
Hive.registerAdapter(MatchedTrackAdapter());
|
Hive.registerAdapter(MatchedTrackAdapter());
|
||||||
|
Hive.registerAdapter(SkipSegmentAdapter());
|
||||||
|
|
||||||
await Hive.openLazyBox<MatchedTrack>(
|
await Hive.openLazyBox<MatchedTrack>(
|
||||||
MatchedTrack.boxName,
|
MatchedTrack.boxName,
|
||||||
|
24
lib/models/skip_segment.dart
Normal file
24
lib/models/skip_segment.dart
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import 'package:hive/hive.dart';
|
||||||
|
|
||||||
|
part 'skip_segment.g.dart';
|
||||||
|
|
||||||
|
@HiveType(typeId: 2)
|
||||||
|
class SkipSegment {
|
||||||
|
@HiveField(0)
|
||||||
|
final int start;
|
||||||
|
@HiveField(1)
|
||||||
|
final int end;
|
||||||
|
SkipSegment(this.start, this.end);
|
||||||
|
|
||||||
|
static const boxName = "oss.krtirtho.spotube.skip_segments";
|
||||||
|
static LazyBox<SkipSegment> get box => Hive.lazyBox<SkipSegment>(boxName);
|
||||||
|
|
||||||
|
SkipSegment.fromJson(Map<String, dynamic> json)
|
||||||
|
: start = json['start'],
|
||||||
|
end = json['end'];
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'start': start,
|
||||||
|
'end': end,
|
||||||
|
};
|
||||||
|
}
|
44
lib/models/skip_segment.g.dart
Normal file
44
lib/models/skip_segment.g.dart
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'skip_segment.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// TypeAdapterGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
class SkipSegmentAdapter extends TypeAdapter<SkipSegment> {
|
||||||
|
@override
|
||||||
|
final int typeId = 2;
|
||||||
|
|
||||||
|
@override
|
||||||
|
SkipSegment read(BinaryReader reader) {
|
||||||
|
final numOfFields = reader.readByte();
|
||||||
|
final fields = <int, dynamic>{
|
||||||
|
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||||
|
};
|
||||||
|
return SkipSegment(
|
||||||
|
fields[0] as int,
|
||||||
|
fields[1] as int,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void write(BinaryWriter writer, SkipSegment obj) {
|
||||||
|
writer
|
||||||
|
..writeByte(2)
|
||||||
|
..writeByte(0)
|
||||||
|
..write(obj.start)
|
||||||
|
..writeByte(1)
|
||||||
|
..write(obj.end);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => typeId.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
other is SkipSegmentAdapter &&
|
||||||
|
runtimeType == other.runtimeType &&
|
||||||
|
typeId == other.typeId;
|
||||||
|
}
|
@ -1,19 +1,20 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:catcher/catcher.dart';
|
||||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'package:piped_client/piped_client.dart';
|
import 'package:piped_client/piped_client.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/extensions/album_simple.dart';
|
import 'package:spotube/extensions/album_simple.dart';
|
||||||
import 'package:spotube/extensions/artist_simple.dart';
|
import 'package:spotube/extensions/artist_simple.dart';
|
||||||
|
import 'package:spotube/models/logger.dart';
|
||||||
import 'package:spotube/models/matched_track.dart';
|
import 'package:spotube/models/matched_track.dart';
|
||||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||||
import 'package:spotube/utils/platform.dart';
|
import 'package:spotube/utils/platform.dart';
|
||||||
import 'package:spotube/utils/service_utils.dart';
|
import 'package:spotube/utils/service_utils.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
typedef SkipSegment = ({int start, int end});
|
|
||||||
|
|
||||||
class SpotubeTrack extends Track {
|
class SpotubeTrack extends Track {
|
||||||
final PipedStreamResponse ytTrack;
|
final PipedStreamResponse ytTrack;
|
||||||
final String ytUri;
|
final String ytUri;
|
||||||
@ -68,8 +69,9 @@ class SpotubeTrack extends Track {
|
|||||||
|
|
||||||
static Future<List<PipedSearchItemStream>> fetchSiblings(
|
static Future<List<PipedSearchItemStream>> fetchSiblings(
|
||||||
Track track,
|
Track track,
|
||||||
PipedClient client,
|
PipedClient client, [
|
||||||
) async {
|
PipedFilter filter = PipedFilter.musicSongs,
|
||||||
|
]) async {
|
||||||
final artists = (track.artists ?? [])
|
final artists = (track.artists ?? [])
|
||||||
.map((ar) => ar.name)
|
.map((ar) => ar.name)
|
||||||
.toList()
|
.toList()
|
||||||
@ -82,12 +84,8 @@ class SpotubeTrack extends Track {
|
|||||||
onlyCleanArtist: true,
|
onlyCleanArtist: true,
|
||||||
).trim();
|
).trim();
|
||||||
|
|
||||||
final List<PipedSearchItemStream> siblings = await client
|
final List<PipedSearchItemStream> siblings =
|
||||||
.search(
|
await client.search("$title - ${artists.join(", ")}", filter).then(
|
||||||
"$title - ${artists.join(", ")}",
|
|
||||||
PipedFilter.musicSongs,
|
|
||||||
)
|
|
||||||
.then(
|
|
||||||
(res) {
|
(res) {
|
||||||
final siblings = res.items
|
final siblings = res.items
|
||||||
.whereType<PipedSearchItemStream>()
|
.whereType<PipedSearchItemStream>()
|
||||||
@ -122,7 +120,14 @@ class SpotubeTrack extends Track {
|
|||||||
if (matchedCachedTrack != null) {
|
if (matchedCachedTrack != null) {
|
||||||
ytVideo = await client.streams(matchedCachedTrack.youtubeId);
|
ytVideo = await client.streams(matchedCachedTrack.youtubeId);
|
||||||
} else {
|
} else {
|
||||||
siblings = await fetchSiblings(track, client);
|
siblings = await fetchSiblings(
|
||||||
|
track,
|
||||||
|
client,
|
||||||
|
switch (preferences.searchMode) {
|
||||||
|
SearchMode.youtube => PipedFilter.video,
|
||||||
|
SearchMode.youtubeMusic => PipedFilter.musicSongs,
|
||||||
|
},
|
||||||
|
);
|
||||||
if (siblings.isEmpty) {
|
if (siblings.isEmpty) {
|
||||||
throw Exception("Failed to find any results for ${track.name}");
|
throw Exception("Failed to find any results for ${track.name}");
|
||||||
}
|
}
|
||||||
@ -229,10 +234,17 @@ class SpotubeTrack extends Track {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<SpotubeTrack> populatedCopy(PipedClient client) async {
|
Future<SpotubeTrack> populatedCopy(
|
||||||
|
PipedClient client,
|
||||||
|
PipedFilter filter,
|
||||||
|
) async {
|
||||||
if (this.siblings.isNotEmpty) return this;
|
if (this.siblings.isNotEmpty) return this;
|
||||||
|
|
||||||
final siblings = await fetchSiblings(this, client);
|
final siblings = await fetchSiblings(
|
||||||
|
this,
|
||||||
|
client,
|
||||||
|
filter,
|
||||||
|
);
|
||||||
|
|
||||||
return SpotubeTrack.fromTrack(
|
return SpotubeTrack.fromTrack(
|
||||||
track: this,
|
track: this,
|
||||||
|
@ -324,6 +324,45 @@ class SettingsPage extends HookConsumerWidget {
|
|||||||
Text(error.toString()),
|
Text(error.toString()),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
AdaptiveSelectTile<SearchMode>(
|
||||||
|
secondary: const Icon(SpotubeIcons.youtube),
|
||||||
|
title: Text(context.l10n.search_mode),
|
||||||
|
value: preferences.searchMode,
|
||||||
|
options: SearchMode.values
|
||||||
|
.map((e) => DropdownMenuItem(
|
||||||
|
value: e,
|
||||||
|
child: Text(e.label),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value == null) return;
|
||||||
|
preferences.setSearchMode(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
AnimatedOpacity(
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
opacity:
|
||||||
|
preferences.searchMode == SearchMode.youtubeMusic
|
||||||
|
? 0
|
||||||
|
: 1,
|
||||||
|
child: AnimatedSize(
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
child: SizedBox(
|
||||||
|
height: preferences.searchMode ==
|
||||||
|
SearchMode.youtubeMusic
|
||||||
|
? 0
|
||||||
|
: 50,
|
||||||
|
child: SwitchListTile(
|
||||||
|
secondary: const Icon(SpotubeIcons.skip),
|
||||||
|
title: Text(context.l10n.skip_non_music),
|
||||||
|
value: preferences.skipNonMusic,
|
||||||
|
onChanged: (state) {
|
||||||
|
preferences.setSkipNonMusic(state);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
secondary: const Icon(SpotubeIcons.download),
|
secondary: const Icon(SpotubeIcons.download),
|
||||||
title: Text(context.l10n.pre_download_play),
|
title: Text(context.l10n.pre_download_play),
|
||||||
|
@ -1,12 +1,19 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:catcher/catcher.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:http/http.dart';
|
||||||
import 'package:palette_generator/palette_generator.dart';
|
import 'package:palette_generator/palette_generator.dart';
|
||||||
import 'package:piped_client/piped_client.dart';
|
import 'package:piped_client/piped_client.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
import 'package:spotube/components/shared/image/universal_image.dart';
|
||||||
import 'package:spotube/models/local_track.dart';
|
import 'package:spotube/models/local_track.dart';
|
||||||
|
import 'package:spotube/models/logger.dart';
|
||||||
|
import 'package:spotube/models/skip_segment.dart';
|
||||||
import 'package:spotube/models/spotube_track.dart';
|
import 'package:spotube/models/spotube_track.dart';
|
||||||
import 'package:spotube/provider/blacklist_provider.dart';
|
import 'package:spotube/provider/blacklist_provider.dart';
|
||||||
import 'package:spotube/provider/palette_provider.dart';
|
import 'package:spotube/provider/palette_provider.dart';
|
||||||
@ -62,7 +69,9 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier<ProxyPlaylist>
|
|||||||
() async {
|
() async {
|
||||||
notificationService = await AudioServices.create(ref, this);
|
notificationService = await AudioServices.create(ref, this);
|
||||||
|
|
||||||
audioPlayer.activeSourceChangedStream.listen((newActiveSource) {
|
(String, List<SkipSegment>)? currentSegments;
|
||||||
|
bool isFetchingSegments = false;
|
||||||
|
audioPlayer.activeSourceChangedStream.listen((newActiveSource) async {
|
||||||
final newActiveTrack =
|
final newActiveTrack =
|
||||||
mapSourcesToTracks([newActiveSource]).firstOrNull;
|
mapSourcesToTracks([newActiveSource]).firstOrNull;
|
||||||
|
|
||||||
@ -78,6 +87,10 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier<ProxyPlaylist>
|
|||||||
.indexWhere((element) => element.id == newActiveTrack.id),
|
.indexWhere((element) => element.id == newActiveTrack.id),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
isFetchingSegments = true;
|
||||||
|
|
||||||
|
isFetchingSegments = false;
|
||||||
|
|
||||||
if (preferences.albumColorSync) {
|
if (preferences.albumColorSync) {
|
||||||
updatePalette();
|
updatePalette();
|
||||||
}
|
}
|
||||||
@ -101,7 +114,9 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier<ProxyPlaylist>
|
|||||||
bool isPreSearching = false;
|
bool isPreSearching = false;
|
||||||
|
|
||||||
listenTo60Percent(percent) async {
|
listenTo60Percent(percent) async {
|
||||||
if (isPreSearching || audioPlayer.currentSource == null) return;
|
if (isPreSearching ||
|
||||||
|
audioPlayer.currentSource == null ||
|
||||||
|
audioPlayer.nextSource == null) return;
|
||||||
try {
|
try {
|
||||||
isPreSearching = true;
|
isPreSearching = true;
|
||||||
|
|
||||||
@ -112,6 +127,19 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier<ProxyPlaylist>
|
|||||||
|
|
||||||
if (track != null) {
|
if (track != null) {
|
||||||
state = state.copyWith(tracks: mergeTracks([track], state.tracks));
|
state = state.copyWith(tracks: mergeTracks([track], state.tracks));
|
||||||
|
if (currentSegments == null ||
|
||||||
|
(oldTrack?.id != null &&
|
||||||
|
currentSegments!.$1 != oldTrack!.id!) &&
|
||||||
|
!isFetchingSegments) {
|
||||||
|
isFetchingSegments = true;
|
||||||
|
currentSegments = (
|
||||||
|
audioPlayer.currentSource!,
|
||||||
|
await getAndCacheSkipSegments(
|
||||||
|
track.ytTrack.id,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
isFetchingSegments = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sometimes fetching can take a lot of time, so we need to check
|
/// Sometimes fetching can take a lot of time, so we need to check
|
||||||
@ -144,6 +172,34 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier<ProxyPlaylist>
|
|||||||
isPlayable(audioPlayer.nextSource!)) return;
|
isPlayable(audioPlayer.nextSource!)) return;
|
||||||
await audioPlayer.pause();
|
await audioPlayer.pause();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
audioPlayer.positionStream.listen((position) async {
|
||||||
|
if (preferences.searchMode == SearchMode.youtubeMusic ||
|
||||||
|
!preferences.skipNonMusic) return;
|
||||||
|
|
||||||
|
if (currentSegments == null ||
|
||||||
|
currentSegments!.$1 != state.activeTrack!.id! &&
|
||||||
|
!isFetchingSegments) {
|
||||||
|
isFetchingSegments = true;
|
||||||
|
currentSegments = (
|
||||||
|
audioPlayer.currentSource!,
|
||||||
|
await getAndCacheSkipSegments(
|
||||||
|
(state.activeTrack as SpotubeTrack).ytTrack.id,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
isFetchingSegments = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final (_, segments) = currentSegments!;
|
||||||
|
if (segments.isEmpty) return;
|
||||||
|
|
||||||
|
for (final segment in segments) {
|
||||||
|
if ((position.inSeconds >= segment.start &&
|
||||||
|
position.inSeconds < segment.end)) {
|
||||||
|
await audioPlayer.seek(Duration(seconds: segment.end));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}();
|
}();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -332,7 +388,13 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier<ProxyPlaylist>
|
|||||||
Future<void> populateSibling() async {
|
Future<void> populateSibling() async {
|
||||||
if (state.activeTrack is SpotubeTrack) {
|
if (state.activeTrack is SpotubeTrack) {
|
||||||
final activeTrackWithSiblingsForSure =
|
final activeTrackWithSiblingsForSure =
|
||||||
await (state.activeTrack as SpotubeTrack).populatedCopy(pipedClient);
|
await (state.activeTrack as SpotubeTrack).populatedCopy(
|
||||||
|
pipedClient,
|
||||||
|
switch (preferences.searchMode) {
|
||||||
|
SearchMode.youtube => PipedFilter.video,
|
||||||
|
SearchMode.youtubeMusic => PipedFilter.musicSongs,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
tracks: mergeTracks([activeTrackWithSiblingsForSure], state.tracks),
|
tracks: mergeTracks([activeTrackWithSiblingsForSure], state.tracks),
|
||||||
@ -449,6 +511,64 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier<ProxyPlaylist>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<SkipSegment>> getAndCacheSkipSegments(String id) async {
|
||||||
|
if (!preferences.skipNonMusic ||
|
||||||
|
preferences.searchMode != SearchMode.youtube) return [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
final box = await Hive.openLazyBox<List>(SkipSegment.boxName);
|
||||||
|
final cached = await box.get(id);
|
||||||
|
if (cached != null && cached.isNotEmpty) {
|
||||||
|
return List.castFrom<dynamic, SkipSegment>(cached);
|
||||||
|
}
|
||||||
|
|
||||||
|
final res = await get(Uri(
|
||||||
|
scheme: "https",
|
||||||
|
host: "sponsor.ajay.app",
|
||||||
|
path: "/api/skipSegments",
|
||||||
|
queryParameters: {
|
||||||
|
"videoID": id,
|
||||||
|
"category": [
|
||||||
|
'sponsor',
|
||||||
|
'selfpromo',
|
||||||
|
'interaction',
|
||||||
|
'intro',
|
||||||
|
'outro',
|
||||||
|
'music_offtopic'
|
||||||
|
],
|
||||||
|
"actionType": 'skip'
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
|
if (res.body == "Not Found") {
|
||||||
|
return List.castFrom<dynamic, SkipSegment>([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
final data = jsonDecode(res.body) as List;
|
||||||
|
final segments = data.map((obj) {
|
||||||
|
final start = obj["segment"].first.toInt();
|
||||||
|
final end = obj["segment"].last.toInt();
|
||||||
|
return SkipSegment(
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
getLogger('getSkipSegments').v(
|
||||||
|
"[SponsorBlock] successfully fetched skip segments for $id",
|
||||||
|
);
|
||||||
|
|
||||||
|
await box.put(
|
||||||
|
id,
|
||||||
|
segments,
|
||||||
|
);
|
||||||
|
return List.castFrom<dynamic, SkipSegment>(segments);
|
||||||
|
} catch (e, stack) {
|
||||||
|
await box.put(id, []);
|
||||||
|
Catcher.reportCheckedError(e, stack);
|
||||||
|
return List.castFrom<dynamic, SkipSegment>([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
set state(state) {
|
set state(state) {
|
||||||
super.state = state;
|
super.state = state;
|
||||||
|
@ -28,6 +28,15 @@ enum CloseBehavior {
|
|||||||
close,
|
close,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum SearchMode {
|
||||||
|
youtube._internal('YouTube'),
|
||||||
|
youtubeMusic._internal('YouTubeMusic');
|
||||||
|
|
||||||
|
final String label;
|
||||||
|
|
||||||
|
const SearchMode._internal(this.label);
|
||||||
|
}
|
||||||
|
|
||||||
class UserPreferences extends PersistedChangeNotifier {
|
class UserPreferences extends PersistedChangeNotifier {
|
||||||
ThemeMode themeMode;
|
ThemeMode themeMode;
|
||||||
String recommendationMarket;
|
String recommendationMarket;
|
||||||
@ -52,6 +61,10 @@ class UserPreferences extends PersistedChangeNotifier {
|
|||||||
|
|
||||||
String pipedInstance;
|
String pipedInstance;
|
||||||
|
|
||||||
|
SearchMode searchMode;
|
||||||
|
|
||||||
|
bool skipNonMusic;
|
||||||
|
|
||||||
final Ref ref;
|
final Ref ref;
|
||||||
|
|
||||||
UserPreferences(
|
UserPreferences(
|
||||||
@ -70,6 +83,8 @@ class UserPreferences extends PersistedChangeNotifier {
|
|||||||
this.showSystemTrayIcon = true,
|
this.showSystemTrayIcon = true,
|
||||||
this.locale = const Locale("system", "system"),
|
this.locale = const Locale("system", "system"),
|
||||||
this.pipedInstance = "https://pipedapi.kavin.rocks",
|
this.pipedInstance = "https://pipedapi.kavin.rocks",
|
||||||
|
this.searchMode = SearchMode.youtubeMusic,
|
||||||
|
this.skipNonMusic = true,
|
||||||
}) : super() {
|
}) : super() {
|
||||||
if (downloadLocation.isEmpty) {
|
if (downloadLocation.isEmpty) {
|
||||||
_getDefaultDownloadDirectory().then(
|
_getDefaultDownloadDirectory().then(
|
||||||
@ -170,6 +185,18 @@ class UserPreferences extends PersistedChangeNotifier {
|
|||||||
updatePersistence();
|
updatePersistence();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setSearchMode(SearchMode mode) {
|
||||||
|
searchMode = mode;
|
||||||
|
notifyListeners();
|
||||||
|
updatePersistence();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSkipNonMusic(bool skip) {
|
||||||
|
skipNonMusic = skip;
|
||||||
|
notifyListeners();
|
||||||
|
updatePersistence();
|
||||||
|
}
|
||||||
|
|
||||||
Future<String> _getDefaultDownloadDirectory() async {
|
Future<String> _getDefaultDownloadDirectory() async {
|
||||||
if (kIsAndroid) return "/storage/emulated/0/Download/Spotube";
|
if (kIsAndroid) return "/storage/emulated/0/Download/Spotube";
|
||||||
|
|
||||||
@ -217,6 +244,13 @@ class UserPreferences extends PersistedChangeNotifier {
|
|||||||
localeMap != null ? Locale(localeMap?["lc"], localeMap?["cc"]) : locale;
|
localeMap != null ? Locale(localeMap?["lc"], localeMap?["cc"]) : locale;
|
||||||
|
|
||||||
pipedInstance = map["pipedInstance"] ?? pipedInstance;
|
pipedInstance = map["pipedInstance"] ?? pipedInstance;
|
||||||
|
|
||||||
|
searchMode = SearchMode.values.firstWhere(
|
||||||
|
(mode) => mode.name == map["searchMode"],
|
||||||
|
orElse: () => SearchMode.youtubeMusic,
|
||||||
|
);
|
||||||
|
|
||||||
|
skipNonMusic = map["skipNonMusic"] ?? skipNonMusic;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -237,6 +271,8 @@ class UserPreferences extends PersistedChangeNotifier {
|
|||||||
"locale":
|
"locale":
|
||||||
jsonEncode({"lc": locale.languageCode, "cc": locale.countryCode}),
|
jsonEncode({"lc": locale.languageCode, "cc": locale.countryCode}),
|
||||||
"pipedInstance": pipedInstance,
|
"pipedInstance": pipedInstance,
|
||||||
|
"searchMode": searchMode.name,
|
||||||
|
"skipNonMusic": skipNonMusic,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1654,6 +1654,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1"
|
version: "1.0.1"
|
||||||
|
simple_icons:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: simple_icons
|
||||||
|
sha256: "8aa6832dc7a263a3213e40ecbf1328a392308c809d534a3b860693625890483b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.10.0"
|
||||||
skeleton_text:
|
skeleton_text:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -63,11 +63,13 @@
|
|||||||
"custom_hours",
|
"custom_hours",
|
||||||
"logs",
|
"logs",
|
||||||
"developers",
|
"developers",
|
||||||
"not_logged_in"
|
"not_logged_in",
|
||||||
|
"search_mode"
|
||||||
],
|
],
|
||||||
|
|
||||||
"de": [
|
"de": [
|
||||||
"not_logged_in"
|
"not_logged_in",
|
||||||
|
"search_mode"
|
||||||
],
|
],
|
||||||
|
|
||||||
"fr": [
|
"fr": [
|
||||||
@ -134,7 +136,8 @@
|
|||||||
"custom_hours",
|
"custom_hours",
|
||||||
"logs",
|
"logs",
|
||||||
"developers",
|
"developers",
|
||||||
"not_logged_in"
|
"not_logged_in",
|
||||||
|
"search_mode"
|
||||||
],
|
],
|
||||||
|
|
||||||
"hi": [
|
"hi": [
|
||||||
@ -201,10 +204,12 @@
|
|||||||
"custom_hours",
|
"custom_hours",
|
||||||
"logs",
|
"logs",
|
||||||
"developers",
|
"developers",
|
||||||
"not_logged_in"
|
"not_logged_in",
|
||||||
|
"search_mode"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ja": [
|
"ja": [
|
||||||
"not_logged_in"
|
"not_logged_in",
|
||||||
|
"search_mode"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user