mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 16:05:18 +00:00
feat: bring pre download on desktop, disable pre download for long videos
fix: audio service calling self ref of playlist queue provider
This commit is contained in:
parent
3ccb525260
commit
1d82bb0987
@ -92,15 +92,19 @@ class SiblingTracksSheet extends HookConsumerWidget {
|
|||||||
subtitle: PlatformText(video.author),
|
subtitle: PlatformText(video.author),
|
||||||
enabled: playlist?.isLoading != true,
|
enabled: playlist?.isLoading != true,
|
||||||
selected: playlist?.isLoading != true &&
|
selected: playlist?.isLoading != true &&
|
||||||
video.id ==
|
video.id.value ==
|
||||||
(playlist?.activeTrack as SpotubeTrack).ytTrack.id,
|
(playlist?.activeTrack as SpotubeTrack)
|
||||||
|
.ytTrack
|
||||||
|
.id
|
||||||
|
.value,
|
||||||
selectedTileColor: Theme.of(context).popupMenuTheme.color,
|
selectedTileColor: Theme.of(context).popupMenuTheme.color,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
if (playlist?.isLoading == false &&
|
if (playlist?.isLoading == false &&
|
||||||
video.id !=
|
video.id.value !=
|
||||||
(playlist?.activeTrack as SpotubeTrack)
|
(playlist?.activeTrack as SpotubeTrack)
|
||||||
.ytTrack
|
.ytTrack
|
||||||
.id) {
|
.id
|
||||||
|
.value) {
|
||||||
await playlistNotifier.swapSibling(video);
|
await playlistNotifier.swapSibling(video);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -124,10 +124,11 @@ class BottomPlayer extends HookConsumerWidget {
|
|||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
if (volume.value != volumeState) {
|
if (volume.value != volumeState) {
|
||||||
volumeNotifier.setVolume(volume.value);
|
volume.value = volumeState;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}, [volumeState]);
|
}, [volumeState]);
|
||||||
|
|
||||||
return Listener(
|
return Listener(
|
||||||
onPointerSignal: (event) async {
|
onPointerSignal: (event) async {
|
||||||
if (event is PointerScrollEvent) {
|
if (event is PointerScrollEvent) {
|
||||||
@ -147,12 +148,7 @@ class BottomPlayer extends HookConsumerWidget {
|
|||||||
onChanged: (v) {
|
onChanged: (v) {
|
||||||
volume.value = v;
|
volume.value = v;
|
||||||
},
|
},
|
||||||
onChangeEnd: (value) async {
|
onChangeEnd: volumeNotifier.setVolume,
|
||||||
// You don't really need to know why but this
|
|
||||||
// way it works only
|
|
||||||
await volumeNotifier.setVolume(value);
|
|
||||||
await volumeNotifier.setVolume(value);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
@ -136,6 +136,10 @@ extension GetSkipSegments on Video {
|
|||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
|
||||||
|
if (res.body == "Not Found") {
|
||||||
|
return List.castFrom<dynamic, Map<String, int>>([]);
|
||||||
|
}
|
||||||
|
|
||||||
final data = jsonDecode(res.body);
|
final data = jsonDecode(res.body);
|
||||||
final segments = data.map((obj) {
|
final segments = data.map((obj) {
|
||||||
return Map.castFrom<String, dynamic, String, int>({
|
return Map.castFrom<String, dynamic, String, int>({
|
||||||
|
@ -136,7 +136,8 @@ class SpotubeTrack extends Track {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preferences.androidBytesPlay) {
|
if (preferences.predownload &&
|
||||||
|
ytVideo.duration! < const Duration(minutes: 15)) {
|
||||||
await DefaultCacheManager().getFileFromCache(track.id!).then(
|
await DefaultCacheManager().getFileFromCache(track.id!).then(
|
||||||
(file) async {
|
(file) async {
|
||||||
if (file != null) return file.file;
|
if (file != null) return file.file;
|
||||||
@ -232,7 +233,8 @@ class SpotubeTrack extends Track {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preferences.androidBytesPlay) {
|
if (preferences.predownload &&
|
||||||
|
video.duration! < const Duration(minutes: 15)) {
|
||||||
await DefaultCacheManager().getFileFromCache(id!).then(
|
await DefaultCacheManager().getFileFromCache(id!).then(
|
||||||
(file) async {
|
(file) async {
|
||||||
if (file != null) return file.file;
|
if (file != null) return file.file;
|
||||||
|
@ -15,7 +15,6 @@ import 'package:spotube/collections/spotify_markets.dart';
|
|||||||
import 'package:spotube/models/spotube_track.dart';
|
import 'package:spotube/models/spotube_track.dart';
|
||||||
import 'package:spotube/provider/auth_provider.dart';
|
import 'package:spotube/provider/auth_provider.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:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
class SettingsPage extends HookConsumerWidget {
|
class SettingsPage extends HookConsumerWidget {
|
||||||
@ -308,7 +307,6 @@ class SettingsPage extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (kIsMobile)
|
|
||||||
PlatformListTile(
|
PlatformListTile(
|
||||||
leading: const Icon(SpotubeIcons.download),
|
leading: const Icon(SpotubeIcons.download),
|
||||||
title: const PlatformText(
|
title: const PlatformText(
|
||||||
@ -318,9 +316,9 @@ class SettingsPage extends HookConsumerWidget {
|
|||||||
"Instead of streaming audio, download bytes and play instead (Recommended for higher bandwidth users)",
|
"Instead of streaming audio, download bytes and play instead (Recommended for higher bandwidth users)",
|
||||||
),
|
),
|
||||||
trailing: PlatformSwitch(
|
trailing: PlatformSwitch(
|
||||||
value: preferences.androidBytesPlay,
|
value: preferences.predownload,
|
||||||
onChanged: (state) {
|
onChanged: (state) {
|
||||||
preferences.setAndroidBytesPlay(state);
|
preferences.setPredownload(state);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -14,6 +14,7 @@ import 'package:spotube/utils/persisted_state_notifier.dart';
|
|||||||
import 'package:spotube/utils/platform.dart';
|
import 'package:spotube/utils/platform.dart';
|
||||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||||
import 'package:youtube_explode_dart/youtube_explode_dart.dart' hide Playlist;
|
import 'package:youtube_explode_dart/youtube_explode_dart.dart' hide Playlist;
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
final audioPlayer = AudioPlayer();
|
final audioPlayer = AudioPlayer();
|
||||||
final youtube = YoutubeExplode();
|
final youtube = YoutubeExplode();
|
||||||
@ -24,20 +25,32 @@ class PlaylistQueue {
|
|||||||
|
|
||||||
Track get activeTrack => tracks.elementAt(active);
|
Track get activeTrack => tracks.elementAt(active);
|
||||||
|
|
||||||
factory PlaylistQueue.fromJson(Map<String, dynamic> json) {
|
static Future<PlaylistQueue> fromJson(
|
||||||
|
Map<String, dynamic> json, UserPreferences preferences) async {
|
||||||
|
final List? tracks = json['tracks'];
|
||||||
return PlaylistQueue(
|
return PlaylistQueue(
|
||||||
Set.from(json['tracks'].map(
|
Set.from(
|
||||||
(e) {
|
await Future.wait(
|
||||||
final json = Map.castFrom<dynamic, dynamic, String, dynamic>(e);
|
tracks?.mapIndexed(
|
||||||
if (e["ytTrack"] != null) {
|
(i, e) async {
|
||||||
return SpotubeTrack.fromJson(json);
|
final jsonTrack =
|
||||||
} else if (e["path"] != null) {
|
Map.castFrom<dynamic, dynamic, String, dynamic>(e);
|
||||||
return LocalTrack.fromJson(json);
|
|
||||||
|
if (e["path"] != null) {
|
||||||
|
return LocalTrack.fromJson(jsonTrack);
|
||||||
|
} else if (i == json["active"] && !json.containsKey("path")) {
|
||||||
|
return await SpotubeTrack.fromFetchTrack(
|
||||||
|
Track.fromJson(jsonTrack),
|
||||||
|
preferences,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return Track.fromJson(json);
|
return Track.fromJson(jsonTrack);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)),
|
) ??
|
||||||
|
[],
|
||||||
|
),
|
||||||
|
),
|
||||||
active: json['active'],
|
active: json['active'],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -97,7 +110,7 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
|
|||||||
void configure() async {
|
void configure() async {
|
||||||
if (kIsMobile || kIsMacOS) {
|
if (kIsMobile || kIsMacOS) {
|
||||||
mobileService = await AudioService.init(
|
mobileService = await AudioService.init(
|
||||||
builder: () => MobileAudioService(ref),
|
builder: () => MobileAudioService(this),
|
||||||
config: const AudioServiceConfig(
|
config: const AudioServiceConfig(
|
||||||
androidNotificationChannelId: 'com.krtirtho.Spotube',
|
androidNotificationChannelId: 'com.krtirtho.Spotube',
|
||||||
androidNotificationChannelName: 'Spotube',
|
androidNotificationChannelName: 'Spotube',
|
||||||
@ -106,7 +119,7 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (kIsLinux) {
|
if (kIsLinux) {
|
||||||
linuxService = LinuxAudioService(ref);
|
linuxService = LinuxAudioService(ref, this);
|
||||||
}
|
}
|
||||||
addListener((state) {
|
addListener((state) {
|
||||||
linuxService?.player.updateProperties();
|
linuxService?.player.updateProperties();
|
||||||
@ -160,10 +173,13 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
|
|||||||
static bool get isPaused => audioPlayer.state == PlayerState.paused;
|
static bool get isPaused => audioPlayer.state == PlayerState.paused;
|
||||||
static bool get isStopped => audioPlayer.state == PlayerState.stopped;
|
static bool get isStopped => audioPlayer.state == PlayerState.stopped;
|
||||||
|
|
||||||
static Stream<Duration> get duration => audioPlayer.onDurationChanged;
|
static Stream<Duration> get duration =>
|
||||||
static Stream<Duration> get position => audioPlayer.onPositionChanged;
|
audioPlayer.onDurationChanged.asBroadcastStream();
|
||||||
|
static Stream<Duration> get position =>
|
||||||
|
audioPlayer.onPositionChanged.asBroadcastStream();
|
||||||
static Stream<bool> get playing => audioPlayer.onPlayerStateChanged
|
static Stream<bool> get playing => audioPlayer.onPlayerStateChanged
|
||||||
.map((event) => event == PlayerState.playing);
|
.map((event) => event == PlayerState.playing)
|
||||||
|
.asBroadcastStream();
|
||||||
|
|
||||||
List<Video> get siblings => state?.isLoading == false
|
List<Video> get siblings => state?.isLoading == false
|
||||||
? (state!.activeTrack as SpotubeTrack).siblings
|
? (state!.activeTrack as SpotubeTrack).siblings
|
||||||
@ -192,6 +208,7 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
|
|||||||
..removeAt(state!.active)
|
..removeAt(state!.active)
|
||||||
..shuffle()
|
..shuffle()
|
||||||
},
|
},
|
||||||
|
active: 0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,6 +216,7 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
|
|||||||
if (!isShuffled || !isLoaded) return;
|
if (!isShuffled || !isLoaded) return;
|
||||||
state = state?.copyWith(
|
state = state?.copyWith(
|
||||||
tracks: _tempTracks,
|
tracks: _tempTracks,
|
||||||
|
active: _tempTracks.toList().indexOf(state!.activeTrack),
|
||||||
);
|
);
|
||||||
_tempTracks = {};
|
_tempTracks = {};
|
||||||
}
|
}
|
||||||
@ -265,7 +283,7 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
|
|||||||
|
|
||||||
final cached =
|
final cached =
|
||||||
await DefaultCacheManager().getFileFromCache(state!.activeTrack.id!);
|
await DefaultCacheManager().getFileFromCache(state!.activeTrack.id!);
|
||||||
if (preferences.androidBytesPlay && cached != null) {
|
if (preferences.predownload && cached != null) {
|
||||||
await audioPlayer.play(
|
await audioPlayer.play(
|
||||||
DeviceFileSource(cached.file.path),
|
DeviceFileSource(cached.file.path),
|
||||||
mode: PlayerMode.mediaPlayer,
|
mode: PlayerMode.mediaPlayer,
|
||||||
@ -368,9 +386,9 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
PlaylistQueue? fromJson(Map<String, dynamic> json) {
|
Future<PlaylistQueue>? fromJson(Map<String, dynamic> json) {
|
||||||
if (json.isEmpty) return null;
|
if (json.isEmpty) return null;
|
||||||
return PlaylistQueue.fromJson(json);
|
return PlaylistQueue.fromJson(json, preferences);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -42,7 +42,7 @@ class UserPreferences extends PersistedChangeNotifier {
|
|||||||
LayoutMode layoutMode;
|
LayoutMode layoutMode;
|
||||||
bool rotatingAlbumArt;
|
bool rotatingAlbumArt;
|
||||||
|
|
||||||
bool androidBytesPlay;
|
bool predownload;
|
||||||
|
|
||||||
UserPreferences({
|
UserPreferences({
|
||||||
required this.geniusAccessToken,
|
required this.geniusAccessToken,
|
||||||
@ -50,7 +50,7 @@ class UserPreferences extends PersistedChangeNotifier {
|
|||||||
required this.themeMode,
|
required this.themeMode,
|
||||||
required this.ytSearchFormat,
|
required this.ytSearchFormat,
|
||||||
required this.layoutMode,
|
required this.layoutMode,
|
||||||
this.androidBytesPlay = true,
|
required this.predownload,
|
||||||
this.saveTrackLyrics = false,
|
this.saveTrackLyrics = false,
|
||||||
this.accentColorScheme = Colors.green,
|
this.accentColorScheme = Colors.green,
|
||||||
this.backgroundColorScheme = Colors.grey,
|
this.backgroundColorScheme = Colors.grey,
|
||||||
@ -70,9 +70,10 @@ class UserPreferences extends PersistedChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setAndroidBytesPlay(bool value) {
|
void setPredownload(bool value) {
|
||||||
androidBytesPlay = value;
|
predownload = value;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
updatePersistence();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setThemeMode(ThemeMode mode) {
|
void setThemeMode(ThemeMode mode) {
|
||||||
@ -203,7 +204,7 @@ class UserPreferences extends PersistedChangeNotifier {
|
|||||||
orElse: () => kIsDesktop ? LayoutMode.extended : LayoutMode.compact,
|
orElse: () => kIsDesktop ? LayoutMode.extended : LayoutMode.compact,
|
||||||
);
|
);
|
||||||
rotatingAlbumArt = map["rotatingAlbumArt"] ?? rotatingAlbumArt;
|
rotatingAlbumArt = map["rotatingAlbumArt"] ?? rotatingAlbumArt;
|
||||||
androidBytesPlay = map["androidBytesPlay"] ?? androidBytesPlay;
|
predownload = map["predownload"] ?? predownload;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -223,7 +224,7 @@ class UserPreferences extends PersistedChangeNotifier {
|
|||||||
"downloadLocation": downloadLocation,
|
"downloadLocation": downloadLocation,
|
||||||
"layoutMode": layoutMode.name,
|
"layoutMode": layoutMode.name,
|
||||||
"rotatingAlbumArt": rotatingAlbumArt,
|
"rotatingAlbumArt": rotatingAlbumArt,
|
||||||
"androidBytesPlay": androidBytesPlay,
|
"predownload": predownload,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -235,5 +236,6 @@ final userPreferencesProvider = ChangeNotifierProvider(
|
|||||||
themeMode: ThemeMode.system,
|
themeMode: ThemeMode.system,
|
||||||
ytSearchFormat: "\$MAIN_ARTIST - \$TITLE \$FEATURED_ARTISTS",
|
ytSearchFormat: "\$MAIN_ARTIST - \$TITLE \$FEATURED_ARTISTS",
|
||||||
layoutMode: kIsMobile ? LayoutMode.compact : LayoutMode.adaptive,
|
layoutMode: kIsMobile ? LayoutMode.compact : LayoutMode.adaptive,
|
||||||
|
predownload: kIsMobile,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -218,10 +218,11 @@ class _MprisMediaPlayer2 extends DBusObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _MprisMediaPlayer2Player extends DBusObject {
|
class _MprisMediaPlayer2Player extends DBusObject {
|
||||||
Ref ref;
|
final Ref ref;
|
||||||
|
final PlaylistQueueNotifier playlistNotifier;
|
||||||
|
|
||||||
/// Creates a new object to expose on [path].
|
/// Creates a new object to expose on [path].
|
||||||
_MprisMediaPlayer2Player(this.ref)
|
_MprisMediaPlayer2Player(this.ref, this.playlistNotifier)
|
||||||
: super(DBusObjectPath("/org/mpris/MediaPlayer2")) {
|
: super(DBusObjectPath("/org/mpris/MediaPlayer2")) {
|
||||||
(() async {
|
(() async {
|
||||||
final nameStatus =
|
final nameStatus =
|
||||||
@ -233,9 +234,7 @@ class _MprisMediaPlayer2Player extends DBusObject {
|
|||||||
}());
|
}());
|
||||||
}
|
}
|
||||||
|
|
||||||
PlaylistQueue? get playlist => ref.read(PlaylistQueueNotifier.provider);
|
PlaylistQueue? get playlist => playlistNotifier.state;
|
||||||
PlaylistQueueNotifier get playlistNotifier =>
|
|
||||||
ref.read(PlaylistQueueNotifier.notifier);
|
|
||||||
double get volume => ref.read(VolumeProvider.provider);
|
double get volume => ref.read(VolumeProvider.provider);
|
||||||
VolumeProvider get volumeNotifier =>
|
VolumeProvider get volumeNotifier =>
|
||||||
ref.read(VolumeProvider.provider.notifier);
|
ref.read(VolumeProvider.provider.notifier);
|
||||||
@ -727,9 +726,9 @@ class LinuxAudioService {
|
|||||||
_MprisMediaPlayer2 mp2;
|
_MprisMediaPlayer2 mp2;
|
||||||
_MprisMediaPlayer2Player player;
|
_MprisMediaPlayer2Player player;
|
||||||
|
|
||||||
LinuxAudioService(Ref ref)
|
LinuxAudioService(Ref ref, PlaylistQueueNotifier playlistNotifier)
|
||||||
: mp2 = _MprisMediaPlayer2(),
|
: mp2 = _MprisMediaPlayer2(),
|
||||||
player = _MprisMediaPlayer2Player(ref);
|
player = _MprisMediaPlayer2Player(ref, playlistNotifier);
|
||||||
|
|
||||||
void dispose() {
|
void dispose() {
|
||||||
mp2.dispose();
|
mp2.dispose();
|
||||||
|
@ -3,18 +3,16 @@ import 'dart:async';
|
|||||||
import 'package:audio_service/audio_service.dart';
|
import 'package:audio_service/audio_service.dart';
|
||||||
import 'package:audio_session/audio_session.dart';
|
import 'package:audio_session/audio_session.dart';
|
||||||
import 'package:audioplayers/audioplayers.dart';
|
import 'package:audioplayers/audioplayers.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:spotube/provider/playlist_queue_provider.dart';
|
import 'package:spotube/provider/playlist_queue_provider.dart';
|
||||||
|
|
||||||
class MobileAudioService extends BaseAudioHandler {
|
class MobileAudioService extends BaseAudioHandler {
|
||||||
final Ref ref;
|
|
||||||
AudioSession? session;
|
AudioSession? session;
|
||||||
|
final PlaylistQueueNotifier playlistNotifier;
|
||||||
|
|
||||||
PlaylistQueue? get playlist => ref.watch(PlaylistQueueNotifier.provider);
|
|
||||||
PlaylistQueueNotifier get playlistNotifier =>
|
|
||||||
ref.watch(PlaylistQueueNotifier.notifier);
|
|
||||||
|
|
||||||
MobileAudioService(this.ref) {
|
PlaylistQueue? get playlist => playlistNotifier.state;
|
||||||
|
|
||||||
|
MobileAudioService(this.playlistNotifier) {
|
||||||
AudioSession.instance.then((s) {
|
AudioSession.instance.then((s) {
|
||||||
session = s;
|
session = s;
|
||||||
s.interruptionEventStream.listen((event) async {
|
s.interruptionEventStream.listen((event) async {
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
|
|
||||||
abstract class PersistedStateNotifier<T> extends StateNotifier<T> {
|
abstract class PersistedStateNotifier<T> extends StateNotifier<T> {
|
||||||
final String cacheKey;
|
final String cacheKey;
|
||||||
|
|
||||||
PersistedStateNotifier(super.state, this.cacheKey) : super() {
|
PersistedStateNotifier(super.state, this.cacheKey) {
|
||||||
_load();
|
_load();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -13,7 +15,7 @@ abstract class PersistedStateNotifier<T> extends StateNotifier<T> {
|
|||||||
final json = await box.get(cacheKey);
|
final json = await box.get(cacheKey);
|
||||||
|
|
||||||
if (json != null) {
|
if (json != null) {
|
||||||
state = fromJson(castNestedJson(json));
|
state = await fromJson(castNestedJson(json));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,7 +46,7 @@ abstract class PersistedStateNotifier<T> extends StateNotifier<T> {
|
|||||||
box.put(cacheKey, toJson());
|
box.put(cacheKey, toJson());
|
||||||
}
|
}
|
||||||
|
|
||||||
T fromJson(Map<String, dynamic> json);
|
FutureOr<T> fromJson(Map<String, dynamic> json);
|
||||||
Map<String, dynamic> toJson();
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
Loading…
Reference in New Issue
Block a user