Add discovery page in search page.
This commit is contained in:
parent
d99e7a2e04
commit
f7dfb0b005
|
@ -8,6 +8,7 @@ import 'package:flutter/services.dart';
|
|||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tsacdop/state/search_state.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
|
|
|
@ -0,0 +1,385 @@
|
|||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import '../service/api_search.dart';
|
||||
import '../state/search_state.dart';
|
||||
import '../type/search_genre.dart';
|
||||
import '../type/searchpodcast.dart';
|
||||
import '../util/custom_widget.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
import 'search_podcast.dart';
|
||||
|
||||
class DiscoveryPage extends StatefulWidget {
|
||||
DiscoveryPage({this.onTap, Key key}) : super(key: key);
|
||||
final ValueChanged<String> onTap;
|
||||
@override
|
||||
DiscoveryPageState createState() => DiscoveryPageState();
|
||||
}
|
||||
|
||||
class DiscoveryPageState extends State<DiscoveryPage> {
|
||||
Genre _selectedGenre;
|
||||
Genre get selectedGenre => _selectedGenre;
|
||||
final List<OnlinePodcast> _podcastList = [];
|
||||
bool _loading;
|
||||
Future _searchTopPodcast;
|
||||
int _page;
|
||||
Future<List<String>> _getSearchHistory() {
|
||||
final storage = KeyValueStorage(searchHistoryKey);
|
||||
final history = storage.getStringList();
|
||||
return history;
|
||||
}
|
||||
|
||||
void backToHome() {
|
||||
setState(() {
|
||||
_selectedGenre = null;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_searchTopPodcast = _getTopPodcasts(page: 1);
|
||||
}
|
||||
|
||||
Widget _loadTopPodcasts() => Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10), color: context.primaryColor),
|
||||
width: 120,
|
||||
margin: EdgeInsets.fromLTRB(10, 10, 0, 10),
|
||||
padding: EdgeInsets.all(4),
|
||||
alignment: Alignment.topCenter,
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Center(
|
||||
child: Container(
|
||||
height: 50,
|
||||
width: 50,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: context.primaryColorDark,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
height: 2,
|
||||
child: LinearProgressIndicator(value: 0),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 80,
|
||||
height: context.textTheme.bodyText1.fontSize,
|
||||
decoration: BoxDecoration(
|
||||
color: context.primaryColorDark,
|
||||
borderRadius: BorderRadius.circular(4)),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
Container(
|
||||
width: 40,
|
||||
height: context.textTheme.bodyText1.fontSize,
|
||||
decoration: BoxDecoration(
|
||||
color: context.primaryColorDark,
|
||||
borderRadius: BorderRadius.circular(4)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
height: 32,
|
||||
child: OutlineButton(
|
||||
color: context.accentColor.withOpacity(0.5),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(100.0),
|
||||
side: BorderSide(color: Colors.grey[500])),
|
||||
highlightedBorderColor: Colors.grey[500],
|
||||
disabledTextColor: Colors.grey[500],
|
||||
child: Text(context.s.subscribe),
|
||||
disabledBorderColor: Colors.grey[500],
|
||||
onPressed: () {}),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
));
|
||||
|
||||
Future<List<OnlinePodcast>> _getTopPodcasts({int page}) async {
|
||||
final searchEngine = SearchEngine();
|
||||
var searchResult = await searchEngine.fetchBestPodcast(
|
||||
genre: '',
|
||||
page: page,
|
||||
);
|
||||
final podcastTopList =
|
||||
searchResult.podcasts.map((e) => e?.toOnlinePodcast).toList();
|
||||
_podcastList.addAll(podcastTopList.cast());
|
||||
_loading = false;
|
||||
return _podcastList;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final searchState = context.watch<SearchState>();
|
||||
return PodcastSlideup(
|
||||
child: _selectedGenre == null
|
||||
? SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
FutureBuilder<List<String>>(
|
||||
future: _getSearchHistory(),
|
||||
initialData: [],
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData && snapshot.data.isNotEmpty) {
|
||||
final history = snapshot.data;
|
||||
return SizedBox(
|
||||
height: 50,
|
||||
child: Row(
|
||||
children: history
|
||||
.map<Widget>((e) => Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: FlatButton.icon(
|
||||
color: Colors.accents[
|
||||
math.Random().nextInt(10)]
|
||||
.withAlpha(70),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(100.0),
|
||||
),
|
||||
onPressed: () => widget.onTap(e),
|
||||
label: Text(e),
|
||||
icon: Icon(
|
||||
Icons.bookmark_border,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
return SizedBox(
|
||||
height: 1,
|
||||
);
|
||||
}),
|
||||
SizedBox(
|
||||
height: 200,
|
||||
child: FutureBuilder<List<OnlinePodcast>>(
|
||||
future: _searchTopPodcast,
|
||||
builder: (context, snapshot) {
|
||||
return ScrollConfiguration(
|
||||
behavior: NoGrowBehavior(),
|
||||
child: ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
children: snapshot.hasData
|
||||
? snapshot.data.map<Widget>((podcast) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
BorderRadius.circular(10),
|
||||
color: context.primaryColor),
|
||||
width: 120,
|
||||
margin: EdgeInsets.fromLTRB(
|
||||
10, 10, 0, 10),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
borderRadius:
|
||||
BorderRadius.circular(10),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: InkWell(
|
||||
onTap: () => searchState
|
||||
.selectedPodcast = podcast,
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.all(4.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Center(
|
||||
child: PodcastAvatar(
|
||||
podcast)),
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Text(
|
||||
podcast.title,
|
||||
textAlign:
|
||||
TextAlign.center,
|
||||
maxLines: 2,
|
||||
overflow:
|
||||
TextOverflow.fade,
|
||||
style: TextStyle(
|
||||
fontWeight:
|
||||
FontWeight
|
||||
.bold),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
height: 32,
|
||||
child:
|
||||
SubscribeButton(
|
||||
podcast)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList()
|
||||
: [
|
||||
_loadTopPodcasts(),
|
||||
_loadTopPodcasts(),
|
||||
_loadTopPodcasts(),
|
||||
_loadTopPodcasts(),
|
||||
]),
|
||||
);
|
||||
}),
|
||||
),
|
||||
ListView(
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
children: genres
|
||||
.map<Widget>((e) => ListTile(
|
||||
onTap: () => setState(() => _selectedGenre = e),
|
||||
title: Text(e.name),
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
SizedBox(
|
||||
height: 40,
|
||||
child: Center(
|
||||
child: Image(
|
||||
image: context.brightness == Brightness.light
|
||||
? AssetImage('assets/listennotes.png')
|
||||
: AssetImage('assets/listennotes_light.png'),
|
||||
height: 15,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
: _TopPodcastList(genre: _selectedGenre),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TopPodcastList extends StatefulWidget {
|
||||
final Genre genre;
|
||||
_TopPodcastList({this.genre, Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
__TopPodcastListState createState() => __TopPodcastListState();
|
||||
}
|
||||
|
||||
class __TopPodcastListState extends State<_TopPodcastList> {
|
||||
final List<OnlinePodcast> _podcastList = [];
|
||||
Future _searchFuture;
|
||||
bool _loading;
|
||||
int _page;
|
||||
Future<List<OnlinePodcast>> _getTopPodcasts({Genre genre, int page}) async {
|
||||
final searchEngine = SearchEngine();
|
||||
var searchResult = await searchEngine.fetchBestPodcast(
|
||||
genre: genre.id,
|
||||
page: page,
|
||||
);
|
||||
final podcastTopList =
|
||||
searchResult.podcasts.map((e) => e?.toOnlinePodcast).toList();
|
||||
_podcastList.addAll(podcastTopList.cast());
|
||||
_loading = false;
|
||||
return _podcastList;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_page = 1;
|
||||
_searchFuture = _getTopPodcasts(genre: widget.genre, page: _page);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder(
|
||||
future: _searchFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: 200),
|
||||
alignment: Alignment.topCenter,
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
final content = snapshot.data;
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
return SearchResult(
|
||||
onlinePodcast: content[index],
|
||||
);
|
||||
},
|
||||
childCount: content.length,
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10.0, bottom: 20.0),
|
||||
child: OutlineButton(
|
||||
highlightedBorderColor: context.accentColor,
|
||||
splashColor: context.accentColor.withOpacity(0.5),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(100))),
|
||||
child: _loading
|
||||
? SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
))
|
||||
: Text(context.s.loadMore),
|
||||
onPressed: () => _loading
|
||||
? null
|
||||
: setState(
|
||||
() {
|
||||
_loading = true;
|
||||
_page++;
|
||||
print(_page);
|
||||
_searchFuture = _getTopPodcasts(
|
||||
genre: widget.genre, page: _page);
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -7,18 +7,22 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:webfeed/webfeed.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:webfeed/webfeed.dart';
|
||||
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import '../service/api_search.dart';
|
||||
import '../state/podcast_group.dart';
|
||||
import '../state/search_state.dart';
|
||||
import '../type/searchepisodes.dart';
|
||||
import '../type/searchpodcast.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
import 'pocast_discovery.dart';
|
||||
|
||||
class MyHomePageDelegate extends SearchDelegate<int> {
|
||||
final String searchFieldLabel;
|
||||
|
||||
final GlobalKey<DiscoveryPageState> _discoveryKey =
|
||||
GlobalKey<DiscoveryPageState>();
|
||||
MyHomePageDelegate({this.searchFieldLabel})
|
||||
: super(
|
||||
searchFieldLabel: searchFieldLabel,
|
||||
|
@ -44,35 +48,53 @@ class MyHomePageDelegate extends SearchDelegate<int> {
|
|||
child: Text(context.s.searchInvalidRss),
|
||||
);
|
||||
|
||||
@override
|
||||
void close(BuildContext context, int result) {
|
||||
final selectedPodcast = context.read<SearchState>().selectedPodcast;
|
||||
if (selectedPodcast != null) {
|
||||
context.read<SearchState>().clearSelect();
|
||||
} else {
|
||||
if (_discoveryKey.currentState?.selectedGenre != null) {
|
||||
_discoveryKey.currentState.backToHome();
|
||||
} else {
|
||||
context.read<SearchState>().clearList();
|
||||
super.close(context, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
ThemeData appBarTheme(BuildContext context) => Theme.of(context);
|
||||
|
||||
@override
|
||||
Widget buildLeading(BuildContext context) {
|
||||
return IconButton(
|
||||
tooltip: context.s.back,
|
||||
icon: AnimatedIcon(
|
||||
icon: AnimatedIcons.menu_arrow,
|
||||
progress: transitionAnimation,
|
||||
),
|
||||
onPressed: () {
|
||||
close(context, 1);
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
close(context, null);
|
||||
return false;
|
||||
},
|
||||
child: IconButton(
|
||||
tooltip: context.s.back,
|
||||
icon: AnimatedIcon(
|
||||
icon: AnimatedIcons.menu_arrow,
|
||||
progress: transitionAnimation,
|
||||
),
|
||||
onPressed: () {
|
||||
close(context, 1);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildSuggestions(BuildContext context) {
|
||||
return Center(
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(top: 100),
|
||||
child: Image(
|
||||
image: context.brightness == Brightness.light
|
||||
? AssetImage('assets/listennotes.png')
|
||||
: AssetImage('assets/listennotes_light.png'),
|
||||
height: 20,
|
||||
),
|
||||
));
|
||||
return DiscoveryPage(
|
||||
key: _discoveryKey,
|
||||
onTap: (history) {
|
||||
query = history;
|
||||
showResults(context);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -94,18 +116,24 @@ class MyHomePageDelegate extends SearchDelegate<int> {
|
|||
@override
|
||||
Widget buildResults(BuildContext context) {
|
||||
if (query.isEmpty) {
|
||||
return Container(
|
||||
height: 10,
|
||||
width: 10,
|
||||
margin: EdgeInsets.only(top: 400),
|
||||
child: SizedBox(
|
||||
height: 10,
|
||||
child: Image.asset(
|
||||
'assets/listennote.png',
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
);
|
||||
return DiscoveryPage(
|
||||
key: _discoveryKey,
|
||||
onTap: (history) {
|
||||
query = history;
|
||||
showResults(context);
|
||||
});
|
||||
// return Container(
|
||||
// height: 10,
|
||||
// width: 10,
|
||||
// margin: EdgeInsets.only(top: 400),
|
||||
// child: SizedBox(
|
||||
// height: 10,
|
||||
// child: Image.asset(
|
||||
// 'assets/listennotes.png',
|
||||
// fit: BoxFit.fill,
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
} else if (rssExp.stringMatch(query) != null) {
|
||||
return FutureBuilder(
|
||||
future: getRss(rssExp.stringMatch(query)),
|
||||
|
@ -360,18 +388,30 @@ class _SearchListState extends State<SearchList> {
|
|||
final List<OnlinePodcast> _podcastList = [];
|
||||
int _offset;
|
||||
bool _loading;
|
||||
OnlinePodcast _selectedPodcast;
|
||||
Future _searchFuture;
|
||||
final List<OnlinePodcast> _subscribed = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_searchFuture = _getList(widget.query, _nextOffset);
|
||||
}
|
||||
|
||||
Future<void> _saveHistory(String query) async {
|
||||
final storage = KeyValueStorage(searchHistoryKey);
|
||||
final history = await storage.getStringList();
|
||||
if (!history.contains(query)) {
|
||||
if (history.length == 10) {
|
||||
history.removeLast();
|
||||
}
|
||||
history.insert(0, query);
|
||||
await storage.saveStringList(history);
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<OnlinePodcast>> _getList(
|
||||
String searchText, int nextOffset) async {
|
||||
var searchEngine = SearchEngine();
|
||||
if (nextOffset == 0) _saveHistory(searchText);
|
||||
final searchEngine = SearchEngine();
|
||||
var searchResult = await searchEngine.searchPodcasts(
|
||||
searchText: searchText, nextOffset: nextOffset);
|
||||
_offset = searchResult.nextOffset;
|
||||
|
@ -382,151 +422,88 @@ class _SearchListState extends State<SearchList> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
alignment: Alignment.bottomCenter,
|
||||
children: [
|
||||
FutureBuilder<List>(
|
||||
future: _searchFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData && widget.query != null) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: 200),
|
||||
alignment: Alignment.topCenter,
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
var content = snapshot.data;
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
return SearchResult(
|
||||
onlinePodcast: content[index],
|
||||
isSubscribed: _subscribed.contains(content[index]),
|
||||
onSelect: (onlinePodcast) {
|
||||
setState(() {
|
||||
_selectedPodcast = onlinePodcast;
|
||||
});
|
||||
},
|
||||
onSubscribe: (onlinePodcast) {
|
||||
setState(() {
|
||||
_subscribed.add(onlinePodcast);
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
childCount: content.length,
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10.0, bottom: 20.0),
|
||||
child: OutlineButton(
|
||||
highlightedBorderColor: context.accentColor,
|
||||
splashColor: context.accentColor.withOpacity(0.5),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(100))),
|
||||
child: _loading
|
||||
? SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
))
|
||||
: Text(context.s.loadMore),
|
||||
onPressed: () => _loading
|
||||
? null
|
||||
: setState(
|
||||
() {
|
||||
_loading = true;
|
||||
_nextOffset = _offset;
|
||||
_searchFuture =
|
||||
_getList(widget.query, _nextOffset);
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
return PodcastSlideup(
|
||||
child: FutureBuilder<List>(
|
||||
future: _searchFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData && widget.query != null) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: 200),
|
||||
alignment: Alignment.topCenter,
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (_selectedPodcast != null)
|
||||
Positioned.fill(
|
||||
child: GestureDetector(
|
||||
onTap: () => setState(() => _selectedPodcast = null),
|
||||
child: Container(
|
||||
color: context.scaffoldBackgroundColor.withOpacity(0.9),
|
||||
}
|
||||
var content = snapshot.data;
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
return SearchResult(onlinePodcast: content[index]);
|
||||
},
|
||||
childCount: content.length,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_selectedPodcast != null)
|
||||
LayoutBuilder(
|
||||
builder: (context, constrants) => SearchResultDetail(
|
||||
_selectedPodcast,
|
||||
maxHeight: constrants.maxHeight,
|
||||
isSubscribed: _subscribed.contains(_selectedPodcast),
|
||||
onClose: (option) {
|
||||
setState(() => _selectedPodcast = null);
|
||||
},
|
||||
onSubscribe: (onlinePodcast) {
|
||||
setState(() {
|
||||
_subscribed.add(onlinePodcast);
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
SliverToBoxAdapter(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10.0, bottom: 20.0),
|
||||
child: OutlineButton(
|
||||
highlightedBorderColor: context.accentColor,
|
||||
splashColor: context.accentColor.withOpacity(0.5),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(100))),
|
||||
child: _loading
|
||||
? SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
))
|
||||
: Text(context.s.loadMore),
|
||||
onPressed: () => _loading
|
||||
? null
|
||||
: setState(
|
||||
() {
|
||||
_loading = true;
|
||||
_nextOffset = _offset;
|
||||
_searchFuture =
|
||||
_getList(widget.query, _nextOffset);
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SearchResult extends StatelessWidget {
|
||||
final OnlinePodcast onlinePodcast;
|
||||
final ValueChanged<OnlinePodcast> onSelect;
|
||||
final ValueChanged<OnlinePodcast> onSubscribe;
|
||||
final bool isSubscribed;
|
||||
SearchResult(
|
||||
{this.onlinePodcast,
|
||||
this.onSelect,
|
||||
this.onSubscribe,
|
||||
this.isSubscribed,
|
||||
Key key})
|
||||
: super(key: key);
|
||||
SearchResult({this.onlinePodcast, Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var subscribeWorker = Provider.of<GroupList>(context, listen: false);
|
||||
final s = context.s;
|
||||
subscribePodcast(OnlinePodcast podcast) {
|
||||
var item = SubscribeItem(podcast.rss, podcast.title,
|
||||
imgUrl: podcast.image, group: 'Home');
|
||||
subscribeWorker.setSubscribeItem(item);
|
||||
onSubscribe(podcast);
|
||||
}
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
// bottom: Divider.createBorderSide(context),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
ListTile(
|
||||
var searchState = context.watch<SearchState>();
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
ListTile(
|
||||
contentPadding: EdgeInsets.fromLTRB(20, 10, 20, 10),
|
||||
onTap: () {
|
||||
onSelect(onlinePodcast);
|
||||
searchState.selectedPodcast = onlinePodcast;
|
||||
// onSelect(onlinePodcast);
|
||||
},
|
||||
leading: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(25.0),
|
||||
|
@ -559,52 +536,18 @@ class SearchResult extends StatelessWidget {
|
|||
),
|
||||
title: Text(onlinePodcast.title),
|
||||
subtitle: Text(onlinePodcast.publisher ?? ''),
|
||||
trailing: !isSubscribed
|
||||
? OutlineButton(
|
||||
highlightedBorderColor: context.accentColor,
|
||||
borderSide: BorderSide(color: context.accentColor),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(100.0),
|
||||
side: BorderSide(color: context.accentColor)),
|
||||
splashColor: context.accentColor.withOpacity(0.5),
|
||||
child: Text(s.subscribe,
|
||||
style: TextStyle(color: context.accentColor)),
|
||||
onPressed: () {
|
||||
subscribePodcast(onlinePodcast);
|
||||
Fluttertoast.showToast(
|
||||
msg: s.podcastSubscribed,
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
})
|
||||
: OutlineButton(
|
||||
color: context.accentColor.withOpacity(0.5),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(100.0),
|
||||
side: BorderSide(color: Colors.grey[500])),
|
||||
highlightedBorderColor: Colors.grey[500],
|
||||
disabledTextColor: Colors.grey[500],
|
||||
child: Text(s.subscribe),
|
||||
disabledBorderColor: Colors.grey[500],
|
||||
onPressed: () {}),
|
||||
),
|
||||
],
|
||||
),
|
||||
trailing: SubscribeButton(onlinePodcast)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Search podcast detail widget
|
||||
class SearchResultDetail extends StatefulWidget {
|
||||
SearchResultDetail(this.onlinePodcast,
|
||||
{this.onClose,
|
||||
this.maxHeight,
|
||||
this.onSubscribe,
|
||||
this.episodeList,
|
||||
this.isSubscribed,
|
||||
Key key})
|
||||
{this.maxHeight, this.episodeList, this.isSubscribed, Key key})
|
||||
: super(key: key);
|
||||
final OnlinePodcast onlinePodcast;
|
||||
final ValueChanged<bool> onClose;
|
||||
final ValueChanged<OnlinePodcast> onSubscribe;
|
||||
final double maxHeight;
|
||||
final List<OnlineEpisode> episodeList;
|
||||
final bool isSubscribed;
|
||||
|
@ -739,22 +682,16 @@ class _SearchResultDetailState extends State<SearchResultDetail>
|
|||
_initSize = _animation.value > _minHeight - 50 ? _minHeight : 100;
|
||||
});
|
||||
_controller.forward();
|
||||
if (_animation.value < _minHeight - 50) widget.onClose(true);
|
||||
if (_animation.value < _minHeight - 50) {
|
||||
context.read<SearchState>().clearSelect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var subscribeWorker = Provider.of<GroupList>(context, listen: false);
|
||||
final s = context.s;
|
||||
subscribePodcast(OnlinePodcast podcast) {
|
||||
var item = SubscribeItem(podcast.rss, podcast.title,
|
||||
imgUrl: podcast.image, group: 'Home');
|
||||
subscribeWorker.setSubscribeItem(item);
|
||||
widget.onSubscribe(podcast);
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onVerticalDragStart: _start,
|
||||
onVerticalDragUpdate: _update,
|
||||
|
@ -806,45 +743,7 @@ class _SearchResultDetailState extends State<SearchResultDetail>
|
|||
overflow: TextOverflow.fade,
|
||||
style: TextStyle(color: context.accentColor),
|
||||
),
|
||||
!widget.isSubscribed
|
||||
? OutlineButton(
|
||||
highlightedBorderColor:
|
||||
context.accentColor,
|
||||
borderSide: BorderSide(
|
||||
color: context.accentColor,
|
||||
width: 2),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(100.0),
|
||||
side: BorderSide(
|
||||
color: context.accentColor)),
|
||||
splashColor: context.accentColor
|
||||
.withOpacity(0.5),
|
||||
child: Text(s.subscribe,
|
||||
style: TextStyle(
|
||||
color: context.accentColor)),
|
||||
onPressed: () {
|
||||
subscribePodcast(
|
||||
widget.onlinePodcast);
|
||||
Fluttertoast.showToast(
|
||||
msg: s.podcastSubscribed,
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
})
|
||||
: OutlineButton(
|
||||
color: context.accentColor
|
||||
.withOpacity(0.5),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(100.0),
|
||||
side: BorderSide(
|
||||
color: Colors.grey[500])),
|
||||
highlightedBorderColor:
|
||||
Colors.grey[500],
|
||||
disabledTextColor: Colors.grey[500],
|
||||
child: Text(s.subscribe),
|
||||
disabledBorderColor: Colors.grey[500],
|
||||
onPressed: () {})
|
||||
SubscribeButton(widget.onlinePodcast),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -1011,3 +910,128 @@ class _SearchResultDetailState extends State<SearchResultDetail>
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SubscribeButton extends StatelessWidget {
|
||||
SubscribeButton(this.onlinePodcast, {Key key}) : super(key: key);
|
||||
final OnlinePodcast onlinePodcast;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final subscribeWorker = context.watch<GroupList>();
|
||||
final searchState = context.watch<SearchState>();
|
||||
final s = context.s;
|
||||
subscribePodcast(OnlinePodcast podcast) {
|
||||
var item = SubscribeItem(podcast.rss, podcast.title,
|
||||
imgUrl: podcast.image, group: 'Home');
|
||||
subscribeWorker.setSubscribeItem(item);
|
||||
searchState.addPodcast(podcast);
|
||||
}
|
||||
|
||||
return Consumer<SearchState>(builder: (_, searchState, __) {
|
||||
final subscribed = searchState.isSubscribed(onlinePodcast);
|
||||
return !subscribed
|
||||
? OutlineButton(
|
||||
highlightedBorderColor: context.accentColor,
|
||||
borderSide: BorderSide(color: context.accentColor),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(100.0),
|
||||
side: BorderSide(color: context.accentColor)),
|
||||
splashColor: context.accentColor.withOpacity(0.5),
|
||||
child: Text(s.subscribe,
|
||||
style: TextStyle(color: context.accentColor)),
|
||||
onPressed: () {
|
||||
subscribePodcast(onlinePodcast);
|
||||
searchState.addPodcast(onlinePodcast);
|
||||
Fluttertoast.showToast(
|
||||
msg: s.podcastSubscribed,
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
})
|
||||
: OutlineButton(
|
||||
color: context.accentColor.withOpacity(0.5),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(100.0),
|
||||
side: BorderSide(color: Colors.grey[500])),
|
||||
highlightedBorderColor: Colors.grey[500],
|
||||
disabledTextColor: Colors.grey[500],
|
||||
child: Text(s.subscribe),
|
||||
disabledBorderColor: Colors.grey[500],
|
||||
onPressed: () {});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class PodcastSlideup extends StatelessWidget {
|
||||
const PodcastSlideup({this.child, Key key}) : super(key: key);
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<SearchState>(builder: (_, searchState, __) {
|
||||
final selectedPodcast = searchState.selectedPodcast;
|
||||
final subscribed = searchState.subscribedList;
|
||||
return Stack(
|
||||
alignment: Alignment.bottomCenter,
|
||||
children: [
|
||||
child,
|
||||
if (selectedPodcast != null)
|
||||
Positioned.fill(
|
||||
child: GestureDetector(
|
||||
onTap: searchState.clearSelect,
|
||||
child: Container(
|
||||
color: context.scaffoldBackgroundColor.withOpacity(0.9),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (selectedPodcast != null)
|
||||
LayoutBuilder(
|
||||
builder: (context, constrants) => SearchResultDetail(
|
||||
selectedPodcast,
|
||||
maxHeight: constrants.maxHeight,
|
||||
isSubscribed: subscribed.contains(selectedPodcast),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class PodcastAvatar extends StatelessWidget {
|
||||
const PodcastAvatar(this.podcast, {Key key}) : super(key: key);
|
||||
final OnlinePodcast podcast;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 50,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(25.0),
|
||||
child: CachedNetworkImage(
|
||||
height: 50.0,
|
||||
width: 50.0,
|
||||
fit: BoxFit.fitWidth,
|
||||
alignment: Alignment.center,
|
||||
imageUrl: podcast.image,
|
||||
progressIndicatorBuilder: (context, url, downloadProgress) =>
|
||||
Container(
|
||||
height: 50,
|
||||
width: 50,
|
||||
alignment: Alignment.center,
|
||||
color: context.primaryColorDark,
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
height: 2,
|
||||
child: LinearProgressIndicator(value: downloadProgress.progress),
|
||||
),
|
||||
),
|
||||
errorWidget: (context, url, error) => Container(
|
||||
width: 50,
|
||||
height: 50,
|
||||
alignment: Alignment.center,
|
||||
color: context.primaryColorDark,
|
||||
child: Icon(Icons.error)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ const String hideListenedKey = 'hideListenedKey';
|
|||
const String notificationLayoutKey = 'notificationLayoutKey';
|
||||
const String showNotesFontKey = 'showNotesFontKey';
|
||||
const String speedListKey = 'speedListKey';
|
||||
const String searchHistoryKey = 'searchHistoryKey';
|
||||
|
||||
class KeyValueStorage {
|
||||
final String key;
|
||||
|
|
|
@ -13,6 +13,7 @@ import 'state/audio_state.dart';
|
|||
import 'state/download_state.dart';
|
||||
import 'state/podcast_group.dart';
|
||||
import 'state/refresh_podcast.dart';
|
||||
import 'state/search_state.dart';
|
||||
import 'state/setting_state.dart';
|
||||
|
||||
final SettingState themeSetting = SettingState();
|
||||
|
@ -30,6 +31,7 @@ Future main() async {
|
|||
ChangeNotifierProvider(create: (_) => AudioPlayerNotifier()),
|
||||
ChangeNotifierProvider(create: (_) => GroupList()),
|
||||
ChangeNotifierProvider(create: (_) => RefreshWorker()),
|
||||
ChangeNotifierProvider(create: (_) => SearchState()),
|
||||
ChangeNotifierProvider(
|
||||
lazy: false,
|
||||
create: (_) => DownloadState(),
|
||||
|
|
|
@ -5,11 +5,12 @@ import 'package:dio/dio.dart';
|
|||
import '../.env.dart';
|
||||
import '../type/searchepisodes.dart';
|
||||
import '../type/searchpodcast.dart';
|
||||
import '../type/search_top_podcast.dart';
|
||||
|
||||
class SearchEngine {
|
||||
final apiKey = environment['apiKey'];
|
||||
Future<SearchPodcast<dynamic>> searchPodcasts(
|
||||
{String searchText, int nextOffset}) async {
|
||||
var apiKey = environment['apiKey'];
|
||||
var url = "https://listen-api.listennotes.com/api/v2/search?q="
|
||||
"${Uri.encodeComponent(searchText)}${"&sort_by_date=0&type=podcast&offset=$nextOffset"}";
|
||||
var response = await Dio().get(url,
|
||||
|
@ -24,7 +25,6 @@ class SearchEngine {
|
|||
|
||||
Future<SearchEpisodes<dynamic>> fetchEpisode(
|
||||
{String id, int nextEpisodeDate}) async {
|
||||
var apiKey = environment['apiKey'];
|
||||
var url =
|
||||
"https://listen-api.listennotes.com/api/v2/podcasts/$id?next_episode_pub_date=$nextEpisodeDate";
|
||||
var response = await Dio().get(url,
|
||||
|
@ -36,4 +36,18 @@ class SearchEngine {
|
|||
var searchResult = SearchEpisodes.fromJson(searchResultMap);
|
||||
return searchResult;
|
||||
}
|
||||
|
||||
Future<SearchTopPodcast<dynamic>> fetchBestPodcast(
|
||||
{String genre, int page, String region = 'us'}) async {
|
||||
var url =
|
||||
"https://listen-api.listennotes.com/api/v2/best_podcasts?genre_id=$genre&page=$page®ion=$region";
|
||||
var response = await Dio().get(url,
|
||||
options: Options(headers: {
|
||||
'X-ListenAPI-Key': "$apiKey",
|
||||
'Accept': "application/json"
|
||||
}));
|
||||
Map searchResultMap = jsonDecode(response.toString());
|
||||
var searchResult = SearchTopPodcast.fromJson(searchResultMap);
|
||||
return searchResult;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import '../type/searchpodcast.dart';
|
||||
|
||||
class SearchState extends ChangeNotifier {
|
||||
final List<OnlinePodcast> _subscribedList = [];
|
||||
bool _update;
|
||||
List<OnlinePodcast> get subscribedList => _subscribedList;
|
||||
bool get update => _update;
|
||||
OnlinePodcast _selectedPodcast;
|
||||
OnlinePodcast get selectedPodcast => _selectedPodcast;
|
||||
set selectedPodcast(OnlinePodcast podcast) {
|
||||
_selectedPodcast = podcast;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
bool isSubscribed(OnlinePodcast podcast) => _subscribedList.contains(podcast);
|
||||
|
||||
void clearSelect() {
|
||||
_selectedPodcast = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void clearList() {
|
||||
_subscribedList.clear();
|
||||
}
|
||||
|
||||
void addPodcast(OnlinePodcast podcast) {
|
||||
_subscribedList.add(podcast);
|
||||
_update = !_update;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
class Genre {
|
||||
String id;
|
||||
String name;
|
||||
Genre({this.id, this.name});
|
||||
}
|
||||
|
||||
var genres = [
|
||||
Genre(id: '144', name: 'Personal Finance'),
|
||||
Genre(id: '151', name: 'Locally Focused'),
|
||||
Genre(id: '68', name: 'TV & Film'),
|
||||
Genre(id: '127', name: 'Technology'),
|
||||
Genre(id: '135', name: 'True Crime'),
|
||||
Genre(id: '100', name: 'Arts'),
|
||||
Genre(id: '93', name: 'Business'),
|
||||
Genre(id: '67', name: 'Comedy'),
|
||||
Genre(id: '111', name: 'Education'),
|
||||
Genre(id: '168', name: 'Fiction'),
|
||||
Genre(id: '117', name: 'Government'),
|
||||
Genre(id: '88', name: 'Health & Fitness'),
|
||||
Genre(id: '125', name: 'History'),
|
||||
Genre(id: '132', name: 'Kids & Family'),
|
||||
Genre(id: '82', name: 'Leisure'),
|
||||
Genre(id: '134', name: 'Music'),
|
||||
Genre(id: '99', name: 'News'),
|
||||
Genre(id: '69', name: 'Religion & Spirituality'),
|
||||
Genre(id: '107', name: 'Science'),
|
||||
Genre(id: '122', name: 'Society & Culture'),
|
||||
Genre(id: '77', name: 'Sports'),
|
||||
];
|
|
@ -0,0 +1,76 @@
|
|||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'searchpodcast.dart';
|
||||
part 'search_top_podcast.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class SearchTopPodcast<T> {
|
||||
@_ConvertT()
|
||||
final List<T> podcasts;
|
||||
final int id;
|
||||
final int page;
|
||||
final int total;
|
||||
@JsonKey(name: 'has_next')
|
||||
final bool hasNext;
|
||||
SearchTopPodcast(
|
||||
{this.podcasts, this.id, this.total, this.hasNext, this.page});
|
||||
|
||||
factory SearchTopPodcast.fromJson(Map<String, dynamic> json) =>
|
||||
_$SearchTopPodcastFromJson<T>(json);
|
||||
Map<String, dynamic> toJson() => _$SearchTopPodcastToJson(this);
|
||||
}
|
||||
|
||||
class _ConvertT<T> implements JsonConverter<T, Object> {
|
||||
const _ConvertT();
|
||||
@override
|
||||
T fromJson(Object json) {
|
||||
return OnlineTopPodcast.fromJson(json) as T;
|
||||
}
|
||||
|
||||
@override
|
||||
Object toJson(T object) {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class OnlineTopPodcast {
|
||||
@JsonKey(name: 'earliest_pub_date_ms')
|
||||
final int earliestPubDate;
|
||||
@JsonKey(name: 'title')
|
||||
final String title;
|
||||
final String rss;
|
||||
@JsonKey(name: 'latest_pub_date_ms')
|
||||
final int latestPubDate;
|
||||
@JsonKey(name: 'description')
|
||||
final String description;
|
||||
@JsonKey(name: 'total_episodes')
|
||||
final int count;
|
||||
final String image;
|
||||
@JsonKey(name: 'publisher')
|
||||
final String publisher;
|
||||
final String id;
|
||||
OnlineTopPodcast(
|
||||
{this.earliestPubDate,
|
||||
this.title,
|
||||
this.count,
|
||||
this.description,
|
||||
this.image,
|
||||
this.latestPubDate,
|
||||
this.rss,
|
||||
this.publisher,
|
||||
this.id});
|
||||
factory OnlineTopPodcast.fromJson(Map<String, dynamic> json) =>
|
||||
_$OnlineTopPodcastFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$OnlineTopPodcastToJson(this);
|
||||
|
||||
OnlinePodcast get toOnlinePodcast => OnlinePodcast(
|
||||
earliestPubDate: earliestPubDate,
|
||||
title: title,
|
||||
count: count,
|
||||
description: description,
|
||||
image: image,
|
||||
latestPubDate: latestPubDate,
|
||||
rss: rss,
|
||||
publisher: publisher,
|
||||
id: id);
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'search_top_podcast.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
SearchTopPodcast<T> _$SearchTopPodcastFromJson<T>(Map<String, dynamic> json) {
|
||||
return SearchTopPodcast<T>(
|
||||
podcasts:
|
||||
(json['podcasts'] as List)?.map(_ConvertT<T>().fromJson)?.toList(),
|
||||
id: json['id'] as int,
|
||||
total: json['total'] as int,
|
||||
hasNext: json['has_next'] as bool,
|
||||
page: json['page'] as int,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$SearchTopPodcastToJson<T>(
|
||||
SearchTopPodcast<T> instance) =>
|
||||
<String, dynamic>{
|
||||
'podcasts': instance.podcasts?.map(_ConvertT<T>().toJson)?.toList(),
|
||||
'id': instance.id,
|
||||
'page': instance.page,
|
||||
'total': instance.total,
|
||||
'has_next': instance.hasNext,
|
||||
};
|
||||
|
||||
OnlineTopPodcast _$OnlineTopPodcastFromJson(Map<String, dynamic> json) {
|
||||
return OnlineTopPodcast(
|
||||
earliestPubDate: json['earliest_pub_date_ms'] as int,
|
||||
title: json['title'] as String,
|
||||
count: json['total_episodes'] as int,
|
||||
description: json['description'] as String,
|
||||
image: json['image'] as String,
|
||||
latestPubDate: json['latest_pub_date_ms'] as int,
|
||||
rss: json['rss'] as String,
|
||||
publisher: json['publisher'] as String,
|
||||
id: json['id'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$OnlineTopPodcastToJson(OnlineTopPodcast instance) =>
|
||||
<String, dynamic>{
|
||||
'earliest_pub_date_ms': instance.earliestPubDate,
|
||||
'title': instance.title,
|
||||
'rss': instance.rss,
|
||||
'latest_pub_date_ms': instance.latestPubDate,
|
||||
'description': instance.description,
|
||||
'total_episodes': instance.count,
|
||||
'image': instance.image,
|
||||
'publisher': instance.publisher,
|
||||
'id': instance.id,
|
||||
};
|
Loading…
Reference in New Issue