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:
build:
docker:
- image: cirrusci/flutter:v1.13.6
- image: cirrusci/flutter:v1.14.6
branches:
only: master

View File

@ -1,7 +1,9 @@
import 'package:intl/intl.dart';
class EpisodeBrief {
final String title;
String description;
final String pubDate;
final int pubDate;
final int enclosureLength;
final String enclosureUrl;
final String feedTitle;
@ -24,5 +26,16 @@ class EpisodeBrief {
this.explicit,
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) {
List<String> list = List.from(json['podcastList']);
print(json['[podcastList']);
return
GroupEntity(
json['name'] as String,
json['id'] as String,
json['color'] as String,
list);
return GroupEntity(json['name'] as String, json['id'] as String,
json['color'] as String, list);
}
}
@ -91,11 +87,11 @@ class GroupList extends ChangeNotifier {
notifyListeners();
storage.getGroups().then((loadgroups) async {
_groups.addAll(loadgroups.map((e) => PodcastGroup.fromEntity(e)));
await Future.forEach(_groups, (group) async {
await Future.forEach(_groups, (group) async {
await group.getPodcasts();
});
_isLoading = false;
notifyListeners();
_isLoading = false;
notifyListeners();
});
}
@ -124,7 +120,7 @@ class GroupList extends ChangeNotifier {
}
Future subscribe(PodcastLocal podcastLocal) async {
_groups[0].podcastList.add(podcastLocal.id);
_groups[0].podcastList.insert(0, podcastLocal.id);
_saveGroup();
await dbHelper.savePodcastLocal(podcastLocal);
await _groups[0].getPodcasts();
@ -132,7 +128,7 @@ class GroupList extends ChangeNotifier {
}
List<PodcastGroup> getPodcastGroup(String id) {
List<PodcastGroup> result =[];
List<PodcastGroup> result = [];
_groups.forEach((group) {
if (group.podcastList.contains(id)) {
result.add(group);
@ -141,38 +137,42 @@ class GroupList extends ChangeNotifier {
return result;
}
changeGroup(String id, List<String> list) async{
_groups.forEach((group) {
if (group.podcastList.contains(id)) {
group.podcastList.remove(id);
}
changeGroup(String id, List<PodcastGroup> list) async {
_isLoading = true;
notifyListeners();
getPodcastGroup(id).forEach((group) {
group.podcastList.remove(id);
});
await Future.forEach(list, (s) {
_groups.forEach((group) async{
if (group.name == s) group.podcastList.add(id);
await group.getPodcasts();
});
list.forEach((s) {
s.podcastList.insert(0, id);
});
_saveGroup();
await Future.forEach(_groups, (group) async {
await group.getPodcasts();
});
_isLoading = false;
notifyListeners();
}
removePodcast(String id) async{
await Future.forEach(_groups, (group) async{
if (group.podcastList.contains(id)) {
removePodcast(String id) async {
_isLoading = true;
notifyListeners();
_groups.forEach((group) async {
group.podcastList.remove(id);
await group.getPodcasts();
}
});
_saveGroup();
await dbHelper.delPodcastLocal(id);
notifyListeners();
await Future.forEach(_groups, (group) async {
await group.getPodcasts();
});
_isLoading = false;
notifyListeners();
}
saveOrder(PodcastGroup group, List<PodcastLocal> podcasts) async{
group.podcastList = podcasts.map((e) => e.id).toList();
_saveGroup();
await group.getPodcasts();
notifyListeners();
saveOrder(PodcastGroup group, List<PodcastLocal> podcasts) async {
group.podcastList = podcasts.map((e) => e.id).toList();
_saveGroup();
await group.getPodcasts();
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:url_launcher/url_launcher.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:intl/intl.dart';
import 'package:tsacdop/class/audiostate.dart';
import 'package:tsacdop/class/episodebrief.dart';
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
@ -86,7 +87,7 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
height: 30.0,
child: Text(
'Published ' +
widget.episodeItem.pubDate.substring(0, 16),
DateFormat.yMMMd().format( DateTime.fromMillisecondsSinceEpoch(widget.episodeItem.pubDate)),
style: TextStyle(color: Colors.blue[500])),
),
Container(

View File

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

View File

@ -5,6 +5,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:color_thief_flutter/color_thief_flutter.dart';
import 'package:dio/dio.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:path_provider/path_provider.dart';
import 'package:image/image.dart' as img;
@ -31,12 +32,18 @@ class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
systemNavigationBarColor: Colors.grey[100],
statusBarColor: Colors.green,
),
child: Scaffold(
key: _scaffoldKey,
appBar: AppBar(
elevation: 0,
centerTitle: true,
backgroundColor: Colors.grey[100],
brightness: Brightness.light,
leading: IconButton(
tooltip: 'Add',
icon: const Icon(Icons.add_circle_outline),
@ -56,6 +63,7 @@ class _MyHomePageState extends State<MyHomePage> {
],
),
body: Home(),
),
);
}
}
@ -217,50 +225,66 @@ class _SearchResultState extends State<SearchResult> {
var importOmpl = Provider.of<ImportOmpl>(context, listen: false);
var groupList = Provider.of<GroupList>(context, listen: false);
savePodcast(String rss) async {
print(rss);
if (mounted) setState(() => _adding = true);
importOmpl.importState = ImportState.import;
try {
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;
Response response = await Dio().get(rss);
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) {
importOmpl.importState = ImportState.error;
Fluttertoast.showToast(
msg: 'Network error, Subscribe failed',
gravity: ToastGravity.BOTTOM,
gravity: ToastGravity.TOP,
);
await Future.delayed(Duration(seconds: 10));
importOmpl.importState = ImportState.stop;
@ -271,12 +295,14 @@ class _SearchResultState extends State<SearchResult> {
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
children: <Widget>[
ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 30.0),
onTap: (){setState(() {
_showDes = !_showDes;
});},
contentPadding: EdgeInsets.symmetric(horizontal: 20.0),
onTap: () {
setState(() {
_showDes = !_showDes;
});
},
leading: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(20.0)),
child: Image.network(
@ -289,46 +315,57 @@ class _SearchResultState extends State<SearchResult> {
),
title: Text(widget.onlinePodcast.title),
subtitle: Text(widget.onlinePodcast.publisher),
trailing:
Row(
trailing: Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(_showDes ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down),
Padding(padding: EdgeInsets.only(right: 10.0)),
!_issubscribe
? !_adding
? OutlineButton(
child:
Text('Subscribe', style: TextStyle(color: Colors.blue)),
onPressed: () {
importOmpl.rssTitle = widget.onlinePodcast.title;
savePodcast(widget.onlinePodcast.rss);
})
: OutlineButton(
child: SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation(Colors.blue),
)),
onPressed: () {},
)
: OutlineButton(child: Text('Subscribe'), onPressed: (){}),
Icon(_showDes
? Icons.keyboard_arrow_up
: Icons.keyboard_arrow_down),
Padding(padding: EdgeInsets.only(right: 10.0)),
!_issubscribe
? !_adding
? OutlineButton(
child: Text('Subscribe',
style: TextStyle(color: Colors.blue)),
onPressed: () {
importOmpl.rssTitle = widget.onlinePodcast.title;
savePodcast(widget.onlinePodcast.rss);
})
: OutlineButton(
child: SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor:
AlwaysStoppedAnimation(Colors.blue),
)),
onPressed: () {},
)
: OutlineButton(child: Text('Subscribe'), onPressed: () {}),
],
),
),
_showDes ? Container(
alignment: Alignment.centerLeft,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.all(Radius.circular(10.0))
),
margin: EdgeInsets.only(left: 70, right: 50),
padding: EdgeInsets.all(10.0),
child: Text(widget.onlinePodcast.description, maxLines: 3, overflow: TextOverflow.ellipsis,),
) : Center(),
_showDes
? Container(
alignment: Alignment.centerLeft,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.only(
topRight: Radius.circular(15.0),
bottomLeft: Radius.circular(15.0),
bottomRight: Radius.circular(15.0),
)),
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) {
ImportOmpl importOmpl = Provider.of<ImportOmpl>(context, listen: false);
GroupList groupList = Provider.of<GroupList>(context, listen: false);
_refreshAll() async{
_refreshAll() async {
var dbHelper = DBHelper();
List<PodcastLocal> podcastList =
await dbHelper.getPodcastLocalAll();
List<PodcastLocal> podcastList = await dbHelper.getPodcastLocalAll();
await Future.forEach(podcastList, (podcastLocal) async {
importOmpl.rssTitle = podcastLocal.title;
importOmpl.importState = ImportState.parse;
@ -62,10 +61,10 @@ class PopupMenu extends StatelessWidget {
saveOmpl(String rss) async {
var dbHelper = DBHelper();
try {
importOmpl.importState = ImportState.import;
Response response = await Dio().get(rss);
importOmpl.importState = ImportState.import;
Response response = await Dio().get(rss);
if (response.statusCode == 200) {
var _p = RssFeed.parse(response.data);
var dir = await getApplicationDocumentsDirectory();
@ -75,55 +74,84 @@ class PopupMenu extends StatelessWidget {
img.Image image = img.decodeImage(imageResponse.data);
img.Image thumbnail = img.copyResize(image, width: 300);
String _uuid = Uuid().v4();
String _realUrl = response.realUri.toString();
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 _realUrl =
response.isRedirect ? response.realUri.toString() : rss;
print(_realUrl);
PodcastLocal podcastLocal = PodcastLocal(
_p.title, _p.itunes.image.href, _realUrl, _primaryColor, _p.author, _uuid, _imagePath);
bool _checkUrl = await dbHelper.checkPodcast(_realUrl);
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;
groupList.subscribe(podcastLocal);
PodcastLocal podcastLocal = PodcastLocal(
_p.title,
_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;
} catch (e) {
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: 5));
importOmpl.importState = ImportState.stop;
}
}
else {
importOmpl.importState = ImportState.error;
Fluttertoast.showToast(
msg: 'Network error, Subscribe failed',
gravity: ToastGravity.BOTTOM,
);
await Future.delayed(Duration(seconds: 10));
importOmpl.importState = ImportState.stop;
Fluttertoast.showToast(
msg: 'Network error, Subscribe failed',
gravity: ToastGravity.TOP,
);
await Future.delayed(Duration(seconds: 5));
importOmpl.importState = ImportState.stop;
}
}
void _saveOmpl(String path) async {
File file = File(path);
String opml = file.readAsStringSync();
try {
var content = xml.parse(opml);
var total = content
.findAllElements('outline')
.map((ele) => OmplOutline.parse(ele))
.toList();
var content = xml.parse(opml);
var total = content
.findAllElements('outline')
.map((ele) => OmplOutline.parse(ele))
.toList();
if (total.length == 0) {
Fluttertoast.showToast(
msg: 'File Not Valid',
gravity: ToastGravity.BOTTOM,
);
} else {
for (int i = 0; i < total.length; i++) {
if (total[i].xmlUrl != null) {
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('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/material.dart';
import 'package:provider/provider.dart';
import 'package:path_provider/path_provider.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:tsacdop/class/episodebrief.dart';
import 'package:tsacdop/class/podcast_group.dart';
import 'package:tsacdop/class/podcastlocal.dart';
import 'package:tsacdop/class/importompl.dart';
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
import 'package:tsacdop/episodes/episodedetail.dart';
import 'package:tsacdop/podcasts/podcastdetail.dart';
import 'package:tsacdop/podcasts/podcastmanage.dart';
import 'package:tsacdop/util/pageroute.dart';
import 'package:tsacdop/class/settingstate.dart';
class ScrollPodcasts extends StatefulWidget {
@override
@ -26,35 +23,11 @@ class ScrollPodcasts extends StatefulWidget {
}
class _ScrollPodcastsState extends State<ScrollPodcasts> {
var dir;
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
void initState() {
super.initState();
_loaded = false;
_groupIndex = 0;
getApplicationDocumentsDirectory().then((value) {
dir = value.path;
setState(() => _loaded = true);
});
}
@override
@ -66,10 +39,6 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
return isLoading
? Container(
height: (_width - 20) / 3 + 110,
child: SizedBox(
height: 20.0,
width: 20.0,
child: CircularProgressIndicator()),
)
: DefaultTabController(
length: groups[_groupIndex].podcastList.length,
@ -159,7 +128,8 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
indicator: CircleTabIndicator(
color: Colors.blue, radius: 3),
isScrollable: true,
tabs: groups[_groupIndex].podcasts
tabs: groups[_groupIndex]
.podcasts
.map<Tab>((PodcastLocal podcastLocal) {
return Tab(
child: ClipRRect(
@ -168,10 +138,8 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
child: LimitedBox(
maxHeight: 50,
maxWidth: 50,
child: !_loaded
? CircularProgressIndicator()
: Image.file(File(
"${podcastLocal.imagePath}")),
child: Image.file(
File("${podcastLocal.imagePath}")),
),
),
);
@ -188,7 +156,8 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
color: Colors.white,
),
child: TabBarView(
children: groups[_groupIndex].podcasts
children: groups[_groupIndex]
.podcasts
.map<Widget>((PodcastLocal podcastLocal) {
return Container(
decoration: BoxDecoration(color: Colors.grey[100]),
@ -216,7 +185,6 @@ class PodcastPreview extends StatefulWidget {
}
class _PodcastPreviewState extends State<PodcastPreview> {
Future<List<EpisodeBrief>> _getRssItemTop(PodcastLocal podcastLocal) async {
var dbHelper = DBHelper();
Future<List<EpisodeBrief>> episodes =
@ -265,24 +233,33 @@ class _PodcastPreviewState extends State<PodcastPreview> {
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Text(widget.podcastLocal.title,
style: TextStyle(fontWeight: FontWeight.bold, color: _c)),
Spacer(),
Material(
color: Colors.transparent,
child: IconButton(
icon: Icon(Icons.arrow_forward),
splashColor: Colors.transparent,
tooltip: 'See All',
onPressed: () {
Navigator.push(
context,
SlideLeftRoute(
page: PodcastDetail(
podcastLocal: widget.podcastLocal,
)),
);
},
Expanded(
flex: 4,
child: Text(widget.podcastLocal.title,
maxLines: 1,
overflow: TextOverflow.visible,
style: TextStyle(fontWeight: FontWeight.bold, color: _c)),
),
Expanded(
flex: 1,
child: Container(
alignment: Alignment.centerRight,
child: Material(
color: Colors.transparent,
child: IconButton(
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 {
final List<EpisodeBrief> podcast;
final PodcastLocal podcastLocal;
ShowEpisode({Key key, this.podcast, this.podcastLocal})
: super(key: key);
ShowEpisode({Key key, this.podcast, this.podcastLocal}) : super(key: key);
@override
Widget build(BuildContext context) {
double _width = MediaQuery.of(context).size.width;
@ -368,8 +344,8 @@ class ShowEpisode extends StatelessWidget {
child: Container(
height: _width / 18,
width: _width / 18,
child: Image.file(File(
"${podcastLocal.imagePath}")),
child: Image.file(
File("${podcastLocal.imagePath}")),
),
),
),
@ -398,7 +374,8 @@ class ShowEpisode extends StatelessWidget {
child: Container(
alignment: Alignment.bottomLeft,
child: Text(
podcast[index].pubDate.substring(4, 16),
podcast[index].dateToString(),
//podcast[index].pubDate.substring(4, 16),
style: TextStyle(
fontSize: _width / 35,
color: _c,

View File

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

View File

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

View File

@ -24,16 +24,17 @@ class _PodcastDetailState extends State<PodcastDetail> {
result == 0 ?
Fluttertoast.showToast(
msg: 'No Update',
gravity: ToastGravity.BOTTOM,
gravity: ToastGravity.TOP,
)
: Fluttertoast.showToast(
msg: 'Updated $result Episodes',
gravity: ToastGravity.BOTTOM,
gravity: ToastGravity.TOP,
);
if(mounted) setState(() {});
}
Future<List<EpisodeBrief>> _getRssItem(PodcastLocal podcastLocal) async {
print(podcastLocal.id);
var dbHelper = DBHelper();
List<EpisodeBrief> episodes = await
dbHelper.getRssItem(podcastLocal.id);
@ -43,25 +44,25 @@ class _PodcastDetailState extends State<PodcastDetail> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.podcastLocal.title,),
elevation: 0.0,
backgroundColor: Colors.grey[100],
centerTitle: true,
),
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());
},
)),
appBar: AppBar(
title: Text(widget.podcastLocal.title,),
elevation: 0.0,
backgroundColor: Colors.grey[100],
centerTitle: true,
),
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());
},
)),
);
}
}

View File

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

View File

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

View File

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

View File

@ -49,129 +49,133 @@ class EpisodeGrid extends StatelessWidget {
? _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 InkWell(
onTap: () {
Navigator.push(
context,
ScaleRoute(
page: EpisodeDetail(
episodeItem: podcast[index],
heroTag: heroTag,
)),
);
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(5.0)),
color: Theme.of(context).scaffoldBackgroundColor,
border: Border.all(
color: Colors.grey[100],
width: 3.0,
),
boxShadow: [
BoxShadow(
return Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
Navigator.push(
context,
ScaleRoute(
page: EpisodeDetail(
episodeItem: podcast[index],
heroTag: heroTag,
)),
);
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(5.0)),
color: Theme.of(context).scaffoldBackgroundColor,
border: Border.all(
color: Colors.grey[100],
blurRadius: 1.0,
spreadRadius: 0.5,
width: 3.0,
),
]),
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: podcast[index].enclosureUrl + heroTag,
child: Container(
child: ClipRRect(
borderRadius: BorderRadius.all(
Radius.circular(_width / 32)),
child: Container(
height: _width / 16,
width: _width / 16,
child: Image.file(
File("${podcast[index].imagePath}")),
boxShadow: [
BoxShadow(
color: Colors.grey[100],
blurRadius: 1.0,
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: podcast[index].enclosureUrl + heroTag,
child: Container(
child: ClipRRect(
borderRadius: BorderRadius.all(
Radius.circular(_width / 32)),
child: Container(
height: _width / 16,
width: _width / 16,
child: Image.file(File(
"${podcast[index].imagePath}")),
),
),
),
),
),
Spacer(),
showNumber
? Container(
alignment: Alignment.topRight,
child: Text(
(podcast.length - index).toString(),
style: GoogleFonts.teko(
textStyle: TextStyle(
fontSize: _width / 24,
color: _c,
Spacer(),
showNumber
? Container(
alignment: Alignment.topRight,
child: Text(
(podcast.length - index).toString(),
style: GoogleFonts.teko(
textStyle: TextStyle(
fontSize: _width / 24,
color: _c,
),
),
),
),
)
: 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,
)
: Center(),
],
),
),
),
Expanded(
flex: 1,
child: Row(
children: <Widget>[
Align(
alignment: Alignment.bottomLeft,
child: Text(
podcast[index].pubDate.substring(4, 16),
style: TextStyle(
fontSize: _width / 35,
color: _c,
fontStyle: FontStyle.italic),
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,
),
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(
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"),
title: findElementOrNull(element, "itunes:title")?.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")
// ?.text
// ?.split(",")

View File

@ -118,13 +118,6 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
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:
dependency: "direct dev"
description: flutter

View File

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