1
0
mirror of https://github.com/stonega/tsacdop synced 2025-02-11 09:00:36 +01:00

modified: .circleci/config.yml

modified:   lib/class/episodebrief.dart
	modified:   lib/class/podcast_group.dart
	deleted:    lib/class/podcastrss.dart
	deleted:    lib/class/podcastrss.g.dart
	deleted:    lib/class/podcasts.dart
	deleted:    lib/class/podcasts.g.dart
	modified:   lib/episodes/episodedetail.dart
	modified:   lib/home/appbar/about.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/podcasts/podcastlist.dart
	modified:   lib/podcasts/podcastmanage.dart
	modified:   lib/util/episodegrid.dart
	modified:   lib/webfeed/domain/rss_itunes.dart
	modified:   pubspec.lock
	modified:   pubspec.yaml
This commit is contained in:
stonegate 2020-02-21 23:04:02 +08:00
parent 69dfc393ba
commit 9d4bbc895a
22 changed files with 550 additions and 801 deletions

View File

@ -2,7 +2,7 @@ version: 2
jobs: jobs:
build: build:
docker: docker:
- image: cirrusci/flutter:v1.13.6 - image: cirrusci/flutter:v1.14.6
branches: branches:
only: master only: master

View File

@ -1,7 +1,9 @@
import 'package:intl/intl.dart';
class EpisodeBrief { class EpisodeBrief {
final String title; final String title;
String description; String description;
final String pubDate; final int pubDate;
final int enclosureLength; final int enclosureLength;
final String enclosureUrl; final String enclosureUrl;
final String feedTitle; final String feedTitle;
@ -24,5 +26,16 @@ class EpisodeBrief {
this.explicit, this.explicit,
this.imagePath this.imagePath
); );
String dateToString(){
DateTime date = DateTime.fromMillisecondsSinceEpoch(pubDate);
var diffrence = DateTime.now().difference(date);
if(diffrence.inHours < 24) {
return '${diffrence.inHours} hours ago';
} else if (diffrence.inDays < 7){
return '${diffrence.inDays} days ago';}
else {
return DateFormat.yMMMd().format( DateTime.fromMillisecondsSinceEpoch(pubDate));
}
}
} }

View File

@ -22,12 +22,8 @@ class GroupEntity {
static GroupEntity fromJson(Map<String, Object> json) { static GroupEntity fromJson(Map<String, Object> json) {
List<String> list = List.from(json['podcastList']); List<String> list = List.from(json['podcastList']);
print(json['[podcastList']); print(json['[podcastList']);
return return GroupEntity(json['name'] as String, json['id'] as String,
GroupEntity( json['color'] as String, list);
json['name'] as String,
json['id'] as String,
json['color'] as String,
list);
} }
} }
@ -91,11 +87,11 @@ class GroupList extends ChangeNotifier {
notifyListeners(); notifyListeners();
storage.getGroups().then((loadgroups) async { storage.getGroups().then((loadgroups) async {
_groups.addAll(loadgroups.map((e) => PodcastGroup.fromEntity(e))); _groups.addAll(loadgroups.map((e) => PodcastGroup.fromEntity(e)));
await Future.forEach(_groups, (group) async { await Future.forEach(_groups, (group) async {
await group.getPodcasts(); await group.getPodcasts();
}); });
_isLoading = false; _isLoading = false;
notifyListeners(); notifyListeners();
}); });
} }
@ -124,7 +120,7 @@ class GroupList extends ChangeNotifier {
} }
Future subscribe(PodcastLocal podcastLocal) async { Future subscribe(PodcastLocal podcastLocal) async {
_groups[0].podcastList.add(podcastLocal.id); _groups[0].podcastList.insert(0, podcastLocal.id);
_saveGroup(); _saveGroup();
await dbHelper.savePodcastLocal(podcastLocal); await dbHelper.savePodcastLocal(podcastLocal);
await _groups[0].getPodcasts(); await _groups[0].getPodcasts();
@ -132,7 +128,7 @@ class GroupList extends ChangeNotifier {
} }
List<PodcastGroup> getPodcastGroup(String id) { List<PodcastGroup> getPodcastGroup(String id) {
List<PodcastGroup> result =[]; List<PodcastGroup> result = [];
_groups.forEach((group) { _groups.forEach((group) {
if (group.podcastList.contains(id)) { if (group.podcastList.contains(id)) {
result.add(group); result.add(group);
@ -141,38 +137,42 @@ class GroupList extends ChangeNotifier {
return result; return result;
} }
changeGroup(String id, List<String> list) async{ changeGroup(String id, List<PodcastGroup> list) async {
_groups.forEach((group) { _isLoading = true;
if (group.podcastList.contains(id)) { notifyListeners();
group.podcastList.remove(id); getPodcastGroup(id).forEach((group) {
} group.podcastList.remove(id);
}); });
await Future.forEach(list, (s) { list.forEach((s) {
_groups.forEach((group) async{ s.podcastList.insert(0, id);
if (group.name == s) group.podcastList.add(id);
await group.getPodcasts();
});
}); });
_saveGroup(); _saveGroup();
await Future.forEach(_groups, (group) async {
await group.getPodcasts();
});
_isLoading = false;
notifyListeners(); notifyListeners();
} }
removePodcast(String id) async{ removePodcast(String id) async {
await Future.forEach(_groups, (group) async{ _isLoading = true;
if (group.podcastList.contains(id)) { notifyListeners();
_groups.forEach((group) async {
group.podcastList.remove(id); group.podcastList.remove(id);
await group.getPodcasts();
}
}); });
_saveGroup(); _saveGroup();
await dbHelper.delPodcastLocal(id); await dbHelper.delPodcastLocal(id);
notifyListeners(); await Future.forEach(_groups, (group) async {
await group.getPodcasts();
});
_isLoading = false;
notifyListeners();
} }
saveOrder(PodcastGroup group, List<PodcastLocal> podcasts) async{ saveOrder(PodcastGroup group, List<PodcastLocal> podcasts) async {
group.podcastList = podcasts.map((e) => e.id).toList(); group.podcastList = podcasts.map((e) => e.id).toList();
_saveGroup(); _saveGroup();
await group.getPodcasts(); await group.getPodcasts();
notifyListeners(); notifyListeners();
} }
} }

View File

@ -1,83 +0,0 @@
import 'package:json_annotation/json_annotation.dart';
part 'podcastrss.g.dart';
@JsonSerializable()
class Podcastrss<R>{
@_ConvertR()
final R rss;
Podcastrss({this.rss});
factory Podcastrss.fromJson(Map<String, dynamic> json) =>
_$PodcastrssFromJson(json);
Map<String, dynamic> toJson() => _$PodcastrssToJson(this);
}
class _ConvertR<R> implements JsonConverter<R, Object>{
const _ConvertR();
@override
R fromJson(Object json){
return Rss.fromJson(json) as R;
}
@override
Object toJson(R object){
return object;
}
}
@JsonSerializable()
class Rss<C>{
@_ConvertC()
final C channel;
Rss({this.channel});
factory Rss.fromJson(Map<String, dynamic> json) =>
_$RssFromJson(json);
Map<String, dynamic> toJson() => _$RssToJson(this);
}
class _ConvertC<C> implements JsonConverter<C, Object>{
const _ConvertC();
@override
C fromJson(Object json){
return Channel.fromJson(json) as C;
}
@override
Object toJson(C object){
return object;
}
}
@JsonSerializable()
class Channel<E> {
final String title;
final String link;
final String description;
@_ConvertE()
final List<E> item;
Channel({this.title, this.link, this.description, this.item});
factory Channel.fromJson(Map<String, dynamic> json) =>
_$ChannelFromJson(json);
Map<String, dynamic> toJson() => _$ChannelToJson(this);
}
class _ConvertE<E> implements JsonConverter<E, Object>{
const _ConvertE();
@override
E fromJson(Object json){
return EpisodeItem.fromJson(json) as E;
}
@override
Object toJson(E object){
return object;
}
}
@JsonSerializable()
class EpisodeItem{
final String title;
final String link;
final String pubDate;
final String description;
EpisodeItem({this.title, this.link, this.pubDate, this.description}
);
factory EpisodeItem.fromJson(Map<String, dynamic> json) =>
_$EpisodeItemFromJson(json);
Map<String, dynamic> toJson() => _$EpisodeItemToJson(this);
}

View File

@ -1,66 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'podcastrss.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Podcastrss<R> _$PodcastrssFromJson<R>(Map<String, dynamic> json) {
return Podcastrss<R>(
rss: json['rss'] == null ? null : _ConvertR<R>().fromJson(json['rss']));
}
Map<String, dynamic> _$PodcastrssToJson<R>(Podcastrss<R> instance) =>
<String, dynamic>{
'rss': instance.rss == null ? null : _ConvertR<R>().toJson(instance.rss)
};
Rss<C> _$RssFromJson<C>(Map<String, dynamic> json) {
return Rss<C>(
channel: json['channel'] == null
? null
: _ConvertC<C>().fromJson(json['channel']));
}
Map<String, dynamic> _$RssToJson<C>(Rss<C> instance) => <String, dynamic>{
'channel': instance.channel == null
? null
: _ConvertC<C>().toJson(instance.channel)
};
Channel<E> _$ChannelFromJson<E>(Map<String, dynamic> json) {
return Channel<E>(
title: json['title'] as String,
link: json['link'] as String,
description: json['description'] as String,
item: (json['item'] as List)
?.map((e) => e == null ? null : _ConvertE<E>().fromJson(e))
?.toList());
}
Map<String, dynamic> _$ChannelToJson<E>(Channel<E> instance) =>
<String, dynamic>{
'title': instance.title,
'link': instance.link,
'description': instance.description,
'item': instance.item
?.map((e) => e == null ? null : _ConvertE<E>().toJson(e))
?.toList()
};
EpisodeItem _$EpisodeItemFromJson(Map<String, dynamic> json) {
return EpisodeItem(
title: json['title'] as String,
link: json['link'] as String,
pubDate: json['pubDate'] as String,
description: json['description'] as String);
}
Map<String, dynamic> _$EpisodeItemToJson(EpisodeItem instance) =>
<String, dynamic>{
'title': instance.title,
'link': instance.link,
'pubDate': instance.pubDate,
'description': instance.description
};

View File

@ -1,112 +0,0 @@
import 'package:json_annotation/json_annotation.dart';
part 'podcasts.g.dart';
@JsonSerializable()
class Podcast<E, F>{
final String version;
final String title;
@JsonKey(name: 'homepage_url')
final String homepageUrl;
@JsonKey(name: 'feed_url')
final String feedUrl;
final String description;
@JsonKey(name: '_fireside')
@_ConvertF()
final F fireSide;
@JsonKey(name: 'items')
@_ConvertE()
final List<E> items;
Podcast(
{this.version, this.title, this.homepageUrl, this.feedUrl, this.description, this.fireSide, this.items}
);
factory Podcast.fromJson(Map<String, dynamic> json) =>
_$PodcastFromJson<E, F>(json);
Map<String, dynamic> toJson() => _$PodcastToJson(this);
}
class _ConvertE<E> implements JsonConverter<E, Object>{
const _ConvertE();
@override
E fromJson(Object json){
return EpisodeItem.fromJson(json) as E;
}
@override
Object toJson(E object){
return object;
}
}
class _ConvertF<F> implements JsonConverter<F, Object>{
const _ConvertF();
@override
F fromJson(Object json){
return FireSide.fromJson(json) as F;
}
@override
Object toJson(F object){
return object;
}
}
@JsonSerializable()
class FireSide{
final String pubdate;
final bool explicit;
final String copyright;
final String owner;
final String image;
FireSide({this.pubdate, this.explicit, this.copyright, this.owner, this.image});
factory FireSide.fromJson(Map<String, dynamic> json) =>
_$FireSideFromJson(json);
Map<String, dynamic> toJson() => _$FireSideToJson(this);
}
@JsonSerializable()
class EpisodeItem<A>{
final String id;
final String title;
final String url;
@JsonKey(name: 'content_text')
final String contentText;
@JsonKey(name: 'content_html')
final String contentHtml;
final String summary;
@JsonKey(name: 'date_published')
final String datePublished;
@_ConvertA()
final List<A> attachments;
EpisodeItem({this.id, this.title, this.url, this.contentText, this.contentHtml, this.summary, this.datePublished, this.attachments}
);
factory EpisodeItem.fromJson(Map<String, dynamic> json) =>
_$EpisodeItemFromJson<A>(json);
Map<String, dynamic> toJson() => _$EpisodeItemToJson(this);
}
class _ConvertA<A> implements JsonConverter<A, Object> {
const _ConvertA();
@override
A fromJson(Object json){
return Attachment.fromJson(json) as A;
}
@override
Object toJson(A object){
return object;
}
}
@JsonSerializable()
class Attachment{
final String url;
@JsonKey(name: 'mime_type')
final String mimeType;
@JsonKey(name: 'size_in_bytes')
final int sizeInBytes;
@JsonKey(name: 'duration_in_seconds')
final int durationInSeconds;
Attachment(
{this.url, this.mimeType, this.sizeInBytes, this.durationInSeconds}
);
factory Attachment.fromJson(Map<String, dynamic> json) =>
_$AttachmentFromJson(json);
Map<String, dynamic> toJson() => _$AttachmentToJson(this);
}

View File

@ -1,98 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'podcasts.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Podcast<E, F> _$PodcastFromJson<E, F>(Map<String, dynamic> json) {
return Podcast<E, F>(
version: json['version'] as String,
title: json['title'] as String,
homepageUrl: json['homepage_url'] as String,
feedUrl: json['feed_url'] as String,
description: json['description'] as String,
fireSide: json['_fireside'] == null
? null
: _ConvertF<F>().fromJson(json['_fireside']),
items: (json['items'] as List)
?.map((e) => e == null ? null : _ConvertE<E>().fromJson(e))
?.toList());
}
Map<String, dynamic> _$PodcastToJson<E, F>(Podcast<E, F> instance) =>
<String, dynamic>{
'version': instance.version,
'title': instance.title,
'homepage_url': instance.homepageUrl,
'feed_url': instance.feedUrl,
'description': instance.description,
'_fireside': instance.fireSide == null
? null
: _ConvertF<F>().toJson(instance.fireSide),
'items': instance.items
?.map((e) => e == null ? null : _ConvertE<E>().toJson(e))
?.toList()
};
FireSide _$FireSideFromJson(Map<String, dynamic> json) {
return FireSide(
pubdate: json['pubdate'] as String,
explicit: json['explicit'] as bool,
copyright: json['copyright'] as String,
owner: json['owner'] as String,
image: json['image'] as String);
}
Map<String, dynamic> _$FireSideToJson(FireSide instance) => <String, dynamic>{
'pubdate': instance.pubdate,
'explicit': instance.explicit,
'copyright': instance.copyright,
'owner': instance.owner,
'image': instance.image
};
EpisodeItem<A> _$EpisodeItemFromJson<A>(Map<String, dynamic> json) {
return EpisodeItem<A>(
id: json['id'] as String,
title: json['title'] as String,
url: json['url'] as String,
contentText: json['content_text'] as String,
contentHtml: json['content_html'] as String,
summary: json['summary'] as String,
datePublished: json['date_published'] as String,
attachments: (json['attachments'] as List)
?.map((e) => e == null ? null : _ConvertA<A>().fromJson(e))
?.toList());
}
Map<String, dynamic> _$EpisodeItemToJson<A>(EpisodeItem<A> instance) =>
<String, dynamic>{
'id': instance.id,
'title': instance.title,
'url': instance.url,
'content_text': instance.contentText,
'content_html': instance.contentHtml,
'summary': instance.summary,
'date_published': instance.datePublished,
'attachments': instance.attachments
?.map((e) => e == null ? null : _ConvertA<A>().toJson(e))
?.toList()
};
Attachment _$AttachmentFromJson(Map<String, dynamic> json) {
return Attachment(
url: json['url'] as String,
mimeType: json['mime_type'] as String,
sizeInBytes: json['size_in_bytes'] as int,
durationInSeconds: json['duration_in_seconds'] as int);
}
Map<String, dynamic> _$AttachmentToJson(Attachment instance) =>
<String, dynamic>{
'url': instance.url,
'mime_type': instance.mimeType,
'size_in_bytes': instance.sizeInBytes,
'duration_in_seconds': instance.durationInSeconds
};

View File

@ -6,6 +6,7 @@ import 'package:provider/provider.dart';
import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_html/flutter_html.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:intl/intl.dart';
import 'package:tsacdop/class/audiostate.dart'; import 'package:tsacdop/class/audiostate.dart';
import 'package:tsacdop/class/episodebrief.dart'; import 'package:tsacdop/class/episodebrief.dart';
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart'; import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
@ -86,7 +87,7 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
height: 30.0, height: 30.0,
child: Text( child: Text(
'Published ' + 'Published ' +
widget.episodeItem.pubDate.substring(0, 16), DateFormat.yMMMd().format( DateTime.fromMillisecondsSinceEpoch(widget.episodeItem.pubDate)),
style: TextStyle(color: Colors.blue[500])), style: TextStyle(color: Colors.blue[500])),
), ),
Container( Container(

View File

@ -3,13 +3,13 @@ import 'package:flutter/material.dart';
class AboutApp extends StatelessWidget { class AboutApp extends StatelessWidget {
TextSpan buildTextSpan() { TextSpan buildTextSpan() {
return TextSpan(children: [ return TextSpan(children: [
TextSpan(text: 'About Dopcast Player\n',style: TextStyle(fontSize: 20)), TextSpan(text: 'Tsacdop\n',style: TextStyle(fontSize: 20)),
TextSpan( TextSpan(
text: text:
'Dopcast Player is a podcast client developed by flutter, is a simple, easy-use player.\n'), 'Tsacdop is a podcast client developed by flutter, is a simple, easy-use player.\n'),
TextSpan( TextSpan(
text: text:
'Github https://github.com/stonga .\n'), 'Github https://github.com/stonga/tsacdop .\n'),
]); ]);
} }
@ -18,7 +18,9 @@ class AboutApp extends StatelessWidget {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
backgroundColor: Colors.grey[100], backgroundColor: Colors.grey[100],
title: Text('About'), title: Text('Tsacdop'),
centerTitle: true,
elevation: 0,
), ),
body: Container( body: Container(
padding: EdgeInsets.all(20), padding: EdgeInsets.all(20),

View File

@ -5,6 +5,7 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:color_thief_flutter/color_thief_flutter.dart'; import 'package:color_thief_flutter/color_thief_flutter.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:image/image.dart' as img; import 'package:image/image.dart' as img;
@ -31,12 +32,18 @@ class _MyHomePageState extends State<MyHomePage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
systemNavigationBarColor: Colors.grey[100],
statusBarColor: Colors.green,
),
child: Scaffold(
key: _scaffoldKey, key: _scaffoldKey,
appBar: AppBar( appBar: AppBar(
elevation: 0, elevation: 0,
centerTitle: true, centerTitle: true,
backgroundColor: Colors.grey[100], backgroundColor: Colors.grey[100],
brightness: Brightness.light,
leading: IconButton( leading: IconButton(
tooltip: 'Add', tooltip: 'Add',
icon: const Icon(Icons.add_circle_outline), icon: const Icon(Icons.add_circle_outline),
@ -56,6 +63,7 @@ class _MyHomePageState extends State<MyHomePage> {
], ],
), ),
body: Home(), body: Home(),
),
); );
} }
} }
@ -217,50 +225,66 @@ class _SearchResultState extends State<SearchResult> {
var importOmpl = Provider.of<ImportOmpl>(context, listen: false); var importOmpl = Provider.of<ImportOmpl>(context, listen: false);
var groupList = Provider.of<GroupList>(context, listen: false); var groupList = Provider.of<GroupList>(context, listen: false);
savePodcast(String rss) async { savePodcast(String rss) async {
print(rss);
if (mounted) setState(() => _adding = true); if (mounted) setState(() => _adding = true);
importOmpl.importState = ImportState.import; importOmpl.importState = ImportState.import;
try { try {
Response response = await Dio().get(rss); Response response = await Dio().get(rss);
String _realUrl = response.realUri.toString();
if (mounted) setState(() => _issubscribe = true);
var _p = RssFeed.parse(response.data);
print(_p.title);
var dir = await getApplicationDocumentsDirectory();
Response<List<int>> imageResponse = await Dio().get<List<int>>(
_p.itunes.image.href,
options: Options(responseType: ResponseType.bytes));
img.Image image = img.decodeImage(imageResponse.data);
img.Image thumbnail = img.copyResize(image, width: 300);
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"));
PodcastLocal podcastLocal = PodcastLocal(
_p.title, _p.itunes.image.href, _realUrl, _primaryColor, _p.author, _uuid, _imagePath);
podcastLocal.description = _p.description;
groupList.subscribe(podcastLocal);
importOmpl.importState = ImportState.parse;
var dbHelper = DBHelper(); var dbHelper = DBHelper();
await dbHelper.savePodcastRss(_p, _uuid); String _realUrl =
response.isRedirect ? response.realUri.toString() : rss;
bool _checkUrl = await dbHelper.checkPodcast(_realUrl);
if (_checkUrl) {
if (mounted) setState(() => _issubscribe = true);
importOmpl.importState = ImportState.complete; var _p = RssFeed.parse(response.data);
print(_p.title);
var dir = await getApplicationDocumentsDirectory();
Response<List<int>> imageResponse = await Dio().get<List<int>>(
_p.itunes.image.href,
options: Options(responseType: ResponseType.bytes));
img.Image image = img.decodeImage(imageResponse.data);
img.Image thumbnail = img.copyResize(image, width: 300);
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"));
PodcastLocal podcastLocal = PodcastLocal(
_p.title,
_p.itunes.image.href,
_realUrl,
_primaryColor,
_p.author,
_uuid,
_imagePath);
podcastLocal.description = _p.description;
groupList.subscribe(podcastLocal);
importOmpl.importState = ImportState.parse;
await dbHelper.savePodcastRss(_p, _uuid);
importOmpl.importState = ImportState.complete;
} else {
importOmpl.importState = ImportState.error;
Fluttertoast.showToast(
msg: 'POdcast Subscribed Already',
gravity: ToastGravity.TOP,
);
await Future.delayed(Duration(seconds: 10));
importOmpl.importState = ImportState.stop;
}
} catch (e) { } catch (e) {
importOmpl.importState = ImportState.error; importOmpl.importState = ImportState.error;
Fluttertoast.showToast( Fluttertoast.showToast(
msg: 'Network error, Subscribe failed', msg: 'Network error, Subscribe failed',
gravity: ToastGravity.BOTTOM, gravity: ToastGravity.TOP,
); );
await Future.delayed(Duration(seconds: 10)); await Future.delayed(Duration(seconds: 10));
importOmpl.importState = ImportState.stop; importOmpl.importState = ImportState.stop;
@ -271,12 +295,14 @@ class _SearchResultState extends State<SearchResult> {
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
ListTile( ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 30.0), contentPadding: EdgeInsets.symmetric(horizontal: 20.0),
onTap: (){setState(() { onTap: () {
_showDes = !_showDes; setState(() {
});}, _showDes = !_showDes;
});
},
leading: ClipRRect( leading: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(20.0)), borderRadius: BorderRadius.all(Radius.circular(20.0)),
child: Image.network( child: Image.network(
@ -289,46 +315,57 @@ class _SearchResultState extends State<SearchResult> {
), ),
title: Text(widget.onlinePodcast.title), title: Text(widget.onlinePodcast.title),
subtitle: Text(widget.onlinePodcast.publisher), subtitle: Text(widget.onlinePodcast.publisher),
trailing: trailing: Row(
Row(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
Icon(_showDes ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down), Icon(_showDes
Padding(padding: EdgeInsets.only(right: 10.0)), ? Icons.keyboard_arrow_up
!_issubscribe : Icons.keyboard_arrow_down),
? !_adding Padding(padding: EdgeInsets.only(right: 10.0)),
? OutlineButton( !_issubscribe
child: ? !_adding
Text('Subscribe', style: TextStyle(color: Colors.blue)), ? OutlineButton(
onPressed: () { child: Text('Subscribe',
importOmpl.rssTitle = widget.onlinePodcast.title; style: TextStyle(color: Colors.blue)),
savePodcast(widget.onlinePodcast.rss); onPressed: () {
}) importOmpl.rssTitle = widget.onlinePodcast.title;
: OutlineButton( savePodcast(widget.onlinePodcast.rss);
child: SizedBox( })
height: 20, : OutlineButton(
width: 20, child: SizedBox(
child: CircularProgressIndicator( height: 20,
strokeWidth: 2, width: 20,
valueColor: AlwaysStoppedAnimation(Colors.blue), child: CircularProgressIndicator(
)), strokeWidth: 2,
onPressed: () {}, valueColor:
) AlwaysStoppedAnimation(Colors.blue),
: OutlineButton(child: Text('Subscribe'), onPressed: (){}), )),
onPressed: () {},
)
: OutlineButton(child: Text('Subscribe'), onPressed: () {}),
], ],
), ),
), ),
_showDes ? Container( _showDes
alignment: Alignment.centerLeft, ? Container(
decoration: BoxDecoration( alignment: Alignment.centerLeft,
color: Colors.grey[300], decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(10.0)) color: Colors.grey[300],
), borderRadius: BorderRadius.only(
margin: EdgeInsets.only(left: 70, right: 50), topRight: Radius.circular(15.0),
padding: EdgeInsets.all(10.0), bottomLeft: Radius.circular(15.0),
child: Text(widget.onlinePodcast.description, maxLines: 3, overflow: TextOverflow.ellipsis,), bottomRight: Radius.circular(15.0),
) : Center(), )),
margin: EdgeInsets.only(left: 70, right: 50),
padding: EdgeInsets.all(10.0),
child: Text(
widget.onlinePodcast.description.trim(),
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
)
: Center(),
], ],
), ),
); );

View File

@ -47,10 +47,9 @@ class PopupMenu extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
ImportOmpl importOmpl = Provider.of<ImportOmpl>(context, listen: false); ImportOmpl importOmpl = Provider.of<ImportOmpl>(context, listen: false);
GroupList groupList = Provider.of<GroupList>(context, listen: false); GroupList groupList = Provider.of<GroupList>(context, listen: false);
_refreshAll() async{ _refreshAll() async {
var dbHelper = DBHelper(); var dbHelper = DBHelper();
List<PodcastLocal> podcastList = List<PodcastLocal> podcastList = await dbHelper.getPodcastLocalAll();
await dbHelper.getPodcastLocalAll();
await Future.forEach(podcastList, (podcastLocal) async { await Future.forEach(podcastList, (podcastLocal) async {
importOmpl.rssTitle = podcastLocal.title; importOmpl.rssTitle = podcastLocal.title;
importOmpl.importState = ImportState.parse; importOmpl.importState = ImportState.parse;
@ -62,10 +61,10 @@ class PopupMenu extends StatelessWidget {
saveOmpl(String rss) async { saveOmpl(String rss) async {
var dbHelper = DBHelper(); var dbHelper = DBHelper();
try { importOmpl.importState = ImportState.import;
importOmpl.importState = ImportState.import;
Response response = await Dio().get(rss);
Response response = await Dio().get(rss);
if (response.statusCode == 200) {
var _p = RssFeed.parse(response.data); var _p = RssFeed.parse(response.data);
var dir = await getApplicationDocumentsDirectory(); var dir = await getApplicationDocumentsDirectory();
@ -75,55 +74,84 @@ class PopupMenu extends StatelessWidget {
img.Image image = img.decodeImage(imageResponse.data); img.Image image = img.decodeImage(imageResponse.data);
img.Image thumbnail = img.copyResize(image, width: 300); img.Image thumbnail = img.copyResize(image, width: 300);
String _uuid = Uuid().v4(); String _uuid = Uuid().v4();
String _realUrl = response.realUri.toString(); String _realUrl =
File("${dir.path}/$_uuid.png") response.isRedirect ? response.realUri.toString() : rss;
..writeAsBytesSync(img.encodePng(thumbnail)); print(_realUrl);
String _imagePath = "${dir.path}/$_uuid.png";
String _primaryColor =
await getColor(File("${dir.path}/$_uuid.png"));
PodcastLocal podcastLocal = PodcastLocal( bool _checkUrl = await dbHelper.checkPodcast(_realUrl);
_p.title, _p.itunes.image.href, _realUrl, _primaryColor, _p.author, _uuid, _imagePath); if (_checkUrl) {
File("${dir.path}/$_uuid.png")
..writeAsBytesSync(img.encodePng(thumbnail));
String _imagePath = "${dir.path}/$_uuid.png";
String _primaryColor = await getColor(File("${dir.path}/$_uuid.png"));
podcastLocal.description = _p.description; PodcastLocal podcastLocal = PodcastLocal(
_p.title,
groupList.subscribe(podcastLocal); _p.itunes.image.href,
_realUrl,
_primaryColor,
_p.author,
_uuid,
_imagePath);
importOmpl.importState = ImportState.parse; podcastLocal.description = _p.description;
await dbHelper.savePodcastRss(_p, _uuid); groupList.subscribe(podcastLocal);
importOmpl.importState = ImportState.complete; importOmpl.importState = ImportState.parse;
} catch (e) {
await dbHelper.savePodcastRss(_p, _uuid);
importOmpl.importState = ImportState.complete;
} else {
importOmpl.importState = ImportState.error;
Fluttertoast.showToast(
msg: 'Podcast Subscribed Already',
gravity: ToastGravity.TOP,
);
await Future.delayed(Duration(seconds: 5));
importOmpl.importState = ImportState.stop;
}
}
else {
importOmpl.importState = ImportState.error; importOmpl.importState = ImportState.error;
Fluttertoast.showToast(
msg: 'Network error, Subscribe failed', Fluttertoast.showToast(
gravity: ToastGravity.BOTTOM, msg: 'Network error, Subscribe failed',
); gravity: ToastGravity.TOP,
await Future.delayed(Duration(seconds: 10)); );
importOmpl.importState = ImportState.stop; await Future.delayed(Duration(seconds: 5));
importOmpl.importState = ImportState.stop;
} }
} }
void _saveOmpl(String path) async { void _saveOmpl(String path) async {
File file = File(path); File file = File(path);
String opml = file.readAsStringSync(); String opml = file.readAsStringSync();
try {
var content = xml.parse(opml); var content = xml.parse(opml);
var total = content var total = content
.findAllElements('outline') .findAllElements('outline')
.map((ele) => OmplOutline.parse(ele)) .map((ele) => OmplOutline.parse(ele))
.toList(); .toList();
if (total.length == 0) {
Fluttertoast.showToast(
msg: 'File Not Valid',
gravity: ToastGravity.BOTTOM,
);
} else {
for (int i = 0; i < total.length; i++) { for (int i = 0; i < total.length; i++) {
if (total[i].xmlUrl != null) { if (total[i].xmlUrl != null) {
importOmpl.rssTitle = total[i].text; importOmpl.rssTitle = total[i].text;
await saveOmpl(total[i].xmlUrl); try{await saveOmpl(total[i].xmlUrl);}
catch (e){
print(e.toString());
}
print(total[i].text); print(total[i].text);
} }
} }
print('Import fisnished'); print('Import fisnished');
} catch (e) {
importOmpl.importState = ImportState.error;
} }
} }

View File

@ -5,20 +5,17 @@ import 'dart:async';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:path_provider/path_provider.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:tsacdop/class/episodebrief.dart'; import 'package:tsacdop/class/episodebrief.dart';
import 'package:tsacdop/class/podcast_group.dart'; import 'package:tsacdop/class/podcast_group.dart';
import 'package:tsacdop/class/podcastlocal.dart'; import 'package:tsacdop/class/podcastlocal.dart';
import 'package:tsacdop/class/importompl.dart';
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart'; import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
import 'package:tsacdop/episodes/episodedetail.dart'; import 'package:tsacdop/episodes/episodedetail.dart';
import 'package:tsacdop/podcasts/podcastdetail.dart'; import 'package:tsacdop/podcasts/podcastdetail.dart';
import 'package:tsacdop/podcasts/podcastmanage.dart'; import 'package:tsacdop/podcasts/podcastmanage.dart';
import 'package:tsacdop/util/pageroute.dart'; import 'package:tsacdop/util/pageroute.dart';
import 'package:tsacdop/class/settingstate.dart';
class ScrollPodcasts extends StatefulWidget { class ScrollPodcasts extends StatefulWidget {
@override @override
@ -26,35 +23,11 @@ class ScrollPodcasts extends StatefulWidget {
} }
class _ScrollPodcastsState extends State<ScrollPodcasts> { class _ScrollPodcastsState extends State<ScrollPodcasts> {
var dir;
int _groupIndex; int _groupIndex;
bool _loaded;
ImportState importState;
Update subscribeUpdate;
@override
void didChangeDependencies() {
super.didChangeDependencies();
subscribeUpdate = Provider.of<SettingState>(context).subscribeupdate;
if (subscribeUpdate == Update.backhome) {
setState(() {
_groupIndex = 0;
});
} else if (subscribeUpdate == Update.justupdate) {
setState(() {});
}
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_loaded = false;
_groupIndex = 0; _groupIndex = 0;
getApplicationDocumentsDirectory().then((value) {
dir = value.path;
setState(() => _loaded = true);
});
} }
@override @override
@ -66,10 +39,6 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
return isLoading return isLoading
? Container( ? Container(
height: (_width - 20) / 3 + 110, height: (_width - 20) / 3 + 110,
child: SizedBox(
height: 20.0,
width: 20.0,
child: CircularProgressIndicator()),
) )
: DefaultTabController( : DefaultTabController(
length: groups[_groupIndex].podcastList.length, length: groups[_groupIndex].podcastList.length,
@ -159,7 +128,8 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
indicator: CircleTabIndicator( indicator: CircleTabIndicator(
color: Colors.blue, radius: 3), color: Colors.blue, radius: 3),
isScrollable: true, isScrollable: true,
tabs: groups[_groupIndex].podcasts tabs: groups[_groupIndex]
.podcasts
.map<Tab>((PodcastLocal podcastLocal) { .map<Tab>((PodcastLocal podcastLocal) {
return Tab( return Tab(
child: ClipRRect( child: ClipRRect(
@ -168,10 +138,8 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
child: LimitedBox( child: LimitedBox(
maxHeight: 50, maxHeight: 50,
maxWidth: 50, maxWidth: 50,
child: !_loaded child: Image.file(
? CircularProgressIndicator() File("${podcastLocal.imagePath}")),
: Image.file(File(
"${podcastLocal.imagePath}")),
), ),
), ),
); );
@ -188,7 +156,8 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
color: Colors.white, color: Colors.white,
), ),
child: TabBarView( child: TabBarView(
children: groups[_groupIndex].podcasts children: groups[_groupIndex]
.podcasts
.map<Widget>((PodcastLocal podcastLocal) { .map<Widget>((PodcastLocal podcastLocal) {
return Container( return Container(
decoration: BoxDecoration(color: Colors.grey[100]), decoration: BoxDecoration(color: Colors.grey[100]),
@ -216,7 +185,6 @@ class PodcastPreview extends StatefulWidget {
} }
class _PodcastPreviewState extends State<PodcastPreview> { class _PodcastPreviewState extends State<PodcastPreview> {
Future<List<EpisodeBrief>> _getRssItemTop(PodcastLocal podcastLocal) async { Future<List<EpisodeBrief>> _getRssItemTop(PodcastLocal podcastLocal) async {
var dbHelper = DBHelper(); var dbHelper = DBHelper();
Future<List<EpisodeBrief>> episodes = Future<List<EpisodeBrief>> episodes =
@ -265,24 +233,33 @@ class _PodcastPreviewState extends State<PodcastPreview> {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Text(widget.podcastLocal.title, Expanded(
style: TextStyle(fontWeight: FontWeight.bold, color: _c)), flex: 4,
Spacer(), child: Text(widget.podcastLocal.title,
Material( maxLines: 1,
color: Colors.transparent, overflow: TextOverflow.visible,
child: IconButton( style: TextStyle(fontWeight: FontWeight.bold, color: _c)),
icon: Icon(Icons.arrow_forward), ),
splashColor: Colors.transparent, Expanded(
tooltip: 'See All', flex: 1,
onPressed: () { child: Container(
Navigator.push( alignment: Alignment.centerRight,
context, child: Material(
SlideLeftRoute( color: Colors.transparent,
page: PodcastDetail( child: IconButton(
podcastLocal: widget.podcastLocal, icon: Icon(Icons.arrow_forward),
)), tooltip: 'See All',
); onPressed: () {
}, Navigator.push(
context,
SlideLeftRoute(
page: PodcastDetail(
podcastLocal: widget.podcastLocal,
)),
);
},
),
),
), ),
), ),
], ],
@ -296,8 +273,7 @@ class _PodcastPreviewState extends State<PodcastPreview> {
class ShowEpisode extends StatelessWidget { class ShowEpisode extends StatelessWidget {
final List<EpisodeBrief> podcast; final List<EpisodeBrief> podcast;
final PodcastLocal podcastLocal; final PodcastLocal podcastLocal;
ShowEpisode({Key key, this.podcast, this.podcastLocal}) ShowEpisode({Key key, this.podcast, this.podcastLocal}) : super(key: key);
: super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
double _width = MediaQuery.of(context).size.width; double _width = MediaQuery.of(context).size.width;
@ -368,8 +344,8 @@ class ShowEpisode extends StatelessWidget {
child: Container( child: Container(
height: _width / 18, height: _width / 18,
width: _width / 18, width: _width / 18,
child: Image.file(File( child: Image.file(
"${podcastLocal.imagePath}")), File("${podcastLocal.imagePath}")),
), ),
), ),
), ),
@ -398,7 +374,8 @@ class ShowEpisode extends StatelessWidget {
child: Container( child: Container(
alignment: Alignment.bottomLeft, alignment: Alignment.bottomLeft,
child: Text( child: Text(
podcast[index].pubDate.substring(4, 16), podcast[index].dateToString(),
//podcast[index].pubDate.substring(4, 16),
style: TextStyle( style: TextStyle(
fontSize: _width / 35, fontSize: _width / 35,
color: _c, color: _c,

View File

@ -62,8 +62,10 @@ class DBHelper {
Future<List<PodcastLocal>> getPodcastLocalAll() async { Future<List<PodcastLocal>> getPodcastLocalAll() async {
var dbClient = await database; var dbClient = await database;
List<Map> list = await dbClient.rawQuery( List<Map> list = await dbClient.rawQuery(
'SELECT title, imageUrl, rssUrl, primaryColor, author, imagePath FROM PodcastLocal ORDER BY add_date DESC'); 'SELECT id, title, imageUrl, rssUrl, primaryColor, author, imagePath FROM PodcastLocal ORDER BY add_date DESC');
List<PodcastLocal> podcastLocal = List(); List<PodcastLocal> podcastLocal = List();
for (int i = 0; i < list.length; i++) { for (int i = 0; i < list.length; i++) {
podcastLocal.add(PodcastLocal( podcastLocal.add(PodcastLocal(
list[i]['title'], list[i]['title'],
@ -77,6 +79,13 @@ class DBHelper {
return podcastLocal; return podcastLocal;
} }
Future<bool> checkPodcast(String url) async {
var dbClient = await database;
List<Map> list = await dbClient
.rawQuery('SELECT id FROM PodcastLocal WHERE rssUrl = ?', [url]);
return list.length == 0;
}
Future savePodcastLocal(PodcastLocal podcastLocal) async { Future savePodcastLocal(PodcastLocal podcastLocal) async {
print('podcast saved in sqllite'); print('podcast saved in sqllite');
int _milliseconds = DateTime.now().millisecondsSinceEpoch; int _milliseconds = DateTime.now().millisecondsSinceEpoch;
@ -115,14 +124,36 @@ class DBHelper {
} }
DateTime _parsePubDate(String pubDate) { DateTime _parsePubDate(String pubDate) {
if (pubDate == null) return null; if (pubDate == null) return DateTime.now();
print(pubDate);
DateTime date; DateTime date;
RegExp yyyy = RegExp(r'[1-2][0-9]{3}');
RegExp hhmm = RegExp(r'[0-2][0-9]\:[0-5][0-9]');
RegExp ddmmm = RegExp(r'[0-3][0-9]\s[A-Z][a-z]{2}');
RegExp mmDd = RegExp(r'([0-1]|\s)[0-9]\-[0-3][0-9]');
try { try {
print('e'); date = DateFormat('EEE, dd MMM yyyy HH:mm:ss Z', 'en_US').parse(pubDate);
date = DateFormat('dd MMM yyyy HH:mm:ss Z', 'en_US').parse(pubDate);
} catch (e) { } catch (e) {
print('e'); try {
date = DateTime(0); date = DateFormat('dd MMM yyyy HH:mm:ss Z', 'en_US').parse(pubDate);
} catch (e) {
try {
date = DateFormat('EEE, dd MMM yyyy HH:mm Z', 'en_US').parse(pubDate);
} catch (e) {
//parse date using regex, bug in parse maonth/day
String year = yyyy.stringMatch(pubDate);
String time = hhmm.stringMatch(pubDate);
String month = ddmmm.stringMatch(pubDate);
if (year != null && time != null && month != null) {
date = DateFormat('dd MMM yyyy HH:mm', 'en_US')
.parse(month + year + time);
} else if(year!=null && time!=null && month == null){
String month = mmDd.stringMatch(pubDate);
date = DateFormat('mm-dd yyyy HH:mm', 'en_US')
.parse(month +' ' + year +' '+ time);
} else {date = DateTime.now();}
}
}
} }
return date; return date;
} }
@ -144,32 +175,33 @@ class DBHelper {
} }
Future<int> savePodcastRss(RssFeed _p, String id) async { Future<int> savePodcastRss(RssFeed _p, String id) async {
String _title;
String _url;
String _description;
int _duration;
int _result = _p.items.length; int _result = _p.items.length;
var dbClient = await database; var dbClient = await database;
String _description, _url;
for (int i = 0; i < _result; i++) { for (int i = 0; i < _result; i++) {
print(_p.items[i].title); print(_p.items[i].title);
_title = _p.items[i].itunes.title ?? _p.items[i].title; if (_p.items[i].itunes.summary != null) {
_p.items[i].itunes.summary.contains('<') _p.items[i].itunes.summary.contains('<')
? _description = _p.items[i].itunes.summary ? _description = _p.items[i].itunes.summary
: _description = _p.items[i].description; : _description = _p.items[i].description;
} else {
_description = _p.items[i].description;
}
isXimalaya(_p.items[i].enclosure.url) isXimalaya(_p.items[i].enclosure.url)
? _url = _p.items[i].enclosure.url.split('=').last ? _url = _p.items[i].enclosure.url.split('=').last
: _url = _p.items[i].enclosure.url; : _url = _p.items[i].enclosure.url;
final _title = _p.items[i].itunes.title ?? _p.items[i].title;
final _length = _p.items[i].enclosure.length; final _length = _p.items[i].enclosure.length;
final _pubDate = _p.items[i].pubDate; final _pubDate = _p.items[i].pubDate;
print(_pubDate);
final _date = _parsePubDate(_pubDate); final _date = _parsePubDate(_pubDate);
final _milliseconds = _date.millisecondsSinceEpoch; final _milliseconds = _date.millisecondsSinceEpoch;
final _duration = _p.items[i].itunes.duration?.inMinutes ?? 0;
_duration = _p.items[i].itunes.duration?.inMinutes ?? 0;
final _explicit = getExplicit(_p.items[i].itunes.explicit); final _explicit = getExplicit(_p.items[i].itunes.explicit);
if (_p.items[i].enclosure.url != null) {
if (_url != null) {
await dbClient.transaction((txn) { await dbClient.transaction((txn) {
return txn.rawInsert( return txn.rawInsert(
"""INSERT OR IGNORE INTO Episodes(title, enclosure_url, enclosure_length, pubDate, """INSERT OR IGNORE INTO Episodes(title, enclosure_url, enclosure_length, pubDate,
@ -194,10 +226,7 @@ class DBHelper {
Future<int> updatePodcastRss(PodcastLocal podcastLocal) async { Future<int> updatePodcastRss(PodcastLocal podcastLocal) async {
Response response = await Dio().get(podcastLocal.rssUrl); Response response = await Dio().get(podcastLocal.rssUrl);
var _p = RssFeed.parse(response.data); var _p = RssFeed.parse(response.data);
String _title; String _url, _description;
String _url;
String _description;
int _duration;
int _result = _p.items.length; int _result = _p.items.length;
var dbClient = await database; var dbClient = await database;
int _count = Sqflite.firstIntValue(await dbClient.rawQuery( int _count = Sqflite.firstIntValue(await dbClient.rawQuery(
@ -209,24 +238,27 @@ class DBHelper {
} else { } else {
for (int i = 0; i < (_result - _count); i++) { for (int i = 0; i < (_result - _count); i++) {
print(_p.items[i].title); print(_p.items[i].title);
_title = _p.items[i].itunes.title ?? _p.items[i].title; if (_p.items[i].itunes.summary != null) {
_p.items[i].itunes.summary.contains('<') _p.items[i].itunes.summary.contains('<')
? _description = _p.items[i].itunes.summary ? _description = _p.items[i].itunes.summary
: _description = _p.items[i].description; : _description = _p.items[i].description;
} else {
_description = _p.items[i].description;
}
isXimalaya(_p.items[i].enclosure.url) isXimalaya(_p.items[i].enclosure.url)
? _url = _p.items[i].enclosure.url.split('=').last ? _url = _p.items[i].enclosure.url.split('=').last
: _url = _p.items[i].enclosure.url; : _url = _p.items[i].enclosure.url;
final _title = _p.items[i].itunes.title ?? _p.items[i].title;
final _length = _p.items[i].enclosure.length; final _length = _p.items[i].enclosure.length;
final _pubDate = _p.items[i].pubDate; final _pubDate = _p.items[i].pubDate;
final _date = _parsePubDate(_pubDate); final _date = _parsePubDate(_pubDate);
final _milliseconds = _date.millisecondsSinceEpoch; final _milliseconds = _date.millisecondsSinceEpoch;
final _duration = _p.items[i].itunes.duration?.inMinutes ?? 0;
_duration = _p.items[i].itunes.duration?.inMinutes ?? 0;
final _explicit = getExplicit(_p.items[i].itunes.explicit); final _explicit = getExplicit(_p.items[i].itunes.explicit);
if (_p.items[i].enclosure.url != null) {
if (_url != null) {
await dbClient.transaction((txn) { await dbClient.transaction((txn) {
return txn.rawInsert( return txn.rawInsert(
"""INSERT OR IGNORE INTO Episodes(title, enclosure_url, enclosure_length, pubDate, """INSERT OR IGNORE INTO Episodes(title, enclosure_url, enclosure_length, pubDate,
@ -251,20 +283,20 @@ class DBHelper {
Future<List<EpisodeBrief>> getRssItem(String id) async { Future<List<EpisodeBrief>> getRssItem(String id) async {
var dbClient = await database; var dbClient = await database;
List<EpisodeBrief> episodes = List(); List<EpisodeBrief> episodes = [];
List<Map> list = await dbClient List<Map> list = await dbClient
.rawQuery("""SELECT E.title, E.enclosure_url, E.enclosure_length, .rawQuery("""SELECT E.title, E.enclosure_url, E.enclosure_length,
E.pubDate, P.imagePath, P.title as feed_title, E.duration, E.explicit, E.liked, E.milliseconds, P.imagePath, P.title as feedTitle, E.duration, E.explicit, E.liked,
E.downloaded, P.primaryColor E.downloaded, P.primaryColor
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
where E.feed_id = ? ORDER BY E.milliseconds DESC""", [id]); WHERE P.id = ? ORDER BY E.milliseconds DESC""", [id]);
for (int x = 0; x < list.length; x++) { for (int x = 0; x < list.length; x++) {
episodes.add(EpisodeBrief( episodes.add(EpisodeBrief(
list[x]['title'], list[x]['title'],
list[x]['enclosure_url'], list[x]['enclosure_url'],
list[x]['enclosure_length'], list[x]['enclosure_length'],
list[x]['pubDate'], list[x]['milliseconds'],
list[x]['feed_title'], list[x]['feedTitle'],
list[x]['primaryColor'], list[x]['primaryColor'],
list[x]['liked'], list[x]['liked'],
list[x]['downloaded'], list[x]['downloaded'],
@ -280,7 +312,7 @@ class DBHelper {
List<EpisodeBrief> episodes = List(); List<EpisodeBrief> episodes = List();
List<Map> list = await dbClient List<Map> list = await dbClient
.rawQuery("""SELECT E.title, E.enclosure_url, E.enclosure_length, .rawQuery("""SELECT E.title, E.enclosure_url, E.enclosure_length,
E.pubDate, P.imagePath, P.title as feed_title, E.duration, E.explicit, E.liked, E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit, E.liked,
E.downloaded, P.primaryColor E.downloaded, P.primaryColor
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
where E.feed_id = ? ORDER BY E.milliseconds DESC LIMIT 3""", [id]); where E.feed_id = ? ORDER BY E.milliseconds DESC LIMIT 3""", [id]);
@ -289,7 +321,7 @@ class DBHelper {
list[x]['title'], list[x]['title'],
list[x]['enclosure_url'], list[x]['enclosure_url'],
list[x]['enclosure_length'], list[x]['enclosure_length'],
list[x]['pubDate'], list[x]['milliseconds'],
list[x]['feed_title'], list[x]['feed_title'],
list[x]['primaryColor'], list[x]['primaryColor'],
list[x]['liked'], list[x]['liked'],
@ -306,7 +338,7 @@ class DBHelper {
EpisodeBrief episode; EpisodeBrief episode;
List<Map> list = await dbClient.rawQuery( List<Map> list = await dbClient.rawQuery(
"""SELECT E.title, E.enclosure_url, E.enclosure_length, """SELECT E.title, E.enclosure_url, E.enclosure_length,
E.pubDate, P.imagePath, P.title as feed_title, E.duration, E.explicit, E.liked, E.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit, E.liked,
E.downloaded, P.primaryColor E.downloaded, P.primaryColor
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
where E.enclosure_url = ? ORDER BY E.milliseconds DESC LIMIT 3""", where E.enclosure_url = ? ORDER BY E.milliseconds DESC LIMIT 3""",
@ -317,7 +349,7 @@ class DBHelper {
list.first['title'], list.first['title'],
list.first['enclosure_url'], list.first['enclosure_url'],
list.first['enclosure_length'], list.first['enclosure_length'],
list.first['pubDate'], list.first['milliseconds'],
list.first['feed_title'], list.first['feed_title'],
list.first['primaryColor'], list.first['primaryColor'],
list.first['liked'], list.first['liked'],
@ -333,7 +365,7 @@ class DBHelper {
List<EpisodeBrief> episodes = List(); List<EpisodeBrief> episodes = List();
List<Map> list = await dbClient List<Map> list = await dbClient
.rawQuery("""SELECT E.title, E.enclosure_url, E.enclosure_length, .rawQuery("""SELECT E.title, E.enclosure_url, E.enclosure_length,
E.pubDate, P.title as feed_title, E.duration, E.explicit, E.liked, E.milliseconds, P.title as feed_title, E.duration, E.explicit, E.liked,
E.downloaded, P.imagePath, P.primaryColor E.downloaded, P.imagePath, P.primaryColor
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
ORDER BY E.milliseconds DESC LIMIT 99"""); ORDER BY E.milliseconds DESC LIMIT 99""");
@ -342,15 +374,14 @@ class DBHelper {
list[x]['title'], list[x]['title'],
list[x]['enclosure_url'], list[x]['enclosure_url'],
list[x]['enclosure_length'], list[x]['enclosure_length'],
list[x]['pubDate'], list[x]['milliseconds'],
list[x]['feed_title'], list[x]['feed_title'],
list[x]['primaryColor'], list[x]['primaryColor'],
list[x]['liked'], list[x]['liked'],
list[x]['doanloaded'], list[x]['doanloaded'],
list[x]['duration'], list[x]['duration'],
list[x]['explicit'], list[x]['explicit'],
list[x]['imagePath'] list[x]['imagePath']));
));
} }
return episodes; return episodes;
} }
@ -359,7 +390,7 @@ class DBHelper {
var dbClient = await database; var dbClient = await database;
List<EpisodeBrief> episodes = List(); List<EpisodeBrief> episodes = List();
List<Map> list = await dbClient.rawQuery( List<Map> list = await dbClient.rawQuery(
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.pubDate, P.imagePath, """SELECT E.title, E.enclosure_url, E.enclosure_length, E.milliseconds, P.imagePath,
P.title as feed_title, E.duration, E.explicit, E.liked, E.downloaded, P.title as feed_title, E.duration, E.explicit, E.liked, E.downloaded,
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
WHERE E.liked = 1 ORDER BY E.milliseconds DESC LIMIT 99"""); WHERE E.liked = 1 ORDER BY E.milliseconds DESC LIMIT 99""");
@ -368,7 +399,7 @@ class DBHelper {
list[x]['title'], list[x]['title'],
list[x]['enclosure_url'], list[x]['enclosure_url'],
list[x]['enclosure_length'], list[x]['enclosure_length'],
list[x]['pubDate'], list[x]['milliseconds'],
list[x]['feed_title'], list[x]['feed_title'],
list[x]['primaryColor'], list[x]['primaryColor'],
list[x]['liked'], list[x]['liked'],
@ -417,7 +448,7 @@ class DBHelper {
var dbClient = await database; var dbClient = await database;
List<EpisodeBrief> episodes = List(); List<EpisodeBrief> episodes = List();
List<Map> list = await dbClient.rawQuery( List<Map> list = await dbClient.rawQuery(
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.pubDate, P.imagePath, """SELECT E.title, E.enclosure_url, E.enclosure_length, E.milliseconds, P.imagePath,
P.title as feed_title, E.duration, E.explicit, E.liked, E.downloaded, P.title as feed_title, E.duration, E.explicit, E.liked, E.downloaded,
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
WHERE E.downloaded != 'ND' ORDER BY E.download_date DESC LIMIT 99"""); WHERE E.downloaded != 'ND' ORDER BY E.download_date DESC LIMIT 99""");
@ -426,7 +457,7 @@ class DBHelper {
list[x]['title'], list[x]['title'],
list[x]['enclosure_url'], list[x]['enclosure_url'],
list[x]['enclosure_length'], list[x]['enclosure_length'],
list[x]['pubDate'], list[x]['milliseconds'],
list[x]['feed_title'], list[x]['feed_title'],
list[x]['primaryColor'], list[x]['primaryColor'],
list[x]['liked'], list[x]['liked'],
@ -446,10 +477,10 @@ class DBHelper {
return description; return description;
} }
Future<String> getFeedDescription(String url) async { Future<String> getFeedDescription(String id) async {
var dbClient = await database; var dbClient = await database;
List<Map> list = await dbClient.rawQuery( List<Map> list = await dbClient.rawQuery(
'SELECT description FROM PodcastLocal WHERE enclosure_url = ?', [url]); 'SELECT description FROM PodcastLocal WHERE id = ?', [id]);
String description = list[0]['description']; String description = list[0]['description'];
return description; return description;
} }
@ -458,7 +489,7 @@ class DBHelper {
var dbClient = await database; var dbClient = await database;
EpisodeBrief episode; EpisodeBrief episode;
List<Map> list = await dbClient.rawQuery( List<Map> list = await dbClient.rawQuery(
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.pubDate, P.imagePath, """SELECT E.title, E.enclosure_url, E.enclosure_length, E.milliseconds, P.imagePath,
P.title as feed_title, E.duration, E.explicit, E.liked, E.downloaded, P.title as feed_title, E.duration, E.explicit, E.liked, E.downloaded,
P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id P.primaryColor FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
WHERE E.enclosure_url = ?""", [url]); WHERE E.enclosure_url = ?""", [url]);
@ -466,7 +497,7 @@ class DBHelper {
list.first['title'], list.first['title'],
list.first['enclosure_url'], list.first['enclosure_url'],
list.first['enclosure_length'], list.first['enclosure_length'],
list.first['pubDate'], list.first['milliseconds'],
list.first['feed_title'], list.first['feed_title'],
list.first['primaryColor'], list.first['primaryColor'],
list.first['liked'], list.first['liked'],

View File

@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_statusbarcolor/flutter_statusbarcolor.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_downloader/flutter_downloader.dart'; import 'package:flutter_downloader/flutter_downloader.dart';
@ -24,14 +23,13 @@ void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await FlutterDownloader.initialize(); await FlutterDownloader.initialize();
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
await FlutterStatusbarcolor.setStatusBarColor(Colors.grey[100]);
await FlutterStatusbarcolor.setNavigationBarColor(Colors.grey[100]);
} }
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle(statusBarColor: Colors.grey[100]));
return MaterialApp( return MaterialApp(
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
title: 'TsacDop', title: 'TsacDop',

View File

@ -24,16 +24,17 @@ class _PodcastDetailState extends State<PodcastDetail> {
result == 0 ? result == 0 ?
Fluttertoast.showToast( Fluttertoast.showToast(
msg: 'No Update', msg: 'No Update',
gravity: ToastGravity.BOTTOM, gravity: ToastGravity.TOP,
) )
: Fluttertoast.showToast( : Fluttertoast.showToast(
msg: 'Updated $result Episodes', msg: 'Updated $result Episodes',
gravity: ToastGravity.BOTTOM, gravity: ToastGravity.TOP,
); );
if(mounted) setState(() {}); if(mounted) setState(() {});
} }
Future<List<EpisodeBrief>> _getRssItem(PodcastLocal podcastLocal) async { Future<List<EpisodeBrief>> _getRssItem(PodcastLocal podcastLocal) async {
print(podcastLocal.id);
var dbHelper = DBHelper(); var dbHelper = DBHelper();
List<EpisodeBrief> episodes = await List<EpisodeBrief> episodes = await
dbHelper.getRssItem(podcastLocal.id); dbHelper.getRssItem(podcastLocal.id);
@ -43,25 +44,25 @@ class _PodcastDetailState extends State<PodcastDetail> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(widget.podcastLocal.title,), title: Text(widget.podcastLocal.title,),
elevation: 0.0, elevation: 0.0,
backgroundColor: Colors.grey[100], backgroundColor: Colors.grey[100],
centerTitle: true, centerTitle: true,
), ),
body: RefreshIndicator( body: RefreshIndicator(
key: _refreshIndicatorKey, key: _refreshIndicatorKey,
color: Colors.blue[500], color: Colors.blue[500],
onRefresh: () => _updateRssItem(widget.podcastLocal), onRefresh: () => _updateRssItem(widget.podcastLocal),
child: FutureBuilder<List<EpisodeBrief>>( child: FutureBuilder<List<EpisodeBrief>>(
future: _getRssItem(widget.podcastLocal), future: _getRssItem(widget.podcastLocal),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error); if (snapshot.hasError) print(snapshot.error);
return (snapshot.hasData) return (snapshot.hasData)
? EpisodeGrid(podcast: snapshot.data, showDownload: true, showFavorite: true, showNumber: true, heroTag: 'podcast',) ? EpisodeGrid(podcast: snapshot.data, showDownload: true, showFavorite: true, showNumber: true, heroTag: 'podcast',)
: Center(child: CircularProgressIndicator()); : Center(child: CircularProgressIndicator());
}, },
)), )),
); );
} }
} }

View File

@ -7,6 +7,8 @@ import 'package:fluttertoast/fluttertoast.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tsacdop/class/podcast_group.dart'; import 'package:tsacdop/class/podcast_group.dart';
import 'package:tsacdop/class/podcastlocal.dart'; import 'package:tsacdop/class/podcastlocal.dart';
import 'package:tsacdop/podcasts/podcastdetail.dart';
import 'package:tsacdop/util/pageroute.dart';
class PodcastGroupList extends StatefulWidget { class PodcastGroupList extends StatefulWidget {
final PodcastGroup group; final PodcastGroup group;
@ -32,10 +34,15 @@ class _PodcastGroupListState extends State<PodcastGroupList> {
child: AnimatedContainer( child: AnimatedContainer(
duration: Duration(milliseconds: 800), duration: Duration(milliseconds: 800),
width: _loadSave ? 70 : 0, width: _loadSave ? 70 : 0,
height: 40, height: 60,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.blue, color: Colors.blue,
shape: BoxShape.circle, shape: BoxShape.circle,
boxShadow: [BoxShadow(
color: Colors.grey[700],
blurRadius: 5,
offset: Offset(1, 1),
),]
), ),
alignment: Alignment.center, alignment: Alignment.center,
child: Text( child: Text(
@ -86,7 +93,7 @@ class _PodcastGroupListState extends State<PodcastGroupList> {
key: ObjectKey(podcastLocal.title), key: ObjectKey(podcastLocal.title),
child: PodcastCard( child: PodcastCard(
podcastLocal: podcastLocal, podcastLocal: podcastLocal,
group: widget.group.name, group: widget.group,
), ),
); );
}).toList(), }).toList(),
@ -105,7 +112,7 @@ class _PodcastGroupListState extends State<PodcastGroupList> {
class PodcastCard extends StatefulWidget { class PodcastCard extends StatefulWidget {
final PodcastLocal podcastLocal; final PodcastLocal podcastLocal;
final String group; final PodcastGroup group;
PodcastCard({this.podcastLocal, this.group, Key key}) : super(key: key); PodcastCard({this.podcastLocal, this.group, Key key}) : super(key: key);
@override @override
_PodcastCardState createState() => _PodcastCardState(); _PodcastCardState createState() => _PodcastCardState();
@ -114,8 +121,8 @@ class PodcastCard extends StatefulWidget {
class _PodcastCardState extends State<PodcastCard> { class _PodcastCardState extends State<PodcastCard> {
bool _loadMenu; bool _loadMenu;
bool _addGroup; bool _addGroup;
List<String> _selectedGroups; List<PodcastGroup> _selectedGroups;
List<String> _belongGroups; List<PodcastGroup> _belongGroups;
Color _c; Color _c;
@override @override
@ -146,11 +153,8 @@ class _PodcastCardState extends State<PodcastCard> {
: _c = Color.fromRGBO(color[0], color[1], color[2], 1.0); : _c = Color.fromRGBO(color[0], color[1], color[2], 1.0);
double _width = MediaQuery.of(context).size.width; double _width = MediaQuery.of(context).size.width;
var _groupList = Provider.of<GroupList>(context); var _groupList = Provider.of<GroupList>(context);
_belongGroups = _groupList _belongGroups = _groupList.getPodcastGroup(widget.podcastLocal.id);
.getPodcastGroup(widget.podcastLocal.id)
.map((e) => e.name)
.toList();
return Column( return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
@ -198,7 +202,7 @@ class _PodcastCardState extends State<PodcastCard> {
children: _belongGroups.map((group) { children: _belongGroups.map((group) {
return Container( return Container(
padding: EdgeInsets.only(right: 5.0), padding: EdgeInsets.only(right: 5.0),
child: Text(group)); child: Text(group.name));
}).toList(), }).toList(),
), ),
], ],
@ -263,21 +267,20 @@ class _PodcastCardState extends State<PodcastCard> {
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
child: Row( child: Row(
children: _groupList.groups children: _groupList.groups
.map<Widget>((PodcastGroup group){ .map<Widget>((PodcastGroup group) {
return Container( return Container(
padding: EdgeInsets.only(left: 5.0), padding: EdgeInsets.only(left: 5.0),
child: FilterChip( child: FilterChip(
key: ValueKey<String>(group.id), key: ValueKey<String>(group.id),
label: Text(group.name), label: Text(group.name),
selected: selected: _selectedGroups.contains(group),
_selectedGroups.contains(group.name),
onSelected: (bool value) { onSelected: (bool value) {
setState(() { setState(() {
if (!value) { if (!value) {
_selectedGroups.remove(group.name); _selectedGroups.remove(group);
print(group.name); print(group.name);
} else { } else {
_selectedGroups.add(group.name); _selectedGroups.add(group);
} }
}); });
}, },
@ -327,7 +330,15 @@ class _PodcastCardState extends State<PodcastCard> {
: Row( : Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[ children: <Widget>[
_buttonOnMenu(Icon(Icons.fullscreen), () {}), _buttonOnMenu(
Icon(Icons.fullscreen),
() => Navigator.push(
context,
ScaleRoute(
page: PodcastDetail(
podcastLocal: widget.podcastLocal,
)),
)),
_buttonOnMenu(Icon(Icons.add), () { _buttonOnMenu(Icon(Icons.add), () {
setState(() { setState(() {
_addGroup = true; _addGroup = true;

View File

@ -25,9 +25,9 @@ class _AboutPodcastState extends State<AboutPodcast> {
String _description; String _description;
bool _load; bool _load;
void getDescription(String title) async { void getDescription(String id) async {
var dbHelper = DBHelper(); var dbHelper = DBHelper();
String description = await dbHelper.getFeedDescription(title); String description = await dbHelper.getFeedDescription(id);
_description = description; _description = description;
setState(() { setState(() {
_load = true; _load = true;
@ -38,7 +38,7 @@ class _AboutPodcastState extends State<AboutPodcast> {
void initState() { void initState() {
super.initState(); super.initState();
_load = false; _load = false;
getDescription(widget.podcastLocal.title); getDescription(widget.podcastLocal.id);
} }
@override @override

View File

@ -13,7 +13,7 @@ class PodcastManage extends StatefulWidget {
class _PodcastManageState extends State<PodcastManage> { class _PodcastManageState extends State<PodcastManage> {
Decoration getIndicator() { Decoration getIndicator() {
return const UnderlineTabIndicator( return const UnderlineTabIndicator(
borderSide: BorderSide(color: Colors.red, width: 2), borderSide: BorderSide(color: Colors.red, width: 0),
insets: EdgeInsets.only( insets: EdgeInsets.only(
top: 10.0, top: 10.0,
)); ));
@ -50,44 +50,57 @@ class _PodcastManageState extends State<PodcastManage> {
Row( Row(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
flex: 4,
child: Container( child: Container(
height: 50, height: 50,
padding: EdgeInsets.symmetric(horizontal: 10.0), padding: EdgeInsets.symmetric(horizontal: 10.0),
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: TabBar( child: TabBar(
labelPadding: EdgeInsets.only( labelColor: Colors.black,
top: 5.0, unselectedLabelColor: Colors.grey[500],
bottom: 6.0, labelPadding: EdgeInsets.all(5.0),
left: 10.0,
right: 10.0),
indicator: getIndicator(), indicator: getIndicator(),
isScrollable: true, isScrollable: true,
tabs: _groups.map<Tab>((group) { tabs: _groups.map<Tab>((group) {
return Tab( return Tab(
child: Text(group.name), child: Container(
height: 30.0,
padding: EdgeInsets.symmetric(
horizontal: 10.0),
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.all(
Radius.circular(15)),
),
child: Text(
group.name,
)),
); );
}).toList(), }).toList(),
), ),
), ),
), ),
Container( Expanded(
child: FlatButton( flex: 1,
child: IconButton(
onPressed: () => showDialog( onPressed: () => showDialog(
context: context, context: context,
builder: (BuildContext context) => builder: (BuildContext context) =>
AddGroup()), AddGroup()),
child: Icon(Icons.add)), icon: Icon(Icons.add)),
), ),
], ],
), ),
Expanded( Expanded(
child: Container( child: Container(
child: TabBarView( child: TabBarView(
children: _groups.map<Widget>((group) { children: _groups.map<Widget>((group) {
return Container( return Container(
key: ObjectKey(group), key: ObjectKey(group),
child: PodcastGroupList(group: group)); child: PodcastGroupList(group: group));
}).toList(),), }).toList(),
),
), ),
) )
], ],

View File

@ -49,129 +49,133 @@ class EpisodeGrid extends StatelessWidget {
? _c = Color.fromRGBO( ? _c = Color.fromRGBO(
(255 - color[0]), 255 - color[1], 255 - color[2], 1.0) (255 - color[0]), 255 - color[1], 255 - color[2], 1.0)
: _c = Color.fromRGBO(color[0], color[1], color[2], 1.0); : _c = Color.fromRGBO(color[0], color[1], color[2], 1.0);
return InkWell( return Material(
onTap: () { color: Colors.transparent,
Navigator.push( child: InkWell(
context, onTap: () {
ScaleRoute( Navigator.push(
page: EpisodeDetail( context,
episodeItem: podcast[index], ScaleRoute(
heroTag: heroTag, page: EpisodeDetail(
)), episodeItem: podcast[index],
); heroTag: heroTag,
}, )),
child: Container( );
decoration: BoxDecoration( },
borderRadius: BorderRadius.all(Radius.circular(5.0)), child: Container(
color: Theme.of(context).scaffoldBackgroundColor, decoration: BoxDecoration(
border: Border.all( borderRadius: BorderRadius.all(Radius.circular(5.0)),
color: Colors.grey[100], color: Theme.of(context).scaffoldBackgroundColor,
width: 3.0, border: Border.all(
),
boxShadow: [
BoxShadow(
color: Colors.grey[100], color: Colors.grey[100],
blurRadius: 1.0, width: 3.0,
spreadRadius: 0.5,
), ),
]), boxShadow: [
alignment: Alignment.center, BoxShadow(
padding: EdgeInsets.all(8.0), color: Colors.grey[100],
child: Column( blurRadius: 1.0,
mainAxisAlignment: MainAxisAlignment.center, spreadRadius: 0.5,
children: <Widget>[ ),
Expanded( ]),
flex: 2, alignment: Alignment.center,
child: Row( padding: EdgeInsets.all(8.0),
mainAxisAlignment: MainAxisAlignment.start, child: Column(
children: <Widget>[ mainAxisAlignment: MainAxisAlignment.center,
Hero( children: <Widget>[
tag: podcast[index].enclosureUrl + heroTag, Expanded(
child: Container( flex: 2,
child: ClipRRect( child: Row(
borderRadius: BorderRadius.all( mainAxisAlignment: MainAxisAlignment.start,
Radius.circular(_width / 32)), children: <Widget>[
child: Container( Hero(
height: _width / 16, tag: podcast[index].enclosureUrl + heroTag,
width: _width / 16, child: Container(
child: Image.file( child: ClipRRect(
File("${podcast[index].imagePath}")), borderRadius: BorderRadius.all(
Radius.circular(_width / 32)),
child: Container(
height: _width / 16,
width: _width / 16,
child: Image.file(File(
"${podcast[index].imagePath}")),
),
), ),
), ),
), ),
), Spacer(),
Spacer(), showNumber
showNumber ? Container(
? Container( alignment: Alignment.topRight,
alignment: Alignment.topRight, child: Text(
child: Text( (podcast.length - index).toString(),
(podcast.length - index).toString(), style: GoogleFonts.teko(
style: GoogleFonts.teko( textStyle: TextStyle(
textStyle: TextStyle( fontSize: _width / 24,
fontSize: _width / 24, color: _c,
color: _c, ),
), ),
), ),
), )
) : Center(),
: Center(), ],
],
),
),
Expanded(
flex: 5,
child: Container(
alignment: Alignment.topLeft,
padding: EdgeInsets.only(top: 2.0),
child: Text(
podcast[index].title,
style: TextStyle(
fontSize: _width / 32,
),
maxLines: 4,
overflow: TextOverflow.fade,
), ),
), ),
), Expanded(
Expanded( flex: 5,
flex: 1, child: Container(
child: Row( alignment: Alignment.topLeft,
children: <Widget>[ padding: EdgeInsets.only(top: 2.0),
Align( child: Text(
alignment: Alignment.bottomLeft, podcast[index].title,
child: Text( style: TextStyle(
podcast[index].pubDate.substring(4, 16), fontSize: _width / 32,
style: TextStyle(
fontSize: _width / 35,
color: _c,
fontStyle: FontStyle.italic),
), ),
maxLines: 4,
overflow: TextOverflow.fade,
), ),
Spacer(), ),
showDownload
? DownloadIcon(episodeBrief: podcast[index])
: Center(),
Padding(
padding: EdgeInsets.all(1),
),
showFavorite
? Container(
alignment: Alignment.bottomRight,
child: (podcast[index].liked == 0)
? Center()
: IconTheme(
data: IconThemeData(size: 15),
child: Icon(
Icons.favorite,
color: Colors.red,
),
),
)
: Center(),
],
), ),
), Expanded(
], flex: 1,
child: Row(
children: <Widget>[
Align(
alignment: Alignment.bottomLeft,
child: Text(
podcast[index].dateToString(),
//podcast[index].pubDate.substring(4, 16),
style: TextStyle(
fontSize: _width / 35,
color: _c,
fontStyle: FontStyle.italic),
),
),
Spacer(),
showDownload
? DownloadIcon(episodeBrief: podcast[index])
: Center(),
Padding(
padding: EdgeInsets.all(1),
),
showFavorite
? Container(
alignment: Alignment.bottomRight,
child: (podcast[index].liked == 0)
? Center()
: IconTheme(
data: IconThemeData(size: 15),
child: Icon(
Icons.favorite,
color: Colors.red,
),
),
)
: Center(),
],
),
),
],
),
), ),
), ),
); );

View File

@ -45,11 +45,11 @@ class RssItunes {
} }
return RssItunes( return RssItunes(
author: findElementOrNull(element, "itunes:author")?.text?.trim(), author: findElementOrNull(element, "itunes:author")?.text?.trim(),
summary: findElementOrNull(element, "itunes:summary")?.text?.trim() ?? '', summary: findElementOrNull(element, "itunes:summary")?.text?.trim(),
explicit: parseBoolLiteral(element, "itunes:explicit"), explicit: parseBoolLiteral(element, "itunes:explicit"),
title: findElementOrNull(element, "itunes:title")?.text?.trim(), title: findElementOrNull(element, "itunes:title")?.text?.trim(),
// subtitle: findElementOrNull(element, "itunes:subtitle")?.text?.trim(), // subtitle: findElementOrNull(element, "itunes:subtitle")?.text?.trim(),
//owner: RssItunesOwner.parse(findElementOrNull(element, "itunes:owner")), //owner: RssItunesOwner.parse(findElementOrNull(element, "itunes:owner")),
// keywords: findElementOrNull(element, "itunes:keywords") // keywords: findElementOrNull(element, "itunes:keywords")
// ?.text // ?.text
// ?.split(",") // ?.split(",")

View File

@ -118,13 +118,6 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "0.11.1" version: "0.11.1"
flutter_statusbarcolor:
dependency: "direct dev"
description:
name: flutter_statusbarcolor
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.3"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter

View File

@ -27,7 +27,6 @@ dependencies:
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
flutter_statusbarcolor: ^0.2.3
json_annotation: any json_annotation: any
sqflite: any sqflite: any
flutter_html: ^0.11.1 flutter_html: ^0.11.1