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:
stonegate 2020-02-23 21:20:07 +08:00
parent 7ba0552717
commit d4ebdf769d
11 changed files with 784 additions and 269 deletions

View File

@ -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);
}

View File

@ -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);

View File

@ -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;

View File

@ -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(),
),
),
],
),
);
);
});
}
}

View File

@ -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
]);
});
}

View File

@ -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(),
);
}

View File

@ -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);
}
},
);
}
}

View File

@ -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),
),
)
],
),
));
}),
],
),
),

View File

@ -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>[

View File

@ -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"

View File

@ -48,6 +48,7 @@ dev_dependencies:
image: ^2.1.4
shared_preferences: ^0.5.6+1
uuid: ^2.0.4
expandable: ^4.1.2