modified: lib/class/podcastlocal.dart
modified: lib/home/appbar/addpodcast.dart modified: lib/home/appbar/popupmenu.dart modified: lib/home/homescroll.dart modified: lib/local_storage/sqflite_localpodcast.dart modified: lib/main.dart modified: lib/podcasts/podcastdetail.dart modified: lib/podcasts/podcastgroup.dart modified: lib/util/episodegrid.dart modified: pubspec.lock modified: pubspec.yaml Improved UI of podcast page.
This commit is contained in:
parent
7ba0552717
commit
d4ebdf769d
|
@ -1,4 +1,3 @@
|
|||
|
||||
class PodcastLocal {
|
||||
final String title;
|
||||
final String imageUrl;
|
||||
|
@ -6,8 +5,20 @@ class PodcastLocal {
|
|||
final String author;
|
||||
String description;
|
||||
final String primaryColor;
|
||||
final String id;
|
||||
final String id;
|
||||
final String imagePath;
|
||||
PodcastLocal(this.title, this.imageUrl, this.rssUrl, this.primaryColor, this.author, this.id, this.imagePath);
|
||||
final String email;
|
||||
final String provider;
|
||||
final String link;
|
||||
PodcastLocal(
|
||||
this.title,
|
||||
this.imageUrl,
|
||||
this.rssUrl,
|
||||
this.primaryColor,
|
||||
this.author,
|
||||
this.id,
|
||||
this.imagePath,
|
||||
this.email,
|
||||
this.provider,
|
||||
this.link);
|
||||
}
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||
value: SystemUiOverlayStyle(
|
||||
statusBarIconBrightness: Theme.of(context).accentColorBrightness,
|
||||
systemNavigationBarColor: Theme.of(context).primaryColor,
|
||||
statusBarColor: Theme.of(context).primaryColor,
|
||||
statusBarColor: Theme.of(context).primaryColor,
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Scaffold(
|
||||
|
@ -83,9 +83,9 @@ class _MyHomePageDelegate extends SearchDelegate<int> {
|
|||
var searchResult = SearchPodcast.fromJson(searchResultMap);
|
||||
return searchResult.results;
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
ThemeData appBarTheme(BuildContext context) => Theme.of(context);
|
||||
ThemeData appBarTheme(BuildContext context) => Theme.of(context);
|
||||
|
||||
@override
|
||||
Widget buildLeading(BuildContext context) {
|
||||
|
@ -256,16 +256,24 @@ class _SearchResultState extends State<SearchResult> {
|
|||
String _uuid = Uuid().v4();
|
||||
File("${dir.path}/$_uuid.png")
|
||||
..writeAsBytesSync(img.encodePng(thumbnail));
|
||||
|
||||
String _imagePath = "${dir.path}/$_uuid.png";
|
||||
String _primaryColor = await getColor(File("${dir.path}/$_uuid.png"));
|
||||
String _author = _p.itunes.author ?? _p.author ?? '';
|
||||
String _email = _p.itunes.owner?.email ?? '';
|
||||
String _provider = _p.generator ?? '';
|
||||
String _link = _p.link ?? '';
|
||||
PodcastLocal podcastLocal = PodcastLocal(
|
||||
_p.title,
|
||||
_p.itunes.image.href,
|
||||
_realUrl,
|
||||
_primaryColor,
|
||||
_p.author,
|
||||
_author,
|
||||
_uuid,
|
||||
_imagePath);
|
||||
_imagePath,
|
||||
_email,
|
||||
_provider,
|
||||
_link);
|
||||
podcastLocal.description = _p.description;
|
||||
groupList.subscribe(podcastLocal);
|
||||
|
||||
|
@ -277,7 +285,7 @@ class _SearchResultState extends State<SearchResult> {
|
|||
} else {
|
||||
importOmpl.importState = ImportState.error;
|
||||
Fluttertoast.showToast(
|
||||
msg: 'POdcast Subscribed Already',
|
||||
msg: 'Podcast Subscribed Already',
|
||||
gravity: ToastGravity.TOP,
|
||||
);
|
||||
await Future.delayed(Duration(seconds: 10));
|
||||
|
@ -289,7 +297,7 @@ class _SearchResultState extends State<SearchResult> {
|
|||
msg: 'Network error, Subscribe failed',
|
||||
gravity: ToastGravity.TOP,
|
||||
);
|
||||
await Future.delayed(Duration(seconds: 10));
|
||||
await Future.delayed(Duration(seconds: 5));
|
||||
importOmpl.importState = ImportState.stop;
|
||||
}
|
||||
}
|
||||
|
@ -330,7 +338,8 @@ class _SearchResultState extends State<SearchResult> {
|
|||
? !_adding
|
||||
? OutlineButton(
|
||||
child: Text('Subscribe',
|
||||
style: TextStyle(color: Theme.of(context).accentColor)),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).accentColor)),
|
||||
onPressed: () {
|
||||
importOmpl.rssTitle = widget.onlinePodcast.title;
|
||||
savePodcast(widget.onlinePodcast.rss);
|
||||
|
|
|
@ -86,15 +86,21 @@ class PopupMenu extends StatelessWidget {
|
|||
..writeAsBytesSync(img.encodePng(thumbnail));
|
||||
String _imagePath = "${dir.path}/$_uuid.png";
|
||||
String _primaryColor = await getColor(File("${dir.path}/$_uuid.png"));
|
||||
|
||||
String _author = _p.itunes.author ?? _p.author??'';
|
||||
String _email = _p.itunes.owner.email?? '';
|
||||
String _provider = _p.generator??'';
|
||||
String _link = _p.link??'';
|
||||
PodcastLocal podcastLocal = PodcastLocal(
|
||||
_p.title,
|
||||
_p.itunes.image.href,
|
||||
_realUrl,
|
||||
_primaryColor,
|
||||
_p.author,
|
||||
_author,
|
||||
_uuid,
|
||||
_imagePath);
|
||||
_imagePath,
|
||||
_email,
|
||||
_provider,
|
||||
_link);
|
||||
|
||||
podcastLocal.description = _p.description;
|
||||
|
||||
|
|
|
@ -40,144 +40,242 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
|
|||
? Container(
|
||||
height: (_width - 20) / 3 + 110,
|
||||
)
|
||||
: DefaultTabController(
|
||||
length: groups[_groupIndex].podcastList.length,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
GestureDetector(
|
||||
onVerticalDragEnd: (event) {
|
||||
if (event.primaryVelocity > 200) {
|
||||
if (groups.length == 1) {
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Add some groups',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
} else {
|
||||
if (mounted)
|
||||
setState(() {
|
||||
(_groupIndex != 0)
|
||||
? _groupIndex--
|
||||
: _groupIndex = groups.length - 1;
|
||||
});
|
||||
}
|
||||
} else if (event.primaryVelocity < -200) {
|
||||
if (groups.length == 1) {
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Add some groups',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
} else {
|
||||
setState(() {
|
||||
(_groupIndex < groups.length - 1)
|
||||
? _groupIndex++
|
||||
: _groupIndex = 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
padding:
|
||||
EdgeInsets.symmetric(horizontal: 15.0),
|
||||
child: Text(
|
||||
groups[_groupIndex].name,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyText1
|
||||
.copyWith(color: Colors.red[300]),
|
||||
)),
|
||||
Spacer(),
|
||||
Container(
|
||||
height: 30,
|
||||
padding: EdgeInsets.symmetric(horizontal: 15),
|
||||
alignment: Alignment.bottomRight,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
SlideLeftRoute(page: PodcastManage()),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
height: 30,
|
||||
padding: EdgeInsets.all(5.0),
|
||||
: groups[_groupIndex].podcastList.length == 0
|
||||
? Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
GestureDetector(
|
||||
onVerticalDragEnd: (event) {
|
||||
if (event.primaryVelocity > 200) {
|
||||
if (groups.length == 1) {
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Add some groups',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
} else {
|
||||
if (mounted)
|
||||
setState(() {
|
||||
(_groupIndex != 0)
|
||||
? _groupIndex--
|
||||
: _groupIndex = groups.length - 1;
|
||||
});
|
||||
}
|
||||
} else if (event.primaryVelocity < -200) {
|
||||
if (groups.length == 1) {
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Add some groups',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
} else {
|
||||
setState(() {
|
||||
(_groupIndex < groups.length - 1)
|
||||
? _groupIndex++
|
||||
: _groupIndex = 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 15.0),
|
||||
child: Text(
|
||||
'See All',
|
||||
groups[_groupIndex].name,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyText1
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.accentColor),
|
||||
.copyWith(color: Colors.red[300]),
|
||||
)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
// color: Colors.white10,
|
||||
height: 70,
|
||||
width: _width,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: TabBar(
|
||||
labelPadding: EdgeInsets.only(
|
||||
top: 5.0, bottom: 10.0, left: 6.0, right: 6.0),
|
||||
indicator: CircleTabIndicator(
|
||||
color: Colors.blue, radius: 3),
|
||||
isScrollable: true,
|
||||
tabs: groups[_groupIndex]
|
||||
.podcasts
|
||||
.map<Tab>((PodcastLocal podcastLocal) {
|
||||
return Tab(
|
||||
child: ClipRRect(
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(25.0)),
|
||||
child: LimitedBox(
|
||||
maxHeight: 50,
|
||||
maxWidth: 50,
|
||||
child: Image.file(
|
||||
File("${podcastLocal.imagePath}")),
|
||||
Spacer(),
|
||||
Container(
|
||||
height: 30,
|
||||
padding:
|
||||
EdgeInsets.symmetric(horizontal: 15),
|
||||
alignment: Alignment.bottomRight,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
SlideLeftRoute(page: PodcastManage()),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
height: 30,
|
||||
padding: EdgeInsets.all(5.0),
|
||||
child: Text(
|
||||
'See All',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyText1
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.accentColor),
|
||||
)),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: 70,
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
),
|
||||
],
|
||||
)),
|
||||
Container(
|
||||
height: (_width - 20) / 3 + 40,
|
||||
color: Theme.of(context).primaryColor,
|
||||
margin: EdgeInsets.symmetric(horizontal: 15),
|
||||
),
|
||||
],
|
||||
)
|
||||
: DefaultTabController(
|
||||
length: groups[_groupIndex].podcastList.length,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
GestureDetector(
|
||||
onVerticalDragEnd: (event) {
|
||||
if (event.primaryVelocity > 200) {
|
||||
if (groups.length == 1) {
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Add some groups',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
} else {
|
||||
if (mounted)
|
||||
setState(() {
|
||||
(_groupIndex != 0)
|
||||
? _groupIndex--
|
||||
: _groupIndex = groups.length - 1;
|
||||
});
|
||||
}
|
||||
} else if (event.primaryVelocity < -200) {
|
||||
if (groups.length == 1) {
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Add some groups',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
} else {
|
||||
setState(() {
|
||||
(_groupIndex < groups.length - 1)
|
||||
? _groupIndex++
|
||||
: _groupIndex = 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 15.0),
|
||||
child: Text(
|
||||
groups[_groupIndex].name,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyText1
|
||||
.copyWith(color: Colors.red[300]),
|
||||
)),
|
||||
Spacer(),
|
||||
Container(
|
||||
height: 30,
|
||||
padding:
|
||||
EdgeInsets.symmetric(horizontal: 15),
|
||||
alignment: Alignment.bottomRight,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
SlideLeftRoute(page: PodcastManage()),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
height: 30,
|
||||
padding: EdgeInsets.all(5.0),
|
||||
child: Text(
|
||||
'See All',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyText1
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.accentColor),
|
||||
)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
// color: Colors.white10,
|
||||
height: 70,
|
||||
width: _width,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: TabBar(
|
||||
labelPadding: EdgeInsets.only(
|
||||
top: 5.0,
|
||||
bottom: 10.0,
|
||||
left: 6.0,
|
||||
right: 6.0),
|
||||
indicator: CircleTabIndicator(
|
||||
color: Colors.blue, radius: 3),
|
||||
isScrollable: true,
|
||||
tabs: groups[_groupIndex]
|
||||
.podcasts
|
||||
.map<Tab>((PodcastLocal podcastLocal) {
|
||||
return Tab(
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(25.0)),
|
||||
child: LimitedBox(
|
||||
maxHeight: 50,
|
||||
maxWidth: 50,
|
||||
child: Image.file(
|
||||
File("${podcastLocal.imagePath}")),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: (_width - 20) / 3 + 40,
|
||||
margin: EdgeInsets.only(left: 10, right: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
),
|
||||
child: TabBarView(
|
||||
children: groups[_groupIndex]
|
||||
.podcasts
|
||||
.map<Widget>((PodcastLocal podcastLocal) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).primaryColor),
|
||||
margin: EdgeInsets.symmetric(horizontal: 5.0),
|
||||
key: ObjectKey(podcastLocal.title),
|
||||
child: PodcastPreview(
|
||||
podcastLocal: podcastLocal,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Container(
|
||||
height: (_width - 20) / 3 + 40,
|
||||
margin: EdgeInsets.only(left: 10, right: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
),
|
||||
child: TabBarView(
|
||||
children: groups[_groupIndex]
|
||||
.podcasts
|
||||
.map<Widget>((PodcastLocal podcastLocal) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).primaryColor),
|
||||
margin: EdgeInsets.symmetric(horizontal: 5.0),
|
||||
key: ObjectKey(podcastLocal.title),
|
||||
child: PodcastPreview(
|
||||
podcastLocal: podcastLocal,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ class DBHelper {
|
|||
await db
|
||||
.execute("""CREATE TABLE PodcastLocal(id TEXT PRIMARY KEY,title TEXT,
|
||||
imageUrl TEXT,rssUrl TEXT UNIQUE,primaryColor TEXT,author TEXT,
|
||||
description TEXT, add_date INTEGER, imagePath TEXT)""");
|
||||
description TEXT, add_date INTEGER, imagePath TEXT, email TEXT, provider TEXT, link TEXT)""");
|
||||
await db
|
||||
.execute("""CREATE TABLE Episodes(id INTEGER PRIMARY KEY,title TEXT,
|
||||
enclosure_url TEXT UNIQUE, enclosure_length INTEGER, pubDate TEXT,
|
||||
|
@ -45,7 +45,7 @@ class DBHelper {
|
|||
await Future.forEach(podcasts, (s) async {
|
||||
List<Map> list;
|
||||
list = await dbClient.rawQuery(
|
||||
'SELECT id, title, imageUrl, rssUrl, primaryColor, author, imagePath FROM PodcastLocal WHERE id = ?',
|
||||
'SELECT id, title, imageUrl, rssUrl, primaryColor, author, imagePath , email, provider, link FROM PodcastLocal WHERE id = ?',
|
||||
[s]);
|
||||
podcastLocal.add(PodcastLocal(
|
||||
list.first['title'],
|
||||
|
@ -54,7 +54,10 @@ class DBHelper {
|
|||
list.first['primaryColor'],
|
||||
list.first['author'],
|
||||
list.first['id'],
|
||||
list.first['imagePath']));
|
||||
list.first['imagePath'],
|
||||
list.first['email'],
|
||||
list.first['provider'],
|
||||
list.first['link']));
|
||||
});
|
||||
return podcastLocal;
|
||||
}
|
||||
|
@ -62,7 +65,7 @@ class DBHelper {
|
|||
Future<List<PodcastLocal>> getPodcastLocalAll() async {
|
||||
var dbClient = await database;
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
'SELECT id, title, imageUrl, rssUrl, primaryColor, author, imagePath FROM PodcastLocal ORDER BY add_date DESC');
|
||||
'SELECT id, title, imageUrl, rssUrl, primaryColor, author, imagePath, email, provider, link FROM PodcastLocal ORDER BY add_date DESC');
|
||||
|
||||
List<PodcastLocal> podcastLocal = List();
|
||||
|
||||
|
@ -74,7 +77,10 @@ class DBHelper {
|
|||
list[i]['primaryColor'],
|
||||
list[i]['author'],
|
||||
list[i]['id'],
|
||||
list[i]['imagePath']));
|
||||
list[i]['imagePath'],
|
||||
list.first['email'],
|
||||
list.first['provider'],
|
||||
list.first['link']));
|
||||
}
|
||||
return podcastLocal;
|
||||
}
|
||||
|
@ -93,7 +99,7 @@ class DBHelper {
|
|||
await dbClient.transaction((txn) async {
|
||||
return await txn.rawInsert(
|
||||
"""INSERT OR IGNORE INTO PodcastLocal (id, title, imageUrl, rssUrl,
|
||||
primaryColor, author, description, add_date, imagePath) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||
primaryColor, author, description, add_date, imagePath, email, provider, link) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||
[
|
||||
podcastLocal.id,
|
||||
podcastLocal.title,
|
||||
|
@ -103,7 +109,10 @@ class DBHelper {
|
|||
podcastLocal.author,
|
||||
podcastLocal.description,
|
||||
_milliseconds,
|
||||
podcastLocal.imagePath
|
||||
podcastLocal.imagePath,
|
||||
podcastLocal.email,
|
||||
podcastLocal.provider,
|
||||
podcastLocal.link
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ class MyApp extends StatelessWidget {
|
|||
unselectedLabelColor: Colors.grey[400],
|
||||
),
|
||||
),
|
||||
darkTheme: ThemeData.dark(),
|
||||
darkTheme: ThemeData.dark().copyWith(accentColor: Colors.blue[400],),
|
||||
home: MyHomePage(),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,12 +1,20 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'dart:async';
|
||||
import 'package:html/parser.dart';
|
||||
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:tsacdop/class/podcastlocal.dart';
|
||||
import 'package:tsacdop/class/episodebrief.dart';
|
||||
import 'package:tsacdop/episodes/episodedetail.dart';
|
||||
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
|
||||
import 'package:tsacdop/util/episodegrid.dart';
|
||||
import 'package:tsacdop/util/pageroute.dart';
|
||||
|
||||
class PodcastDetail extends StatefulWidget {
|
||||
PodcastDetail({Key key, this.podcastLocal}) : super(key: key);
|
||||
|
@ -41,43 +49,406 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
return episodes;
|
||||
}
|
||||
|
||||
Widget podcastInfo(BuildContext context) {
|
||||
return Container(
|
||||
height: 170,
|
||||
padding: EdgeInsets.only(top: 40, left: 80, right: 120),
|
||||
alignment: Alignment.topLeft,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 10),
|
||||
child: Text(widget.podcastLocal.title,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headline5
|
||||
.copyWith(color: Colors.white))),
|
||||
);
|
||||
}
|
||||
|
||||
double top = 0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
double _width = MediaQuery.of(context).size.width;
|
||||
Color _color;
|
||||
var color = json.decode(widget.podcastLocal.primaryColor);
|
||||
(color[0] > 200 && color[1] > 200 && color[2] > 200)
|
||||
? _color = Color.fromRGBO(
|
||||
(255 - color[0]), 255 - color[1], 255 - color[2], 1.0)
|
||||
: _color = Color.fromRGBO(color[0], color[1], color[2], 1.0);
|
||||
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle(
|
||||
statusBarIconBrightness: Theme.of(context).accentColorBrightness,
|
||||
statusBarIconBrightness: Brightness.light,
|
||||
systemNavigationBarColor: Theme.of(context).primaryColor,
|
||||
statusBarColor: Theme.of(context).primaryColor,
|
||||
statusBarColor: _color,
|
||||
// statusBarColor: Theme.of(context).primaryColor,
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
widget.podcastLocal.title,
|
||||
),
|
||||
centerTitle: true,
|
||||
body: RefreshIndicator(
|
||||
key: _refreshIndicatorKey,
|
||||
color: Theme.of(context).accentColor,
|
||||
onRefresh: () => _updateRssItem(widget.podcastLocal),
|
||||
child: FutureBuilder<List<EpisodeBrief>>(
|
||||
future: _getRssItem(widget.podcastLocal),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError) print(snapshot.error);
|
||||
return (snapshot.hasData)
|
||||
? CustomScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
primary: true,
|
||||
slivers: <Widget>[
|
||||
SliverAppBar(
|
||||
elevation: 0,
|
||||
iconTheme: IconThemeData(color: Colors.white),
|
||||
expandedHeight: 170,
|
||||
backgroundColor: _color,
|
||||
floating: true,
|
||||
pinned: true,
|
||||
flexibleSpace: LayoutBuilder(builder:
|
||||
(BuildContext context,
|
||||
BoxConstraints constraints) {
|
||||
top = constraints.biggest.height;
|
||||
return FlexibleSpaceBar(
|
||||
background: Stack(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 120),
|
||||
padding: EdgeInsets.only(left: 80, right: 120),
|
||||
color: Colors.white10,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Column(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(widget.podcastLocal.author ?? '',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyText1
|
||||
.copyWith(color: Colors.grey[300])),
|
||||
Text(widget.podcastLocal.provider ?? '',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyText1
|
||||
.copyWith(color: Colors.grey[300]))
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
alignment: Alignment.centerRight,
|
||||
padding: EdgeInsets.only(right: 10),
|
||||
child: SizedBox(
|
||||
height: 120,
|
||||
child: Image.file(File(
|
||||
"${widget.podcastLocal.imagePath}")),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
alignment: Alignment.center,
|
||||
child: podcastInfo(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
title: top < 70
|
||||
? Text(widget.podcastLocal.title,
|
||||
style: TextStyle(color: Colors.white))
|
||||
: Center(),
|
||||
);
|
||||
}),
|
||||
),
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(
|
||||
left: 10.0,
|
||||
right: 10.0,
|
||||
top: 20.0,
|
||||
bottom: 10.0),
|
||||
alignment: Alignment.topLeft,
|
||||
color:
|
||||
Theme.of(context).scaffoldBackgroundColor,
|
||||
child: AboutPodcast(
|
||||
podcastLocal: widget.podcastLocal),
|
||||
);
|
||||
},
|
||||
childCount: 1,
|
||||
),
|
||||
),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.all(5.0),
|
||||
sliver: SliverGrid(
|
||||
gridDelegate:
|
||||
SliverGridDelegateWithFixedCrossAxisCount(
|
||||
childAspectRatio: 1.0,
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 6.0,
|
||||
crossAxisSpacing: 6.0,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
EpisodeBrief episodeBrief =
|
||||
snapshot.data[index];
|
||||
Color _c;
|
||||
var color = json
|
||||
.decode(widget.podcastLocal.primaryColor);
|
||||
if (Theme.of(context).brightness ==
|
||||
Brightness.light) {
|
||||
(color[0] > 200 &&
|
||||
color[1] > 200 &&
|
||||
color[2] > 200)
|
||||
? _c = Color.fromRGBO((255 - color[0]),
|
||||
255 - color[1], 255 - color[2], 1.0)
|
||||
: _c = Color.fromRGBO(
|
||||
color[0], color[1], color[2], 1.0);
|
||||
} else {
|
||||
(color[0] < 50 &&
|
||||
color[1] < 50 &&
|
||||
color[2] < 50)
|
||||
? _c = Color.fromRGBO((255 - color[0]),
|
||||
255 - color[1], 255 - color[2], 1.0)
|
||||
: _c = Color.fromRGBO(
|
||||
color[0], color[1], color[2], 1.0);
|
||||
}
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
ScaleRoute(
|
||||
page: EpisodeDetail(
|
||||
episodeItem: episodeBrief,
|
||||
heroTag: 'podcast',
|
||||
)),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(5.0)),
|
||||
color: Theme.of(context)
|
||||
.scaffoldBackgroundColor,
|
||||
border: Border.all(
|
||||
color: Theme.of(context)
|
||||
.brightness ==
|
||||
Brightness.light
|
||||
? Theme.of(context).primaryColor
|
||||
: Theme.of(context)
|
||||
.scaffoldBackgroundColor,
|
||||
width: 3.0,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Theme.of(context)
|
||||
.primaryColor,
|
||||
blurRadius: 0.5,
|
||||
spreadRadius: 0.5,
|
||||
),
|
||||
]),
|
||||
alignment: Alignment.center,
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Hero(
|
||||
tag: episodeBrief
|
||||
.enclosureUrl +
|
||||
'podcast',
|
||||
child: Container(
|
||||
child: ClipRRect(
|
||||
borderRadius:
|
||||
BorderRadius.all(
|
||||
Radius.circular(
|
||||
_width / 32)),
|
||||
child: Container(
|
||||
height: _width / 16,
|
||||
width: _width / 16,
|
||||
child: Image.file(File(
|
||||
"${episodeBrief.imagePath}")),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
Container(
|
||||
alignment: Alignment.topRight,
|
||||
child: Text(
|
||||
(snapshot.data.length -
|
||||
index)
|
||||
.toString(),
|
||||
style: GoogleFonts.teko(
|
||||
textStyle: TextStyle(
|
||||
fontSize: _width / 24,
|
||||
color: _c,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 5,
|
||||
child: Container(
|
||||
alignment: Alignment.topLeft,
|
||||
padding:
|
||||
EdgeInsets.only(top: 2.0),
|
||||
child: Text(
|
||||
episodeBrief.title,
|
||||
style: TextStyle(
|
||||
fontSize: _width / 32,
|
||||
),
|
||||
maxLines: 4,
|
||||
overflow: TextOverflow.fade,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Align(
|
||||
alignment:
|
||||
Alignment.bottomLeft,
|
||||
child: Text(
|
||||
episodeBrief.dateToString(),
|
||||
//podcast[index].pubDate.substring(4, 16),
|
||||
style: TextStyle(
|
||||
fontSize: _width / 35,
|
||||
color: _c,
|
||||
fontStyle:
|
||||
FontStyle.italic),
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
DownloadIcon(
|
||||
episodeBrief: episodeBrief),
|
||||
Padding(
|
||||
padding: EdgeInsets.all(1),
|
||||
),
|
||||
Container(
|
||||
alignment:
|
||||
Alignment.bottomRight,
|
||||
child: (episodeBrief.liked ==
|
||||
0)
|
||||
? Center()
|
||||
: IconTheme(
|
||||
data: IconThemeData(
|
||||
size: 15),
|
||||
child: Icon(
|
||||
Icons.favorite,
|
||||
color: Colors.red,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
childCount: snapshot.data.length,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Center(child: CircularProgressIndicator());
|
||||
},
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
key: _refreshIndicatorKey,
|
||||
color: Colors.blue[500],
|
||||
onRefresh: () => _updateRssItem(widget.podcastLocal),
|
||||
child: FutureBuilder<List<EpisodeBrief>>(
|
||||
future: _getRssItem(widget.podcastLocal),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError) print(snapshot.error);
|
||||
return (snapshot.hasData)
|
||||
? EpisodeGrid(
|
||||
podcast: snapshot.data,
|
||||
showDownload: true,
|
||||
showFavorite: true,
|
||||
showNumber: true,
|
||||
heroTag: 'podcast',
|
||||
)
|
||||
: Center(child: CircularProgressIndicator());
|
||||
},
|
||||
)),
|
||||
),
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AboutPodcast extends StatefulWidget {
|
||||
final PodcastLocal podcastLocal;
|
||||
AboutPodcast({this.podcastLocal, Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_AboutPodcastState createState() => _AboutPodcastState();
|
||||
}
|
||||
|
||||
class _AboutPodcastState extends State<AboutPodcast> {
|
||||
String _description;
|
||||
bool _load;
|
||||
bool _expand;
|
||||
void getDescription(String id) async {
|
||||
var dbHelper = DBHelper();
|
||||
String description = await dbHelper.getFeedDescription(id);
|
||||
if (description == null || description.isEmpty) {
|
||||
_description = '';
|
||||
} else {
|
||||
var doc = parse(description);
|
||||
_description = parse(doc.body.text).documentElement.text;
|
||||
}
|
||||
setState(() => _load = true);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_load = false;
|
||||
_expand = false;
|
||||
getDescription(widget.podcastLocal.id);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return !_load
|
||||
? Center()
|
||||
: LayoutBuilder(
|
||||
builder: (context, size) {
|
||||
final span = TextSpan(text: _description);
|
||||
final tp = TextPainter(
|
||||
text: span, maxLines: 3, textDirection: TextDirection.ltr);
|
||||
tp.layout(maxWidth: size.maxWidth);
|
||||
|
||||
if (tp.didExceedMaxLines) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
setState(() => _expand = !_expand);
|
||||
},
|
||||
child: !_expand
|
||||
? Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
_description,
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Container(
|
||||
alignment: Alignment.center,
|
||||
child: Icon(Icons.keyboard_arrow_down,),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Text(_description),
|
||||
);
|
||||
} else {
|
||||
return Text(_description);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -173,95 +173,62 @@ class _PodcastCardState extends State<PodcastCard> {
|
|||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12),
|
||||
height: 100,
|
||||
child: Row(children: <Widget>[
|
||||
Container(
|
||||
child: Icon(
|
||||
Icons.unfold_more,
|
||||
color: _c,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.all(Radius.circular(30)),
|
||||
child: Container(
|
||||
height: 60,
|
||||
width: 60,
|
||||
child: Image.file(File("${widget.podcastLocal.imagePath}")),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
child: Icon(
|
||||
Icons.unfold_more,
|
||||
color: _c,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: _width / 2,
|
||||
padding: EdgeInsets.symmetric(horizontal: 10),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
widget.podcastLocal.title,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.fade,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold, fontSize: 15),
|
||||
),
|
||||
Container(
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.all(Radius.circular(30)),
|
||||
child: Container(
|
||||
height: 60,
|
||||
width: 60,
|
||||
child: Image.file(
|
||||
File("${widget.podcastLocal.imagePath}")),
|
||||
),
|
||||
Row(
|
||||
children: _belongGroups.map((group) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(right: 5.0),
|
||||
child: Text(group.name));
|
||||
}).toList(),
|
||||
),
|
||||
],
|
||||
)),
|
||||
Spacer(),
|
||||
Icon(_loadMenu
|
||||
? Icons.keyboard_arrow_up
|
||||
: Icons.keyboard_arrow_down),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 5.0),
|
||||
),
|
||||
OutlineButton(
|
||||
child: Text('Remove'),
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
child: AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle(
|
||||
systemNavigationBarColor:
|
||||
Colors.black.withOpacity(0.5),
|
||||
statusBarColor: Colors.red,
|
||||
),
|
||||
child: AlertDialog(
|
||||
elevation: 2.0,
|
||||
title: Text('Remove confirm'),
|
||||
content: Text(
|
||||
'${widget.podcastLocal.title} will be removed from device.'),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text('CANCEL'),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: _width / 2,
|
||||
padding: EdgeInsets.symmetric(horizontal: 10),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
widget.podcastLocal.title,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.fade,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold, fontSize: 15),
|
||||
),
|
||||
FlatButton(
|
||||
onPressed: () {
|
||||
_groupList
|
||||
.removePodcast(widget.podcastLocal.id);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
'CONFIRM',
|
||||
style: TextStyle(color: Colors.red),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
));
|
||||
},
|
||||
),
|
||||
]),
|
||||
),
|
||||
Row(
|
||||
children: _belongGroups.map((group) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(right: 5.0),
|
||||
child: Text(group.name));
|
||||
}).toList(),
|
||||
),
|
||||
],
|
||||
)),
|
||||
Spacer(),
|
||||
Icon(_loadMenu
|
||||
? Icons.keyboard_arrow_up
|
||||
: Icons.keyboard_arrow_down),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 5.0),
|
||||
),
|
||||
]),
|
||||
),
|
||||
),
|
||||
!_loadMenu
|
||||
|
@ -364,7 +331,42 @@ class _PodcastCardState extends State<PodcastCard> {
|
|||
_addGroup = true;
|
||||
});
|
||||
}),
|
||||
_buttonOnMenu(Icon(Icons.notifications), () {})
|
||||
_buttonOnMenu(Icon(Icons.notifications), () {}),
|
||||
_buttonOnMenu(Icon(Icons.remove_circle), () {
|
||||
showDialog(
|
||||
context: context,
|
||||
child: AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle(
|
||||
systemNavigationBarColor:
|
||||
Colors.black.withOpacity(0.5),
|
||||
statusBarColor: Colors.red,
|
||||
),
|
||||
child: AlertDialog(
|
||||
elevation: 2.0,
|
||||
title: Text('Remove confirm'),
|
||||
content: Text(
|
||||
'${widget.podcastLocal.title} will be removed from device.'),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
onPressed: () =>
|
||||
Navigator.of(context).pop(),
|
||||
child: Text('CANCEL'),
|
||||
),
|
||||
FlatButton(
|
||||
onPressed: () {
|
||||
_groupList.removePodcast(
|
||||
widget.podcastLocal.id);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
'CONFIRM',
|
||||
style: TextStyle(color: Colors.red),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
));
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -28,7 +28,8 @@ class EpisodeGrid extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
double _width = MediaQuery.of(context).size.width;
|
||||
return CustomScrollView(
|
||||
return
|
||||
CustomScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
primary: false,
|
||||
slivers: <Widget>[
|
||||
|
|
|
@ -92,6 +92,13 @@ packages:
|
|||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.0.8"
|
||||
expandable:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: expandable
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "4.1.2"
|
||||
file_picker:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
|
@ -442,5 +449,5 @@ packages:
|
|||
source: hosted
|
||||
version: "3.5.0"
|
||||
sdks:
|
||||
dart: ">=2.6.0 <3.0.0"
|
||||
dart: ">=2.7.0 <3.0.0"
|
||||
flutter: ">=1.12.13+hotfix.4 <2.0.0"
|
||||
|
|
|
@ -48,6 +48,7 @@ dev_dependencies:
|
|||
image: ^2.1.4
|
||||
shared_preferences: ^0.5.6+1
|
||||
uuid: ^2.0.4
|
||||
expandable: ^4.1.2
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue