Open playlist page default.

This commit is contained in:
Stonegate 2021-01-07 00:28:53 +08:00
parent 747aa47772
commit 4fe335ff69
8 changed files with 537 additions and 393 deletions

View File

@ -60,6 +60,8 @@ const String downloadPositionKey = 'downloadPositionKey';
const String deleteAfterPlayedKey = 'removeAfterPlayedKey'; const String deleteAfterPlayedKey = 'removeAfterPlayedKey';
const String playlistsAllKey = 'playlistsAllKey'; const String playlistsAllKey = 'playlistsAllKey';
const String playerStateKey = 'playerStateKey'; const String playerStateKey = 'playerStateKey';
const String openPlaylistDefaultKey = 'openPlaylistDefaultKey';
const String openAllPodcastDefaultKey = 'openAllPodcastDefaultKey';
class KeyValueStorage { class KeyValueStorage {
final String key; final String key;

View File

@ -5,6 +5,8 @@ import 'package:flutter/services.dart';
import 'package:flutter_downloader/flutter_downloader.dart'; import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tsacdop/playlists/playlist_home.dart';
import 'package:tuple/tuple.dart';
import 'generated/l10n.dart'; import 'generated/l10n.dart';
import 'home/home.dart'; import 'home/home.dart';
@ -52,15 +54,17 @@ Future main() async {
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Consumer<SettingState>( return Selector<SettingState, Tuple3<ThemeMode, ThemeData, ThemeData>>(
builder: (_, setting, child) { selector: (_, setting) =>
Tuple3(setting.theme, setting.lightTheme, setting.darkTheme),
builder: (_, data, child) {
return FeatureDiscovery( return FeatureDiscovery(
child: MaterialApp( child: MaterialApp(
themeMode: setting.theme, themeMode: data.item1,
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
title: 'Tsacdop', title: 'Tsacdop',
theme: setting.lightTheme, theme: data.item2,
darkTheme: setting.darkTheme, darkTheme: data.item3,
localizationsDelegates: [ localizationsDelegates: [
S.delegate, S.delegate,
GlobalMaterialLocalizations.delegate, GlobalMaterialLocalizations.delegate,
@ -68,11 +72,15 @@ class MyApp extends StatelessWidget {
GlobalCupertinoLocalizations.delegate, GlobalCupertinoLocalizations.delegate,
], ],
supportedLocales: S.delegate.supportedLocales, supportedLocales: S.delegate.supportedLocales,
home: setting.showIntro ? SlideIntro(goto: Goto.home) : child, home: context.read<SettingState>().showIntro
? SlideIntro(goto: Goto.home)
: context.read<SettingState>().openPlaylistDefault
? PlaylistHome()
: Home(),
), ),
); );
}, },
child: FeatureDiscovery(child: Home()), //child: FeatureDiscovery(child: Home()),
); );
} }
} }

View File

@ -5,10 +5,13 @@ import 'package:flutter/services.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:line_icons/line_icons.dart'; import 'package:line_icons/line_icons.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tsacdop/util/pageroute.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import '../home/home.dart';
import '../local_storage/sqflite_localpodcast.dart'; import '../local_storage/sqflite_localpodcast.dart';
import '../state/audio_state.dart'; import '../state/audio_state.dart';
import '../state/setting_state.dart';
import '../type/episodebrief.dart'; import '../type/episodebrief.dart';
import '../type/play_histroy.dart'; import '../type/play_histroy.dart';
import '../type/playlist.dart'; import '../type/playlist.dart';
@ -30,7 +33,9 @@ class _PlaylistHomeState extends State<PlaylistHome> {
@override @override
void initState() { void initState() {
Future.microtask(() => context.read<AudioPlayerNotifier>().initPlaylist());
super.initState(); super.initState();
//context.read<AudioPlayerNotifier>().initPlaylist();
_selected = 'PlayNext'; _selected = 'PlayNext';
_body = _Queue(); _body = _Queue();
} }
@ -64,184 +69,195 @@ class _PlaylistHomeState extends State<PlaylistHome> {
statusBarIconBrightness: Theme.of(context).accentColorBrightness, statusBarIconBrightness: Theme.of(context).accentColorBrightness,
systemNavigationBarColor: Theme.of(context).primaryColor, systemNavigationBarColor: Theme.of(context).primaryColor,
), ),
child: Scaffold( child: WillPopScope(
appBar: AppBar( onWillPop: () {
leading: CustomBackButton(), if (context.read<SettingState>().openPlaylistDefault) {
centerTitle: true, Navigator.push(context, SlideRightRoute(page: Home()));
title: Selector<AudioPlayerNotifier, EpisodeBrief>( return Future.value(false);
selector: (_, audio) => audio.episode, } else {
builder: (_, data, __) { return Future.value(true);
return Text(data?.title ?? '', maxLines: 1); }
}, },
child: Scaffold(
appBar: AppBar(
leading: CustomBackButton(),
centerTitle: true,
title: Selector<AudioPlayerNotifier, EpisodeBrief>(
selector: (_, audio) => audio.episode,
builder: (_, data, __) {
return Text(data?.title ?? '', maxLines: 1);
},
),
backgroundColor: context.scaffoldBackgroundColor,
), ),
backgroundColor: context.scaffoldBackgroundColor, body: Column(
), children: [
body: Column( SizedBox(
children: [ height: 100,
SizedBox( child: Selector<AudioPlayerNotifier,
height: 100, Tuple4<Playlist, bool, bool, EpisodeBrief>>(
child: Selector<AudioPlayerNotifier, selector: (_, audio) => Tuple4(audio.playlist,
Tuple4<Playlist, bool, bool, EpisodeBrief>>( audio.playerRunning, audio.playing, audio.episode),
selector: (_, audio) => Tuple4(audio.playlist, builder: (_, data, __) {
audio.playerRunning, audio.playing, audio.episode), final running = data.item2;
builder: (_, data, __) { final playing = data.item3;
final running = data.item2; final audio = context.read<AudioPlayerNotifier>();
final playing = data.item3; return Row(
final audio = context.read<AudioPlayerNotifier>(); children: [
return Row( Expanded(
children: [ child: Column(
Expanded( mainAxisAlignment: MainAxisAlignment.center,
child: Column( children: [
mainAxisAlignment: MainAxisAlignment.center, Row(
children: [ mainAxisAlignment: MainAxisAlignment.center,
Row( crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, children: [
crossAxisAlignment: CrossAxisAlignment.center, IconButton(
children: [ icon: Icon(Icons.fast_rewind),
IconButton( onPressed: () {
icon: Icon(Icons.fast_rewind), if (running) {
onPressed: () { audio.rewind();
}
}),
SizedBox(width: 30),
IconButton(
padding: EdgeInsets.zero,
icon: Icon(
playing
? LineIcons.pause_solid
: LineIcons.play_solid,
size: 40),
onPressed: () {
if (running) {
playing
? audio.pauseAduio()
: audio.resumeAudio();
} else {
context
.read<AudioPlayerNotifier>()
.playFromLastPosition();
}
}),
SizedBox(width: 30),
IconButton(
icon: Icon(Icons.fast_forward),
onPressed: () {
if (running) {
audio.fastForward();
}
})
],
),
if (data.item2)
Selector<AudioPlayerNotifier,
Tuple3<bool, double, String>>(
selector: (_, audio) => Tuple3(
audio.buffering,
(audio.backgroundAudioDuration -
audio.backgroundAudioPosition) /
1000,
audio.remoteErrorMessage),
builder: (_, data, __) {
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10),
child: data.item3 != null
? Text(data.item3,
style: const TextStyle(
color: Color(0xFFFF0000)))
: data.item1
? Text(
s.buffering,
style: TextStyle(
color:
context.accentColor),
)
: Text(
s.timeLeft((data.item2)
.toInt()
.toTime ??
''),
maxLines: 2,
),
);
},
)
],
)),
data.item4 != null
? ClipRRect(
borderRadius: BorderRadius.circular(10),
child: InkWell(
onTap: () {
if (running) { if (running) {
audio.rewind();
}
}),
SizedBox(width: 30),
IconButton(
padding: EdgeInsets.zero,
icon: Icon(
playing
? LineIcons.pause_solid
: LineIcons.play_solid,
size: 40),
onPressed: () {
if (running) {
playing
? audio.pauseAduio()
: audio.resumeAudio();
} else {
context context
.read<AudioPlayerNotifier>() .read<AudioPlayerNotifier>()
.playFromLastPosition(); .playNext();
} }
}), },
SizedBox(width: 30), child: SizedBox(
IconButton( width: 80,
icon: Icon(Icons.fast_forward), height: 80,
onPressed: () { child: Image(
if (running) { image: data.item4.avatarImage)),
audio.fastForward(); ),
} )
}) : Container(
], decoration: BoxDecoration(
), color: context.accentColor.withAlpha(70),
if (data.item2) borderRadius: BorderRadius.circular(10)),
Selector<AudioPlayerNotifier, width: 80,
Tuple3<bool, double, String>>( height: 80),
selector: (_, audio) => Tuple3( SizedBox(
audio.buffering, width: 20,
(audio.backgroundAudioDuration - ),
audio.backgroundAudioPosition) / ],
1000, );
audio.remoteErrorMessage), },
builder: (_, data, __) { ),
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10),
child: data.item3 != null
? Text(data.item3,
style: const TextStyle(
color: Color(0xFFFF0000)))
: data.item1
? Text(
s.buffering,
style: TextStyle(
color: context.accentColor),
)
: Text(
s.timeLeft((data.item2)
.toInt()
.toTime ??
''),
maxLines: 2,
),
);
},
)
],
)),
data.item4 != null
? ClipRRect(
borderRadius: BorderRadius.circular(10),
child: InkWell(
onTap: () {
if (running) {
context
.read<AudioPlayerNotifier>()
.playNext();
}
},
child: SizedBox(
width: 80,
height: 80,
child:
Image(image: data.item4.avatarImage)),
),
)
: Container(
decoration: BoxDecoration(
color: context.accentColor.withAlpha(70),
borderRadius: BorderRadius.circular(10)),
width: 80,
height: 80),
SizedBox(
width: 20,
),
],
);
},
), ),
), SizedBox(
SizedBox( height: 50,
height: 50, child: Row(
child: Row( mainAxisAlignment: MainAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, children: [
children: [ _tabWidget(
_tabWidget( icon: Icon(Icons.queue_music_rounded),
icon: Icon(Icons.queue_music_rounded), label: s.playNext,
label: s.playNext, color: Colors.blue,
color: Colors.blue, isSelected: _selected == 'PlayNext',
isSelected: _selected == 'PlayNext', onTap: () => setState(() {
onTap: () => setState(() { _body = _Queue();
_body = _Queue(); _selected = 'PlayNext';
_selected = 'PlayNext'; })),
})), _tabWidget(
_tabWidget( icon: Icon(Icons.history),
icon: Icon(Icons.history), label: s.settingsHistory,
label: s.settingsHistory, color: Colors.green,
color: Colors.green, isSelected: _selected == 'History',
isSelected: _selected == 'History', onTap: () => setState(() {
onTap: () => setState(() { _body = _History();
_body = _History(); _selected = 'History';
_selected = 'History'; })),
})), _tabWidget(
_tabWidget( icon: Icon(Icons.playlist_play),
icon: Icon(Icons.playlist_play), label: s.playlists,
label: s.playlists, color: Colors.purple,
color: Colors.purple, isSelected: _selected == 'Playlists',
isSelected: _selected == 'Playlists', onTap: () => setState(() {
onTap: () => setState(() { _body = _Playlists();
_body = _Playlists(); _selected = 'Playlists';
_selected = 'Playlists'; })),
})), ],
], ),
), ),
), Divider(height: 1),
Divider(height: 1), Expanded(
Expanded( child: AnimatedSwitcher(
child: AnimatedSwitcher( duration: Duration(milliseconds: 300), child: _body))
duration: Duration(milliseconds: 300), child: _body)) ],
], )),
)), ),
); );
} }
} }
@ -260,61 +276,67 @@ class __QueueState extends State<_Queue> {
selector: (_, audio) => selector: (_, audio) =>
Tuple3(audio.playlist, audio.playerRunning, audio.episode), Tuple3(audio.playlist, audio.playerRunning, audio.episode),
builder: (_, data, __) { builder: (_, data, __) {
var episodes = data.item1.episodes.toSet().toList(); var episodes = data.item1?.episodes?.toSet()?.toList();
var queue = data.item1; var queue = data.item1;
var running = data.item2; var running = data.item2;
return queue.name == 'Queue' return queue == null
? ReorderableListView( ? Center()
onReorder: (oldIndex, newIndex) { : queue?.name == 'Queue'
context ? ReorderableListView(
.read<AudioPlayerNotifier>() onReorder: (oldIndex, newIndex) {
.reorderPlaylist(oldIndex, newIndex); context
setState(() {}); .read<AudioPlayerNotifier>()
}, .reorderPlaylist(oldIndex, newIndex);
scrollDirection: Axis.vertical, setState(() {});
children: data.item2
? episodes.map<Widget>((episode) {
if (episode.enclosureUrl !=
episodes.first.enclosureUrl) {
return DismissibleContainer(
episode: episode,
onRemove: (value) => setState(() {}),
key: ValueKey(episode.enclosureUrl),
);
} else {
return EpisodeCard(episode,
key: ValueKey('playing'),
isPlaying: true,
canReorder: true,
tileColor: context.primaryColorDark);
}
}).toList()
: episodes
.map<Widget>((episode) => DismissibleContainer(
episode: episode,
onRemove: (value) => setState(() {}),
key: ValueKey(episode.enclosureUrl),
))
.toList())
: ListView.builder(
itemCount: queue.episodeList.length,
itemBuilder: (context, index) {
final episode = queue.episodes[index];
final isPlaying =
data.item3 != null && data.item3 == episode;
return EpisodeCard(
episode,
isPlaying: isPlaying && running,
tileColor: isPlaying ? context.primaryColorDark : null,
onTap: () async {
if (!isPlaying) {
await context
.read<AudioPlayerNotifier>()
.loadEpisodeFromPlaylist(episode);
}
}, },
); scrollDirection: Axis.vertical,
}); children: data.item2
? episodes.map<Widget>((episode) {
if (episode.enclosureUrl !=
episodes.first.enclosureUrl) {
return DismissibleContainer(
episode: episode,
onRemove: (value) => setState(() {}),
key: ValueKey(episode.enclosureUrl),
);
} else {
return EpisodeCard(episode,
key: ValueKey('playing'),
isPlaying: true,
canReorder: true,
tileColor: context.primaryColorDark);
}
}).toList()
: episodes
.map<Widget>((episode) => DismissibleContainer(
episode: episode,
onRemove: (value) => setState(() {}),
key: ValueKey(episode.enclosureUrl),
))
.toList())
: ListView.builder(
itemCount: queue?.length,
itemBuilder: (context, index) {
final episode =
queue != null ? queue.episodes[index] : null;
final isPlaying =
data.item3 != null && data.item3 == episode;
return episode == null
? Center()
: EpisodeCard(
episode,
isPlaying: isPlaying && running,
tileColor:
isPlaying ? context.primaryColorDark : null,
onTap: () async {
if (!isPlaying) {
await context
.read<AudioPlayerNotifier>()
.loadEpisodeFromPlaylist(episode);
}
},
);
});
}); });
} }
} }

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tsacdop/state/setting_state.dart';
import '../local_storage/key_value_storage.dart'; import '../local_storage/key_value_storage.dart';
import '../service/search_api.dart'; import '../service/search_api.dart';
@ -310,9 +311,61 @@ class _LayoutSettingState extends State<LayoutSetting> {
), ),
), ),
Divider(height: 1), Divider(height: 1),
Padding( SizedBox(height: 20),
padding: EdgeInsets.all(10.0), Container(
height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft,
child: Text('Default page',
style: context.textTheme.bodyText1
.copyWith(color: context.accentColor)),
), ),
Selector<SettingState, bool>(
selector: (_, setting) => setting.openPlaylistDefault,
builder: (_, data, __) {
return ListTile(
contentPadding: EdgeInsets.fromLTRB(70, 10, 10, 10),
onTap: () => context
.read<SettingState>()
.openPlaylistDefault = !data,
title: Text('Open playlist page by default'),
subtitle: Text(
'Open playlist page instead of homepage by default'),
trailing: Transform.scale(
scale: 0.9,
child: Switch(
value: data,
onChanged: (boo) => context
.read<SettingState>()
.openPlaylistDefault = boo),
),
);
},
),
Selector<SettingState, bool>(
selector: (_, setting) => setting.openAllPodcastDefalt,
builder: (_, data, __) {
return ListTile(
contentPadding: EdgeInsets.fromLTRB(70, 10, 10, 10),
onTap: () => context
.read<SettingState>()
.openAllPodcastDefault = !data,
title: Text('Open all podcasts page by default'),
subtitle: Text(
'Open all podcasts page instead of group page by default'),
trailing: Transform.scale(
scale: 0.9,
child: Switch(
value: data,
onChanged: (boo) => context
.read<SettingState>()
.openAllPodcastDefault = boo),
),
);
},
),
Divider(height: 1),
SizedBox(height: 20),
Container( Container(
height: 30.0, height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 70), padding: EdgeInsets.symmetric(horizontal: 70),

View File

@ -84,7 +84,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
EpisodeBrief _episode; EpisodeBrief _episode;
/// Playlists include queue and playlists created by user. /// Playlists include queue and playlists created by user.
List<Playlist> _playlists; List<Playlist> _playlists = [];
/// Playing playlist. /// Playing playlist.
Playlist _playlist; Playlist _playlist;
@ -261,53 +261,55 @@ class AudioPlayerNotifier extends ChangeNotifier {
} }
Future<void> initPlaylist() async { Future<void> initPlaylist() async {
var playlistEntities = await _playlistsStorgae.getPlaylists(); if (_playlists.isEmpty) {
_playlists = [ var playlistEntities = await _playlistsStorgae.getPlaylists();
for (var entity in playlistEntities) Playlist.fromEntity(entity) _playlists = [
]; for (var entity in playlistEntities) Playlist.fromEntity(entity)
await _playlists.first.getPlaylist(); ];
await _getAutoPlay(); await _playlists.first.getPlaylist();
await _getAutoPlay();
var state = await _playerStateStorage.getPlayerState(); var state = await _playerStateStorage.getPlayerState();
var idList = [for (var p in _playlists) p.id]; var idList = [for (var p in _playlists) p.id];
if (idList.contains(state[1])) { if (idList.contains(state[1])) {
_playlist = _playlists.firstWhere( _playlist = _playlists.firstWhere(
(p) => p.id == state[0], (p) => p.id == state[0],
); );
await _playlist.getPlaylist(); await _playlist.getPlaylist();
if (state[1] != '') { if (state[1] != '') {
var episode = await _dbHelper.getRssItemWithUrl(state[1]); var episode = await _dbHelper.getRssItemWithUrl(state[1]);
if ((!_playlist.isQueue && _playlist.contains(episode)) || if ((!_playlist.isQueue && _playlist.contains(episode)) ||
(_playlist.isQueue && (_playlist.isQueue &&
_queue.isNotEmpty && _queue.isNotEmpty &&
_queue.episodes.first == episode)) { _queue.episodes.first == episode)) {
_episode = episode; _episode = episode;
_lastPosition = int.parse(state[2] ?? '0'); _lastPosition = int.parse(state[2] ?? '0');
} else {
_episode = _playlist.isNotEmpty ? _playlist.episodes.first : null;
_lastPosition = 0;
}
} else { } else {
_episode = _playlist.isNotEmpty ? _playlist.episodes.first : null; _episode = _playlist.isNotEmpty ? _playlist.episodes.first : null;
_lastPosition = 0; _lastPosition = 0;
} }
} else { } else {
_episode = _playlist.isNotEmpty ? _playlist.episodes.first : null; _playlist = _playlists.first;
_episode = _playlist.isNotEmpty ? _playlist.episodes?.first : null;
_lastPosition = 0; _lastPosition = 0;
} }
} else { notifyListeners();
_playlist = _playlists.first;
_episode = _playlist.isNotEmpty ? _playlist.episodes?.first : null;
_lastPosition = 0;
}
notifyListeners();
/// Save plays history if app is closed accidentally. /// Save plays history if app is closed accidentally.
/// if (_lastPostion > 0 && _queue.episodes.isNotEmpty) { /// if (_lastPostion > 0 && _queue.episodes.isNotEmpty) {
/// final episode = _queue.episodes.first; /// final episode = _queue.episodes.first;
/// final duration = episode.duration * 1000; /// final duration = episode.duration * 1000;
/// final seekValue = duration != 0 ? _lastPostion / duration : 1.0; /// final seekValue = duration != 0 ? _lastPostion / duration : 1.0;
/// final history = PlayHistory( /// final history = PlayHistory(
/// episode.title, episode.enclosureUrl, _lastPostion ~/ 1000, seekValue); /// episode.title, episode.enclosureUrl, _lastPostion ~/ 1000, seekValue);
/// await _dbHelper.saveHistory(history); /// await _dbHelper.saveHistory(history);
/// } /// }
await KeyValueStorage(lastWorkKey).saveInt(0); await KeyValueStorage(lastWorkKey).saveInt(0);
}
} }
Future<void> playFromLastPosition() async { Future<void> playFromLastPosition() async {

View File

@ -74,37 +74,40 @@ final showNotesFontStyles = <TextStyle>[
]; ];
class SettingState extends ChangeNotifier { class SettingState extends ChangeNotifier {
var themeStorage = KeyValueStorage(themesKey); final _themeStorage = KeyValueStorage(themesKey);
var accentStorage = KeyValueStorage(accentsKey); final _accentStorage = KeyValueStorage(accentsKey);
var autoupdateStorage = KeyValueStorage(autoUpdateKey); final _autoupdateStorage = KeyValueStorage(autoUpdateKey);
var intervalStorage = KeyValueStorage(updateIntervalKey); final _intervalStorage = KeyValueStorage(updateIntervalKey);
var downloadUsingDataStorage = KeyValueStorage(downloadUsingDataKey); final _downloadUsingDataStorage = KeyValueStorage(downloadUsingDataKey);
var introStorage = KeyValueStorage(introKey); final _introStorage = KeyValueStorage(introKey);
var realDarkStorage = KeyValueStorage(realDarkKey); final _realDarkStorage = KeyValueStorage(realDarkKey);
var autoPlayStorage = KeyValueStorage(autoPlayKey); final _autoPlayStorage = KeyValueStorage(autoPlayKey);
var defaultSleepTimerStorage = KeyValueStorage(defaultSleepTimerKey); final _defaultSleepTimerStorage = KeyValueStorage(defaultSleepTimerKey);
var autoSleepTimerStorage = KeyValueStorage(autoSleepTimerKey); final _autoSleepTimerStorage = KeyValueStorage(autoSleepTimerKey);
var autoSleepTimerModeStorage = KeyValueStorage(autoSleepTimerModeKey); final _autoSleepTimerModeStorage = KeyValueStorage(autoSleepTimerModeKey);
var autoSleepTimerStartStorage = KeyValueStorage(autoSleepTimerStartKey); final _autoSleepTimerStartStorage = KeyValueStorage(autoSleepTimerStartKey);
var autoSleepTimerEndStorage = KeyValueStorage(autoSleepTimerEndKey); final _autoSleepTimerEndStorage = KeyValueStorage(autoSleepTimerEndKey);
var tapToOpenPopupMenuStorage = KeyValueStorage(tapToOpenPopupMenuKey); final _cacheStorage = KeyValueStorage(cacheMaxKey);
var cacheStorage = KeyValueStorage(cacheMaxKey); final _podcastLayoutStorage = KeyValueStorage(podcastLayoutKey);
var podcastLayoutStorage = KeyValueStorage(podcastLayoutKey); final _favLayoutStorage = KeyValueStorage(favLayoutKey);
var favLayoutStorage = KeyValueStorage(favLayoutKey); final _downloadLayoutStorage = KeyValueStorage(downloadLayoutKey);
var downloadLayoutStorage = KeyValueStorage(downloadLayoutKey); final _recentLayoutStorage = KeyValueStorage(recentLayoutKey);
var recentLayoutStorage = KeyValueStorage(recentLayoutKey); final _autoDeleteStorage = KeyValueStorage(autoDeleteKey);
var autoDeleteStorage = KeyValueStorage(autoDeleteKey); final _autoDownloadStorage = KeyValueStorage(autoDownloadNetworkKey);
var autoDownloadStorage = KeyValueStorage(autoDownloadNetworkKey); final _fastForwardSecondsStorage = KeyValueStorage(fastForwardSecondsKey);
var fastForwardSecondsStorage = KeyValueStorage(fastForwardSecondsKey); final _rewindSecondsStorage = KeyValueStorage(rewindSecondsKey);
var rewindSecondsStorage = KeyValueStorage(rewindSecondsKey); final _localeStorage = KeyValueStorage(localeKey);
var localeStorage = KeyValueStorage(localeKey); final _showNotesFontStorage = KeyValueStorage(showNotesFontKey);
var showNotesFontStorage = KeyValueStorage(showNotesFontKey); final _openPlaylistDefaultStorage = KeyValueStorage(openPlaylistDefaultKey);
final _openAllPodcastDefaultStorage =
KeyValueStorage(openAllPodcastDefaultKey);
Future initData() async { Future initData() async {
await _getTheme(); await _getTheme();
await _getAccentSetColor(); await _getAccentSetColor();
await _getShowIntro(); await _getShowIntro();
await _getRealDark(); await _getRealDark();
await _getOpenPlaylistDefault();
} }
@override @override
@ -116,6 +119,7 @@ class SettingState extends ChangeNotifier {
_getSleepTimerData(); _getSleepTimerData();
_getPlayerSeconds(); _getPlayerSeconds();
_getShowNotesFonts(); _getShowNotesFonts();
_getOpenAllPodcastDefault();
_getUpdateInterval().then((value) async { _getUpdateInterval().then((value) async {
if (_initUpdateTag == 0) { if (_initUpdateTag == 0) {
setWorkManager(24); setWorkManager(24);
@ -247,6 +251,24 @@ class SettingState extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
/// Open playlist page default
bool _openPlaylistDefault;
bool get openPlaylistDefault => _openPlaylistDefault;
set openPlaylistDefault(bool boo) {
_openPlaylistDefault = boo;
_setOpenPlaylistDefault();
notifyListeners();
}
/// Open all podcasts page default
bool _openAllPodcastDefault;
bool get openAllPodcastDefalt => _openAllPodcastDefault;
set openAllPodcastDefault(boo) {
_openAllPodcastDefault = boo;
_setOpenAllPodcastDefault();
notifyListeners();
}
int _defaultSleepTimer; int _defaultSleepTimer;
int get defaultSleepTimer => _defaultSleepTimer; int get defaultSleepTimer => _defaultSleepTimer;
set setDefaultSleepTimer(int i) { set setDefaultSleepTimer(int i) {
@ -322,12 +344,12 @@ class SettingState extends ChangeNotifier {
} }
Future _getTheme() async { Future _getTheme() async {
var mode = await themeStorage.getInt(); var mode = await _themeStorage.getInt();
_theme = ThemeMode.values[mode]; _theme = ThemeMode.values[mode];
} }
Future _getAccentSetColor() async { Future _getAccentSetColor() async {
var colorString = await accentStorage.getString(); var colorString = await _accentStorage.getString();
if (colorString.isNotEmpty) { if (colorString.isNotEmpty) {
var color = int.parse('FF${colorString.toUpperCase()}', radix: 16); var color = int.parse('FF${colorString.toUpperCase()}', radix: 16);
_accentSetColor = Color(color).withOpacity(1.0); _accentSetColor = Color(color).withOpacity(1.0);
@ -339,53 +361,63 @@ class SettingState extends ChangeNotifier {
Future _getAutoUpdate() async { Future _getAutoUpdate() async {
_autoUpdate = _autoUpdate =
await autoupdateStorage.getBool(defaultValue: true, reverse: true); await _autoupdateStorage.getBool(defaultValue: true, reverse: true);
} }
Future _getUpdateInterval() async { Future _getUpdateInterval() async {
_initUpdateTag = await intervalStorage.getInt(); _initUpdateTag = await _intervalStorage.getInt();
_updateInterval = _initUpdateTag; _updateInterval = _initUpdateTag;
} }
Future _getDownloadUsingData() async { Future _getDownloadUsingData() async {
_downloadUsingData = await downloadUsingDataStorage.getBool( _downloadUsingData = await _downloadUsingDataStorage.getBool(
defaultValue: true, reverse: true); defaultValue: true, reverse: true);
} }
Future _saveDownloadUsingData() async { Future _saveDownloadUsingData() async {
await downloadUsingDataStorage.saveBool(_downloadUsingData, reverse: true); await _downloadUsingDataStorage.saveBool(_downloadUsingData, reverse: true);
} }
Future _getShowIntro() async { Future _getShowIntro() async {
_initialShowIntor = await introStorage.getInt(); _initialShowIntor = await _introStorage.getInt();
_showIntro = _initialShowIntor == 0; _showIntro = _initialShowIntor == 0;
} }
Future _getRealDark() async { Future _getRealDark() async {
_realDark = await realDarkStorage.getBool(defaultValue: false); _realDark = await _realDarkStorage.getBool(defaultValue: false);
}
Future _getOpenPlaylistDefault() async {
_openPlaylistDefault =
await _openPlaylistDefaultStorage.getBool(defaultValue: false);
}
Future _getOpenAllPodcastDefault() async {
_openAllPodcastDefault =
await _openAllPodcastDefaultStorage.getBool(defaultValue: false);
} }
Future _getSleepTimerData() async { Future _getSleepTimerData() async {
_defaultSleepTimer = _defaultSleepTimer =
await defaultSleepTimerStorage.getInt(defaultValue: 30); await _defaultSleepTimerStorage.getInt(defaultValue: 30);
_autoSleepTimer = await autoSleepTimerStorage.getBool(defaultValue: false); _autoSleepTimer = await _autoSleepTimerStorage.getBool(defaultValue: false);
_autoSleepTimerStart = _autoSleepTimerStart =
await autoSleepTimerStartStorage.getInt(defaultValue: 1380); await _autoSleepTimerStartStorage.getInt(defaultValue: 1380);
_autoSleepTimerEnd = _autoSleepTimerEnd =
await autoSleepTimerEndStorage.getInt(defaultValue: 360); await _autoSleepTimerEndStorage.getInt(defaultValue: 360);
_autoPlay = _autoPlay =
await autoPlayStorage.getBool(defaultValue: true, reverse: true); await _autoPlayStorage.getBool(defaultValue: true, reverse: true);
_autoSleepTimerMode = await autoSleepTimerModeStorage.getInt(); _autoSleepTimerMode = await _autoSleepTimerModeStorage.getInt();
} }
Future _getPlayerSeconds() async { Future _getPlayerSeconds() async {
_rewindSeconds = await rewindSecondsStorage.getInt(defaultValue: 10); _rewindSeconds = await _rewindSecondsStorage.getInt(defaultValue: 10);
_fastForwardSeconds = _fastForwardSeconds =
await fastForwardSecondsStorage.getInt(defaultValue: 30); await _fastForwardSecondsStorage.getInt(defaultValue: 30);
} }
Future _getLocale() async { Future _getLocale() async {
var localeString = await localeStorage.getStringList(); var localeString = await _localeStorage.getStringList();
if (localeString.isEmpty) { if (localeString.isEmpty) {
await findSystemLocale(); await findSystemLocale();
var systemLanCode; var systemLanCode;
@ -405,111 +437,119 @@ class SettingState extends ChangeNotifier {
} }
Future<void> _getShowNotesFonts() async { Future<void> _getShowNotesFonts() async {
_showNotesFontIndex = await showNotesFontStorage.getInt(defaultValue: 1); _showNotesFontIndex = await _showNotesFontStorage.getInt(defaultValue: 1);
} }
Future<void> _saveAccentSetColor() async { Future<void> _saveAccentSetColor() async {
await accentStorage await _accentStorage
.saveString(_accentSetColor.toString().substring(10, 16)); .saveString(_accentSetColor.toString().substring(10, 16));
} }
Future<void> _setRealDark() async { Future<void> _setRealDark() async {
await realDarkStorage.saveBool(_realDark); await _realDarkStorage.saveBool(_realDark);
}
Future<void> _setOpenPlaylistDefault() async {
await _openPlaylistDefaultStorage.saveBool(_openPlaylistDefault);
}
Future<void> _setOpenAllPodcastDefault() async {
await _openAllPodcastDefaultStorage.saveBool(_openAllPodcastDefault);
} }
Future<void> saveShowIntro(int i) async { Future<void> saveShowIntro(int i) async {
await introStorage.saveInt(i); await _introStorage.saveInt(i);
} }
Future<void> _saveUpdateInterval() async { Future<void> _saveUpdateInterval() async {
await intervalStorage.saveInt(_updateInterval); await _intervalStorage.saveInt(_updateInterval);
} }
Future<void> _saveTheme() async { Future<void> _saveTheme() async {
await themeStorage.saveInt(_theme.index); await _themeStorage.saveInt(_theme.index);
} }
Future<void> _saveAutoUpdate() async { Future<void> _saveAutoUpdate() async {
await autoupdateStorage.saveBool(_autoUpdate, reverse: true); await _autoupdateStorage.saveBool(_autoUpdate, reverse: true);
} }
Future<void> _saveAutoPlay() async { Future<void> _saveAutoPlay() async {
await autoPlayStorage.saveBool(_autoPlay, reverse: true); await _autoPlayStorage.saveBool(_autoPlay, reverse: true);
} }
Future<void> _setDefaultSleepTimer() async { Future<void> _setDefaultSleepTimer() async {
await defaultSleepTimerStorage.saveInt(_defaultSleepTimer); await _defaultSleepTimerStorage.saveInt(_defaultSleepTimer);
} }
Future<void> _saveAutoSleepTimer() async { Future<void> _saveAutoSleepTimer() async {
await autoSleepTimerStorage.saveBool(_autoSleepTimer); await _autoSleepTimerStorage.saveBool(_autoSleepTimer);
} }
Future<void> _saveAutoSleepTimerMode() async { Future<void> _saveAutoSleepTimerMode() async {
await autoSleepTimerModeStorage.saveInt(_autoSleepTimerMode); await _autoSleepTimerModeStorage.saveInt(_autoSleepTimerMode);
} }
Future<void> _saveAutoSleepTimerStart() async { Future<void> _saveAutoSleepTimerStart() async {
await autoSleepTimerStartStorage.saveInt(_autoSleepTimerStart); await _autoSleepTimerStartStorage.saveInt(_autoSleepTimerStart);
} }
Future<void> _saveAutoSleepTimerEnd() async { Future<void> _saveAutoSleepTimerEnd() async {
await autoSleepTimerEndStorage.saveInt(_autoSleepTimerEnd); await _autoSleepTimerEndStorage.saveInt(_autoSleepTimerEnd);
} }
Future<void> _saveFastForwardSeconds() async { Future<void> _saveFastForwardSeconds() async {
await fastForwardSecondsStorage.saveInt(_fastForwardSeconds); await _fastForwardSecondsStorage.saveInt(_fastForwardSeconds);
} }
Future<void> _saveRewindSeconds() async { Future<void> _saveRewindSeconds() async {
await rewindSecondsStorage.saveInt(_rewindSeconds); await _rewindSecondsStorage.saveInt(_rewindSeconds);
} }
Future<void> _saveShowNotesFonts() async { Future<void> _saveShowNotesFonts() async {
await showNotesFontStorage.saveInt(_showNotesFontIndex); await _showNotesFontStorage.saveInt(_showNotesFontIndex);
} }
Future<SettingsBackup> backup() async { Future<SettingsBackup> backup() async {
var theme = await themeStorage.getInt(); var theme = await _themeStorage.getInt();
var accentColor = await accentStorage.getString(); var accentColor = await _accentStorage.getString();
var realDark = await realDarkStorage.getBool(defaultValue: false); var realDark = await _realDarkStorage.getBool(defaultValue: false);
var autoPlay = var autoPlay =
await autoPlayStorage.getBool(defaultValue: true, reverse: true); await _autoPlayStorage.getBool(defaultValue: true, reverse: true);
var autoUpdate = var autoUpdate =
await autoupdateStorage.getBool(defaultValue: true, reverse: true); await _autoupdateStorage.getBool(defaultValue: true, reverse: true);
var updateInterval = await intervalStorage.getInt(); var updateInterval = await _intervalStorage.getInt();
var downloadUsingData = await downloadUsingDataStorage.getBool( var downloadUsingData = await _downloadUsingDataStorage.getBool(
defaultValue: true, reverse: true); defaultValue: true, reverse: true);
var cacheMax = await cacheStorage.getInt(defaultValue: 500 * 1024 * 1024); var cacheMax = await _cacheStorage.getInt(defaultValue: 500 * 1024 * 1024);
var podcastLayout = await podcastLayoutStorage.getInt(); var podcastLayout = await _podcastLayoutStorage.getInt();
var recentLayout = await recentLayoutStorage.getInt(); var recentLayout = await _recentLayoutStorage.getInt();
var favLayout = await favLayoutStorage.getInt(); var favLayout = await _favLayoutStorage.getInt();
var downloadLayout = await downloadLayoutStorage.getInt(); var downloadLayout = await _downloadLayoutStorage.getInt();
var autoDownloadNetwork = var autoDownloadNetwork =
await autoDownloadStorage.getBool(defaultValue: false); await _autoDownloadStorage.getBool(defaultValue: false);
var episodePopupMenu = await KeyValueStorage(episodePopupMenuKey).getMenu(); var episodePopupMenu = await KeyValueStorage(episodePopupMenuKey).getMenu();
var autoDelete = await autoDeleteStorage.getInt(); var autoDelete = await _autoDeleteStorage.getInt();
var autoSleepTimer = var autoSleepTimer =
await autoSleepTimerStorage.getBool(defaultValue: false); await _autoSleepTimerStorage.getBool(defaultValue: false);
var autoSleepTimerStart = await autoSleepTimerStartStorage.getInt(); var autoSleepTimerStart = await _autoSleepTimerStartStorage.getInt();
var autoSleepTimerEnd = await autoSleepTimerEndStorage.getInt(); var autoSleepTimerEnd = await _autoSleepTimerEndStorage.getInt();
var autoSleepTimerMode = await autoSleepTimerModeStorage.getInt(); var autoSleepTimerMode = await _autoSleepTimerModeStorage.getInt();
var defaultSleepTime = await defaultSleepTimerStorage.getInt(); var defaultSleepTime = await _defaultSleepTimerStorage.getInt();
var tapToOpenPopupMenu = await KeyValueStorage(tapToOpenPopupMenuKey) var tapToOpenPopupMenu = await KeyValueStorage(tapToOpenPopupMenuKey)
.getBool(defaultValue: false); .getBool(defaultValue: false);
var fastForwardSeconds = var fastForwardSeconds =
await fastForwardSecondsStorage.getInt(defaultValue: 30); await _fastForwardSecondsStorage.getInt(defaultValue: 30);
var rewindSeconds = await rewindSecondsStorage.getInt(defaultValue: 10); var rewindSeconds = await _rewindSecondsStorage.getInt(defaultValue: 10);
var playerHeight = var playerHeight =
await KeyValueStorage(playerHeightKey).getInt(defaultValue: 0); await KeyValueStorage(playerHeightKey).getInt(defaultValue: 0);
var localeList = await localeStorage.getStringList(); var localeList = await _localeStorage.getStringList();
var backupLocale = var backupLocale =
localeList.isEmpty ? '' : '${'${localeList.first}-'}${localeList[1]}'; localeList.isEmpty ? '' : '${'${localeList.first}-'}${localeList[1]}';
var hideListened = var hideListened =
await KeyValueStorage(hideListenedKey).getBool(defaultValue: false); await KeyValueStorage(hideListenedKey).getBool(defaultValue: false);
var notificationLayout = var notificationLayout =
await KeyValueStorage(notificationLayoutKey).getInt(defaultValue: 0); await KeyValueStorage(notificationLayoutKey).getInt(defaultValue: 0);
var showNotesFont = await showNotesFontStorage.getInt(defaultValue: 1); var showNotesFont = await _showNotesFontStorage.getInt(defaultValue: 1);
var speedList = await KeyValueStorage(speedListKey).getStringList(); var speedList = await KeyValueStorage(speedListKey).getStringList();
var hidePodcastDiscovery = await KeyValueStorage(hidePodcastDiscoveryKey) var hidePodcastDiscovery = await KeyValueStorage(hidePodcastDiscoveryKey)
.getBool(defaultValue: false); .getBool(defaultValue: false);
@ -555,37 +595,37 @@ class SettingState extends ChangeNotifier {
} }
Future<void> restore(SettingsBackup backup) async { Future<void> restore(SettingsBackup backup) async {
await themeStorage.saveInt(backup.theme); await _themeStorage.saveInt(backup.theme);
await accentStorage.saveString(backup.accentColor); await _accentStorage.saveString(backup.accentColor);
await realDarkStorage.saveBool(backup.realDark); await _realDarkStorage.saveBool(backup.realDark);
await autoPlayStorage.saveBool(backup.autoPlay, reverse: true); await _autoPlayStorage.saveBool(backup.autoPlay, reverse: true);
await autoupdateStorage.saveBool(backup.autoUpdate, reverse: true); await _autoupdateStorage.saveBool(backup.autoUpdate, reverse: true);
await intervalStorage.saveInt(backup.updateInterval); await _intervalStorage.saveInt(backup.updateInterval);
await downloadUsingDataStorage.saveBool(backup.downloadUsingData, await _downloadUsingDataStorage.saveBool(backup.downloadUsingData,
reverse: true); reverse: true);
await cacheStorage.saveInt(backup.cacheMax); await _cacheStorage.saveInt(backup.cacheMax);
await podcastLayoutStorage.saveInt(backup.podcastLayout); await _podcastLayoutStorage.saveInt(backup.podcastLayout);
await recentLayoutStorage.saveInt(backup.recentLayout); await _recentLayoutStorage.saveInt(backup.recentLayout);
await favLayoutStorage.saveInt(backup.favLayout); await _favLayoutStorage.saveInt(backup.favLayout);
await downloadLayoutStorage.saveInt(backup.downloadLayout); await _downloadLayoutStorage.saveInt(backup.downloadLayout);
await autoDownloadStorage.saveBool(backup.autoDownloadNetwork); await _autoDownloadStorage.saveBool(backup.autoDownloadNetwork);
await KeyValueStorage(episodePopupMenuKey) await KeyValueStorage(episodePopupMenuKey)
.saveStringList(backup.episodePopupMenu); .saveStringList(backup.episodePopupMenu);
await autoDeleteStorage.saveInt(backup.autoDelete); await _autoDeleteStorage.saveInt(backup.autoDelete);
await autoSleepTimerStorage.saveBool(backup.autoSleepTimer); await _autoSleepTimerStorage.saveBool(backup.autoSleepTimer);
await autoSleepTimerStartStorage.saveInt(backup.autoSleepTimerStart); await _autoSleepTimerStartStorage.saveInt(backup.autoSleepTimerStart);
await autoSleepTimerEndStorage.saveInt(backup.autoSleepTimerEnd); await _autoSleepTimerEndStorage.saveInt(backup.autoSleepTimerEnd);
await autoSleepTimerModeStorage.saveInt(backup.autoSleepTimerMode); await _autoSleepTimerModeStorage.saveInt(backup.autoSleepTimerMode);
await defaultSleepTimerStorage.saveInt(backup.defaultSleepTime); await _defaultSleepTimerStorage.saveInt(backup.defaultSleepTime);
await fastForwardSecondsStorage.saveInt(backup.fastForwardSeconds); await _fastForwardSecondsStorage.saveInt(backup.fastForwardSeconds);
await rewindSecondsStorage.saveInt(backup.rewindSeconds); await _rewindSecondsStorage.saveInt(backup.rewindSeconds);
await KeyValueStorage(playerHeightKey).saveInt(backup.playerHeight); await KeyValueStorage(playerHeightKey).saveInt(backup.playerHeight);
await KeyValueStorage(tapToOpenPopupMenuKey) await KeyValueStorage(tapToOpenPopupMenuKey)
.saveBool(backup.tapToOpenPopupMenu); .saveBool(backup.tapToOpenPopupMenu);
await KeyValueStorage(hideListenedKey).saveBool(backup.hideListened); await KeyValueStorage(hideListenedKey).saveBool(backup.hideListened);
await KeyValueStorage(notificationLayoutKey) await KeyValueStorage(notificationLayoutKey)
.saveInt(backup.notificationLayout); .saveInt(backup.notificationLayout);
await showNotesFontStorage.saveInt(backup.showNotesFont); await _showNotesFontStorage.saveInt(backup.showNotesFont);
await KeyValueStorage(speedListKey).saveStringList(backup.speedList); await KeyValueStorage(speedListKey).saveStringList(backup.speedList);
await KeyValueStorage(markListenedAfterSkipKey) await KeyValueStorage(markListenedAfterSkipKey)
.saveBool(backup.markListenedAfterSkip); .saveBool(backup.markListenedAfterSkip);
@ -593,7 +633,7 @@ class SettingState extends ChangeNotifier {
.saveBool(backup.deleteAfterPlayed); .saveBool(backup.deleteAfterPlayed);
if (backup.locale == '') { if (backup.locale == '') {
await localeStorage.saveStringList([]); await _localeStorage.saveStringList([]);
await S.load(Locale(Intl.systemLocale)); await S.load(Locale(Intl.systemLocale));
} else { } else {
var localeList = backup.locale.split('-'); var localeList = backup.locale.split('-');
@ -603,7 +643,7 @@ class SettingState extends ChangeNotifier {
} else { } else {
backupLocale = Locale(localeList.first, localeList[1]); backupLocale = Locale(localeList.first, localeList[1]);
} }
await localeStorage.saveStringList( await _localeStorage.saveStringList(
[backupLocale.languageCode, backupLocale.countryCode]); [backupLocale.languageCode, backupLocale.countryCode]);
await S.load(backupLocale); await S.load(backupLocale);
} }

View File

@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
//Slide Transition //Slide Transition
class SlideLeftRoute extends PageRouteBuilder { class SlideLeftRoute extends PageRouteBuilder {
@override
Duration get transitionDuration => Duration(milliseconds: 300);
final Widget page; final Widget page;
SlideLeftRoute({this.page}) SlideLeftRoute({this.page})
: super( : super(
@ -17,23 +19,38 @@ class SlideLeftRoute extends PageRouteBuilder {
secondaryAnimation, secondaryAnimation,
child, child,
) { ) {
var begin = Offset(1.0, 0.0);
var end = Offset.zero;
var curve = Curves.easeOutQuart;
var tween =
Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
var tweenSequence = TweenSequence(<TweenSequenceItem<Offset>>[
TweenSequenceItem<Offset>(
tween: tween,
weight: 90.0,
),
TweenSequenceItem<Offset>(
tween: ConstantTween<Offset>(Offset.zero),
weight: 10.0,
),
]);
return SlideTransition( return SlideTransition(
position: animation.drive(tweenSequence), position: Tween<Offset>(
begin: const Offset(1, 0),
end: Offset.zero,
).animate(animation),
child: child,
);
});
}
//Slide Transition
class SlideRightRoute extends PageRouteBuilder {
final Widget page;
SlideRightRoute({this.page})
: super(
pageBuilder: (
context,
animation,
secondaryAnimation,
) =>
page,
transitionsBuilder: (
context,
animation,
secondaryAnimation,
child,
) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(-1, 0),
end: Offset.zero,
).animate(animation),
child: child, child: child,
); );
}); });

View File

@ -200,7 +200,7 @@ class EpisodeCard extends StatelessWidget {
this.isPlaying, this.isPlaying,
this.canReorder = false, this.canReorder = false,
Key key}) Key key})
: super(key: key); : assert(episode != null), super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {