improve subscribe experience

This commit is contained in:
stonegate 2020-02-11 21:01:57 +08:00
parent 56f258d44a
commit 4e01b3979f
20 changed files with 425 additions and 304 deletions

View File

@ -2,11 +2,11 @@
Enjoy podcasts with tsacdop!
Tsacdop is a podcasts player developed with flutter.
The development is still on early stage.
Thanks for flutter team and all plugin developers, especially [webfeed](https://github.com/witochandra/webfeed) and [audiofileplayer](https://github.com/google/flutter.plugins/tree/master/packages/audiofileplayer/).
The podcasts engine is powered by [ListenNote](https://listennote.com).
The podcasts search engine is powered by [ListenNotes](https://listennotes.com).
## Getting Started

View File

@ -1,8 +1,12 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:color_thief_flutter/color_thief_flutter.dart';
import 'class/importompl.dart';
import 'package:dio/dio.dart';
import 'package:provider/provider.dart';
import 'package:path_provider/path_provider.dart';
import 'package:image/image.dart' as img;
import 'dart:convert';
import 'dart:async';
import 'class/searchpodcast.dart';
@ -10,6 +14,7 @@ import 'class/podcastlocal.dart';
import 'class/sqflite_localpodcast.dart';
import 'home.dart';
import 'popupmenu.dart';
import 'webfeed/webfeed.dart';
class MyHomePage extends StatefulWidget {
@override
@ -22,31 +27,28 @@ class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => ImportOmpl(),
child: Scaffold(
key: _scaffoldKey,
appBar: AppBar(
elevation: 0,
centerTitle: true,
backgroundColor: Colors.grey[100],
leading: IconButton(
tooltip: 'Add',
icon: const Icon(Icons.add_circle_outline),
onPressed: () async {
await showSearch<int>(
context: context,
delegate: _delegate,
);
},
),
title: Text('🎙TsacDop', style: TextStyle(color: Colors.blue[600])),
actions: <Widget>[
PopupMenu(),
],
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
elevation: 0,
centerTitle: true,
backgroundColor: Colors.grey[100],
leading: IconButton(
tooltip: 'Add',
icon: const Icon(Icons.add_circle_outline),
onPressed: () async {
await showSearch<int>(
context: context,
delegate: _delegate,
);
},
),
body: Home(),
title: Text('🎙TsacDop', style: TextStyle(color: Colors.blue[600])),
actions: <Widget>[
PopupMenu(),
],
),
body: Home(),
);
}
}
@ -119,13 +121,7 @@ class _MyHomePageDelegate extends SearchDelegate<int> {
List<Widget> buildActions(BuildContext context) {
return <Widget>[
if (query.isEmpty)
IconButton(
tooltip: 'Voice Search',
icon: const Icon(Icons.mic),
onPressed: () {
query = 'TODO: implement voice input';
},
)
Center()
else
IconButton(
tooltip: 'Clear',
@ -144,9 +140,12 @@ class _MyHomePageDelegate extends SearchDelegate<int> {
height: 10,
width: 10,
margin: EdgeInsets.only(top: 400),
child: Image.asset(
'assets/listennote.png',
fit: BoxFit.fill,
child: SizedBox(
height: 10,
child: Image.asset(
'assets/listennote.png',
fit: BoxFit.fill,
),
),
);
return FutureBuilder(
@ -183,31 +182,6 @@ class SearchResult extends StatefulWidget {
class _SearchResultState extends State<SearchResult> {
bool _issubscribe;
bool _adding;
Future _subscribe(OnlinePodcast t) async {
if (mounted)
setState(() {
_adding = true;
});
String _primaryColor;
await getColorFromUrl(t.image).then((color) {
print(color.toString());
_primaryColor = color.toString();
});
var dbHelper = DBHelper();
final PodcastLocal _pdt =
PodcastLocal(t.title, t.image, t.rss, _primaryColor, t.publisher);
_pdt.description = t.description;
print(t.title + t.rss);
await dbHelper.savePodcastLocal(_pdt);
final response = await Dio().get(t.rss);
int result = await dbHelper.savePodcastRss(response.data);
if (result == 0 && mounted) setState(() => _issubscribe = true);
}
bool isXimalaya(String input) {
RegExp ximalaya = RegExp(r"ximalaya");
return ximalaya.hasMatch(input);
}
@override
void initState() {
@ -221,8 +195,60 @@ class _SearchResultState extends State<SearchResult> {
super.dispose();
}
Future<String> getColor(File file) async {
final imageProvider = FileImage(file);
var colorImage = await getImageFromProvider(imageProvider);
var color = await getColorFromImage(colorImage);
String primaryColor = color.toString();
return primaryColor;
}
@override
Widget build(BuildContext context) {
final importOmpl = Provider.of<ImportOmpl>(context);
savePodcast(String rss) async {
print(rss);
if (mounted) setState(() => _adding = true);
importOmpl.importState =
ImportState.import;
Response response = await Dio().get(rss);
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);
File("${dir.path}/${_p.title}.png")
..writeAsBytesSync(img.encodePng(thumbnail));
String _primaryColor = await getColor(File("${dir.path}/${_p.title}.png"));
PodcastLocal podcastLocal = PodcastLocal(
_p.title, _p.itunes.image.href, rss, _primaryColor, _p.author);
podcastLocal.description = _p.description;
var dbHelper = DBHelper();
await dbHelper.savePodcastLocal(podcastLocal);
importOmpl.importState =
ImportState.parse;
await dbHelper.savePodcastRss(response.data);
importOmpl.importState =
ImportState.complete;
importOmpl.importState =
ImportState.stop;
print('fatch data');
}
return Container(
padding: EdgeInsets.symmetric(horizontal: 12.0),
child: ListTile(
@ -238,27 +264,27 @@ class _SearchResultState extends State<SearchResult> {
),
title: Text(widget.onlinePodcast.title),
subtitle: Text(widget.onlinePodcast.publisher),
trailing: isXimalaya(widget.onlinePodcast.rss)
? OutlineButton(child: Text('Not Support'), onPressed: null)
: !_issubscribe
? !_adding
? OutlineButton(
child: Text('Subscribe',
style: TextStyle(color: Colors.blue)),
onPressed: () {
_subscribe(widget.onlinePodcast);
})
: OutlineButton(
child: SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation(Colors.blue),
)),
onPressed: () {},
)
: OutlineButton(child: Text('Subscribe'), onPressed: null),
trailing: !_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: null),
),
);
}

View File

@ -1,16 +1,19 @@
import 'package:flutter/foundation.dart';
enum ImportState{start, import, complete, stop, error}
enum ImportState{start, import, parse, complete, stop, error}
class ImportOmpl extends ChangeNotifier{
ImportState _importState = ImportState.stop;
String _rssTitle;
String get rsstitle => _rssTitle;
set rssTitle(String title){
_rssTitle = title;
notifyListeners();
}
ImportState get importState => _importState;
set importState(ImportState state){
_importState = state;
notifyListeners();

View File

@ -82,10 +82,11 @@ class DBHelper {
List<Map> list = await dbClient.rawQuery(
"""SELECT downloaded FROM Episodes WHERE downloaded != 'ND' AND feed_title = ?""",
[title]);
for(int i=0; i < list.length; i++){
if(list[i] != null)
FlutterDownloader.remove(taskId: list[i]['downloaded'], shouldDeleteContent: true);
print('Removed all download task');
for (int i = 0; i < list.length; i++) {
if (list[i] != null)
FlutterDownloader.remove(
taskId: list[i]['downloaded'], shouldDeleteContent: true);
print('Removed all download tasks');
}
await dbClient
.rawDelete('DELETE FROM Episodes WHERE feed_title=?', [title]);
@ -99,70 +100,21 @@ class DBHelper {
return url;
}
int stringToDate(String s) {
var months = {
'Jan': 1,
'Feb': 2,
'Mar': 3,
'Apr': 4,
'May': 5,
'Jun': 6,
'Jul': 7,
'Aug': 8,
'Sep': 9,
'Oct': 10,
'Nov': 11,
'Dec': 12
};
int y;
int m;
int d;
int h;
int min;
int sec;
int result;
try {
y = int.parse(s.substring(12, 16));
} catch (e) {
y = 0;
}
try {
m = months[s.substring(8, 11)];
} catch (e) {
m = 0;
}
try {
d = int.parse(s.substring(5, 7));
} catch (e) {
d = 0;
}
try {
h = int.parse(s.substring(17, 19));
} catch (e) {
h = 0;
}
try {
min = int.parse(s.substring(20, 22));
} catch (e) {
min = 0;
}
try {
sec = int.parse(s.substring(23, 25));
} catch (e) {
sec = 0;
}
try {
result = DateTime(y, m, d, h, min, sec).millisecondsSinceEpoch;
} catch (e) {
result = 0;
}
return result;
}
static _parsePubDate(String pubDate) {
DateTime _parsePubDate(String pubDate) {
if (pubDate == null) return null;
return DateFormat('EEE, dd MMM yyyy HH:mm:ss Z', 'en_US').parse(pubDate);
DateTime date;
try {
date = DateFormat('EEE, dd MMM yyyy HH:mm:ss Z', 'en_US').parse(pubDate);
} catch (e) {
try{
print('e');
date = DateFormat('dd MMM yyyy HH:mm:ss Z', 'en_US').parse(pubDate);}
catch(e) {
print('e');
date = DateTime(0);
}
}
return date;
}
int getExplicit(bool b) {
@ -185,6 +137,7 @@ class DBHelper {
String _title;
String _url;
String _description;
int _duration;
var _p = RssFeed.parse(rss);
int _result = _p.items.length;
var dbClient = await database;
@ -208,9 +161,11 @@ class DBHelper {
: _url = _p.items[i].enclosure.url;
final _length = _p.items[i].enclosure.length;
final _pubDate = _p.items[i].pubDate;
final DateTime _date = _parsePubDate(_pubDate);
final _date = _parsePubDate(_pubDate);
final _milliseconds = _date.millisecondsSinceEpoch;
final _duration = _p.items[i].itunes.duration.inMinutes;
(_p.items[i].itunes.duration != null )
? _duration = _p.items[i].itunes.duration.inMinutes
: _duration = 0;
final _explicit = getExplicit(_p.items[i].itunes.explicit);
if (_p.items[i].enclosure.url != null) {
await dbClient.transaction((txn) {

View File

@ -6,6 +6,7 @@ import 'package:provider/provider.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:url_launcher/url_launcher.dart';
import 'class/audiostate.dart';
import 'class/episodebrief.dart';
import 'class/sqflite_localpodcast.dart';
@ -25,7 +26,6 @@ class EpisodeDetail extends StatefulWidget {
class _EpisodeDetailState extends State<EpisodeDetail> {
final textstyle = TextStyle(fontSize: 15.0, color: Colors.black);
double downloadProgress;
Color _c;
bool _loaddes;
Future getSDescription(String title) async {
@ -36,14 +36,15 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
_loaddes = true;
});
}
_launchUrl(String url) async {
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
}
@override
void initState() {
super.initState();
@ -53,11 +54,6 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
@override
Widget build(BuildContext context) {
var color = json.decode(widget.episodeItem.primaryColor);
(color[0] > 200 && color[1] > 200 && color[2] > 200)
? _c = Color.fromRGBO(
(255 - color[0]), 255 - color[1], 255 - color[2], 1.0)
: _c = Color.fromRGBO(color[0], color[1], color[2], 0.8);
return Scaffold(
backgroundColor: Colors.grey[100],
@ -81,29 +77,28 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
children: <Widget>[
Container(
padding: EdgeInsets.symmetric(horizontal: 12.0),
margin: EdgeInsets.only(bottom: 10.0),
alignment: Alignment.topLeft,
child: Text(
widget.episodeItem.title,
style: Theme.of(context).textTheme.title,
),
),
Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.symmetric(horizontal: 12.0),
height: 30.0,
child: Text(
'Published ' +
widget.episodeItem.pubDate.substring(0, 16),
style: TextStyle(color: Colors.blue[500])),
),
Container(
padding: EdgeInsets.all(12.0),
height: 50,
height: 50.0,
child: Row(
children: <Widget>[
(widget.episodeItem.explicit == 1)
? Container(
decoration: BoxDecoration(
color: Colors.red[800],
shape: BoxShape.circle),
height: 25.0,
margin: EdgeInsets.only(right: 10.0),
padding: EdgeInsets.symmetric(horizontal: 10.0),
alignment: Alignment.center,
child: Text('E',
style: TextStyle(color: Colors.white)))
? ExplicitScale()
: Center(),
Container(
decoration: BoxDecoration(
@ -133,19 +128,6 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
'MB',
style: textstyle),
),
Container(
decoration: BoxDecoration(
color: Colors.lightGreen[300],
borderRadius:
BorderRadius.all(Radius.circular(15.0))),
height: 30.0,
alignment: Alignment.center,
margin: EdgeInsets.only(right: 10.0),
padding: EdgeInsets.symmetric(horizontal: 10.0),
child: Text(
widget.episodeItem.pubDate.substring(0, 16),
style: textstyle),
),
],
),
),
@ -157,12 +139,13 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
padding: EdgeInsets.only(left: 12.0, right: 12.0, top: 5.0),
child: SingleChildScrollView(
child: (widget.episodeItem.description != null && _loaddes)
? Html(data: widget.episodeItem.description,
onLinkTap: (url){
_launchUrl(url);
},
useRichText: true,
)
? Html(
data: widget.episodeItem.description,
onLinkTap: (url) {
_launchUrl(url);
},
useRichText: true,
)
: Center(),
),
),
@ -531,3 +514,53 @@ class _ImageRotateState extends State<ImageRotate>
);
}
}
class ExplicitScale extends StatefulWidget {
@override
_ExplicitScaleState createState() => _ExplicitScaleState();
}
class _ExplicitScaleState extends State<ExplicitScale>
with SingleTickerProviderStateMixin {
Animation _animation;
AnimationController _controller;
double _value;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 500),
);
_animation = Tween(begin: 0.0, end: 1.0).animate(_controller)
..addListener(() {
if (mounted)
setState(() {
_value = _animation.value;
});
});
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Transform.scale(
scale: _value,
child: Container(
decoration:
BoxDecoration(color: Colors.red[800], shape: BoxShape.circle),
height: 25.0,
width: 25.0,
margin: EdgeInsets.only(right: 10.0),
padding: EdgeInsets.symmetric(horizontal: 10.0),
alignment: Alignment.center,
child: Text('E', style: TextStyle(color: Colors.white))));
}
}

View File

@ -120,6 +120,7 @@ class EpisodeGrid extends StatelessWidget {
Expanded(
flex: 5,
child: Container(
alignment: Alignment.topLeft,
padding: EdgeInsets.only(top: 2.0),
child: Text(
podcast[index].title,

View File

@ -13,7 +13,6 @@ class Home extends StatefulWidget {
}
class _HomeState extends State<Home> {
@override
Widget build(BuildContext context) {
return Column(
@ -25,21 +24,25 @@ class _HomeState extends State<Home> {
height: 30,
padding: EdgeInsets.symmetric(horizontal: 15),
alignment: Alignment.bottomRight,
child: GestureDetector(
onTap: () {
Navigator.push(
context,
SlideLeftRoute(page: Podcast()),
);
},
child: Text('See All',
style: TextStyle(
color: Colors.red[300], fontWeight: FontWeight.bold, )),
)),
Container(
child: ScrollPodcasts()),
child: GestureDetector(
onTap: () {
Navigator.push(
context,
SlideLeftRoute(page: Podcast()),
);
},
child: Container(
height: 30,
padding: EdgeInsets.all(5.0),
child: Text('See All',
style: TextStyle(
color: Colors.red[300],
fontWeight: FontWeight.bold,
)),
),
),
),
Container(child: ScrollPodcasts()),
Expanded(
child: MainTab(),
),

View File

@ -1,11 +1,18 @@
import 'dart:convert';
import 'dart:io';
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:provider/provider.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:path_provider/path_provider.dart';
import 'class/episodebrief.dart';
import 'class/podcastlocal.dart';
import 'class/importompl.dart';
import 'class/sqflite_localpodcast.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'episodedetail.dart';
import 'podcastdetail.dart';
import 'pageroute.dart';
@ -16,12 +23,23 @@ class ScrollPodcasts extends StatefulWidget {
}
class _ScrollPodcastsState extends State<ScrollPodcasts> {
var dir;
Future<List<PodcastLocal>> getPodcastLocal() async {
var dbHelper = DBHelper();
List<PodcastLocal> podcastList = await dbHelper.getPodcastLocal();
dir = await getApplicationDocumentsDirectory();
return podcastList;
}
ImportState importState;
didChangeDependencies() {
super.didChangeDependencies();
final importState = Provider.of<ImportOmpl>(context).importState;
if (importState == ImportState.complete) {
setState(() {});
}
}
@override
Widget build(BuildContext context) {
return FutureBuilder<List<PodcastLocal>>(
@ -38,8 +56,8 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
height: 70,
alignment: Alignment.centerLeft,
child: TabBar(
labelPadding:
EdgeInsets.only(bottom: 15.0, left: 6.0, right: 6.0),
labelPadding: EdgeInsets.only(
top: 5.0, bottom: 10.0, left: 6.0, right: 6.0),
indicator:
CircleTabIndicator(color: Colors.blue, radius: 3),
isScrollable: true,
@ -50,11 +68,8 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
child: LimitedBox(
maxHeight: 50,
maxWidth: 50,
child: CachedNetworkImage(
imageUrl: podcastLocal.imageUrl,
placeholder: (context, url) =>
CircularProgressIndicator(),
),
child: Image.file(
File("${dir.path}/${podcastLocal.title}.png")),
),
),
);
@ -62,7 +77,7 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
),
),
Container(
height: 200,
height: 195,
margin: EdgeInsets.only(left: 10, right: 10),
decoration: BoxDecoration(
color: Colors.white,
@ -85,7 +100,9 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
),
);
}
return Center();
return Container(
height: 250.0,
);
},
);
}
@ -204,9 +221,9 @@ class ShowEpisode extends StatelessWidget {
context,
ScaleRoute(
page: EpisodeDetail(
episodeItem: podcast[index],
heroTag: 'scroll',
)),
episodeItem: podcast[index],
heroTag: 'scroll',
)),
);
},
child: Container(
@ -258,6 +275,7 @@ class ShowEpisode extends StatelessWidget {
flex: 5,
child: Container(
padding: EdgeInsets.only(top: 2.0),
alignment: Alignment.topLeft,
child: Text(
podcast[index].title,
style: TextStyle(
@ -269,7 +287,7 @@ class ShowEpisode extends StatelessWidget {
),
Expanded(
flex: 1,
child: Align(
child: Container(
alignment: Alignment.bottomLeft,
child: Text(
podcast[index].pubDate.substring(4, 16),
@ -319,5 +337,3 @@ class _CirclePainter extends BoxPainter {
canvas.drawCircle(circleOffset, radius, _paint);
}
}

View File

@ -15,8 +15,8 @@ class _MainTabState extends State<MainTab> with TickerProviderStateMixin {
Decoration getIndicator() {
return const UnderlineTabIndicator(
borderSide: BorderSide(color: Colors.red, width: 2),
insets: EdgeInsets.only(left:20,top:10,)
);}
insets: EdgeInsets.only(left:10.0,right: 10.0, top:10.0,)
);}
@override
void initState() {
super.initState();
@ -36,20 +36,21 @@ class _MainTabState extends State<MainTab> with TickerProviderStateMixin {
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
padding: EdgeInsets.symmetric(horizontal: 10.0),
height: 50,
alignment: Alignment.centerLeft,
child: TabBar(
isScrollable: true,
labelPadding:
EdgeInsets.only(bottom:10.0,left: 20.0),
EdgeInsets.all(10.0),
controller: _controller,
labelColor: Colors.red,
unselectedLabelColor: Colors.black,
indicator: getIndicator(),
tabs: <Widget>[
Text('Recent Update',style: TextStyle(fontWeight: FontWeight.bold),),
Text('Favorite',style: TextStyle(fontWeight: FontWeight.bold),),
Text('Dowloads',style: TextStyle(fontWeight: FontWeight.bold),),
Text('Favorites',style: TextStyle(fontWeight: FontWeight.bold),),
Text('Downloads',style: TextStyle(fontWeight: FontWeight.bold),),
],
),
),

View File

@ -6,26 +6,72 @@ class Import extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer<ImportOmpl>(
builder: (context, importOmpl, _) => Container(
child: importOmpl.importState == ImportState.start
? Container(
height: 20.0,
alignment: Alignment.center,
child: Text('Start'),
)
: importOmpl.importState == ImportState.import
? Container(
builder: (context, importOmpl, _) => Container(
color: Colors.grey[300],
child: importOmpl.importState == ImportState.start
? Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SizedBox(
height: 2.0,
child: LinearProgressIndicator()),
Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
height: 20.0,
alignment: Alignment.center,
child: Text('Importing'+(importOmpl.rsstitle)))
: importOmpl.importState == ImportState.complete
? Container(
alignment: Alignment.centerLeft,
child: Text('Read file successful'),
),
])
: importOmpl.importState == ImportState.import
? Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SizedBox(
height: 2.0,
child: LinearProgressIndicator()),
Container(
height: 20.0,
alignment: Alignment.center,
child: Text('Complete'),
)
: importOmpl.importState == ImportState.stop
? Center()
: Center()));
padding: EdgeInsets.symmetric(horizontal: 20.0),
alignment: Alignment.centerLeft,
child:
Text('Importing: ' + (importOmpl.rsstitle))),
],
)
: importOmpl.importState == ImportState.parse
? Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SizedBox(
height: 2.0,
child: LinearProgressIndicator()),
Container(
height: 20.0,
padding: EdgeInsets.symmetric(horizontal: 20.0),
alignment: Alignment.centerLeft,
child: Text('Fatch: ' + (importOmpl.rsstitle)),
),
],
)
: importOmpl.importState == ImportState.error
? Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SizedBox(
height: 2.0,
child: LinearProgressIndicator()),
Container(
height: 20.0,
padding: EdgeInsets.symmetric(horizontal: 20.0),
alignment: Alignment.centerLeft,
child: Text('Error: ' + (importOmpl.rsstitle)),
),
],
)
: Center()),
);
}
}

View File

@ -4,12 +4,14 @@ import 'package:provider/provider.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'addpodcast.dart';
import 'class/audiostate.dart';
import 'class/importompl.dart';
void main() async {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => Urlchange()),
ChangeNotifierProvider(create: (context) => ImportOmpl()),
],
child: MyApp(),
),

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'class/podcastlocal.dart';
@ -72,7 +73,7 @@ class _AboutPodcastState extends State<AboutPodcast> {
children: <Widget>[
!_load
? Center()
: _description != null ? Text(_description) : Center(),
: _description != null ? Html(data: _description) : Center(),
(widget.podcastLocal.author != null)
? Text(widget.podcastLocal.author,
style: TextStyle(color: Colors.blue))

View File

@ -6,6 +6,9 @@ import 'package:provider/provider.dart';
import 'package:xml/xml.dart' as xml;
import 'package:file_picker/file_picker.dart';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
import 'package:color_thief_flutter/color_thief_flutter.dart';
import 'package:image/image.dart' as img;
import 'about.dart';
import 'class/podcastlocal.dart';
import 'class/sqflite_localpodcast.dart';
@ -28,43 +31,71 @@ class OmplOutline {
class PopupMenu extends StatelessWidget {
Future<int> saveOmpl(String rss) async {
var dbHelper = DBHelper();
try {
Response response = await Dio().get(rss);
var _p = RssFeed.parse(response.data);
String _primaryColor = '[100,100,100]';
PodcastLocal podcastLocal = PodcastLocal(_p.title, _p.itunes.image.href,
rss, _primaryColor, _p.author);
podcastLocal.description = _p.description;
int total = await dbHelper.savePodcastLocal(podcastLocal);
return total;
} catch (e) {
return 0;
}
Future<String> getColor(File file) async {
final imageProvider = FileImage(file);
var colorImage = await getImageFromProvider(imageProvider);
var color = await getColorFromImage(colorImage);
String primaryColor = color.toString();
return primaryColor;
}
@override
Widget build(BuildContext context) {
final importOmpl = Provider.of<ImportOmpl>(context);
saveOmpl(String rss) async {
var dbHelper = DBHelper();
try {
importOmpl.importState = ImportState.import;
Response response = await Dio().get(rss);
var _p = RssFeed.parse(response.data);
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);
File("${dir.path}/${_p.title}.png")
..writeAsBytesSync(img.encodePng(thumbnail));
String _primaryColor =
await getColor(File("${dir.path}/${_p.title}.png"));
PodcastLocal podcastLocal = PodcastLocal(
_p.title, _p.itunes.image.href, rss, _primaryColor, _p.author);
podcastLocal.description = _p.description;
print('_p.description');
await dbHelper.savePodcastLocal(podcastLocal);
importOmpl.importState = ImportState.parse;
await dbHelper.savePodcastRss(response.data);
} catch (e) {
print(e);
}
}
void _saveOmpl(String path) async {
File file = File(path);
String opml = file.readAsStringSync();
try {
var content = xml.parse(opml);
importOmpl.importState = ImportState.import;
var total = content
.findAllElements('outline')
.map((ele) => OmplOutline.parse(ele))
.toList();
for (int i = 0; i < total.length; i++) {
if (total[i].xmlUrl != null)
await saveOmpl(total[i].xmlUrl);
importOmpl.rssTitle = total[i].text;
print(total[i].text);
if (total[i].xmlUrl != null) {
importOmpl.rssTitle = total[i].text;
await saveOmpl(total[i].xmlUrl);
print(total[i].text);
}
}
importOmpl.importState = ImportState.complete;
importOmpl.importState = ImportState.stop;
print('Import fisnished');
} catch (e) {
print(e);

View File

@ -1,4 +1,4 @@
import 'package:webfeed/util/helpers.dart';
import '../../util/helpers.dart';
import 'package:xml/xml.dart';
class DublinCore {

View File

@ -54,12 +54,12 @@ class RssItem {
guid: findElementOrNull(element, "guid")?.text,
pubDate: findElementOrNull(element, "pubDate")?.text,
author: findElementOrNull(element, "author")?.text,
comments: findElementOrNull(element, "comments")?.text,
source: RssSource.parse(findElementOrNull(element, "source")),
content: RssContent.parse(findElementOrNull(element, "content:encoded")),
media: Media.parse(element),
// comments: findElementOrNull(element, "comments")?.text,
// source: RssSource.parse(findElementOrNull(element, "source")),
// content: RssContent.parse(findElementOrNull(element, "content:encoded")),
// media: Media.parse(element),
enclosure: RssEnclosure.parse(findElementOrNull(element, "enclosure")),
dc: DublinCore.parse(element),
//dc: DublinCore.parse(element),
itunes: RssItemItunes.parse(element),
);
}

View File

@ -40,25 +40,26 @@ class RssItemItunes {
if (element == null) {
return null;
}
var episodeStr = findElementOrNull(element, "itunes:episode")?.text?.trim();
var seasonStr = findElementOrNull(element, "itunes:season")?.text?.trim();
var durationStr = findElementOrNull(element, "itunes:duration")?.text?.trim();
//var episodeStr = findElementOrNull(element, "itunes:episode")?.text?.trim();
//var seasonStr = findElementOrNull(element, "itunes:season")?.text?.trim();
var durationStr =
findElementOrNull(element, "itunes:duration")?.text?.trim();
return RssItemItunes(
title: findElementOrNull(element, "itunes:title")?.text?.trim(),
//episode: episodeStr == null ? null : int.parse(episodeStr),
//season: seasonStr == null ? null : int.parse(seasonStr),
duration: durationStr == null ? null : parseDuration(durationStr),
episodeType: newRssItunesEpisodeType(findElementOrNull(element, "itunes:episodeType")),
// episodeType: newRssItunesEpisodeType(findElementOrNull(element, "itunes:episodeType")),
author: findElementOrNull(element, "itunes:author")?.text?.trim(),
summary: findElementOrNull(element, "itunes:summary")?.text?.trim(),
explicit: parseBoolLiteral(element, "itunes:explicit"),
subtitle: findElementOrNull(element, "itunes:subtitle")?.text?.trim(),
keywords: findElementOrNull(element, "itunes:keywords")?.text?.split(",")?.map((keyword) => keyword.trim())?.toList(),
image: RssItunesImage.parse(findElementOrNull(element, "itunes:image")),
category: RssItunesCategory.parse(
findElementOrNull(element, "itunes:category")),
block: parseBoolLiteral(element, "itunes:block"),
//subtitle: findElementOrNull(element, "itunes:subtitle")?.text?.trim(),
// keywords: findElementOrNull(element, "itunes:keywords")?.text?.split(",")?.map((keyword) => keyword.trim())?.toList(),
// image: RssItunesImage.parse(findElementOrNull(element, "itunes:image")),
// category: RssItunesCategory.parse(
// findElementOrNull(element, "itunes:category")),
// block: parseBoolLiteral(element, "itunes:block"),
);
}
}

View File

@ -48,22 +48,22 @@ class RssItunes {
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")),
keywords: findElementOrNull(element, "itunes:keywords")
?.text
?.split(",")
?.map((keyword) => keyword.trim())
?.toList(),
// subtitle: findElementOrNull(element, "itunes:subtitle")?.text?.trim(),
//owner: RssItunesOwner.parse(findElementOrNull(element, "itunes:owner")),
// keywords: findElementOrNull(element, "itunes:keywords")
// ?.text
// ?.split(",")
// ?.map((keyword) => keyword.trim())
// ?.toList(),
image: RssItunesImage.parse(findElementOrNull(element, "itunes:image")),
categories: findAllDirectElementsOrNull(element, "itunes:category")
.map((ele) => RssItunesCategory.parse(ele))
.toList(),
type: newRssItunesType(findElementOrNull(element, "itunes:type")),
newFeedUrl:
findElementOrNull(element, "itunes:new-feed-url")?.text?.trim(),
block: parseBoolLiteral(element, "itunes:block"),
complete: parseBoolLiteral(element, "itunes:complete"),
// categories: findAllDirectElementsOrNull(element, "itunes:category")
// .map((ele) => RssItunesCategory.parse(ele))
// .toList(),
// type: newRssItunesType(findElementOrNull(element, "itunes:type")),
// newFeedUrl:
// findElementOrNull(element, "itunes:new-feed-url")?.text?.trim(),
// block: parseBoolLiteral(element, "itunes:block"),
// complete: parseBoolLiteral(element, "itunes:complete"),
);
}
}

View File

@ -5,6 +5,7 @@ import 'package:xml/xml.dart';
XmlElement findElementOrNull(XmlElement element, String name,
{String namespace}) {
try {
return element.findAllElements(name, namespace: namespace).first;
} on StateError {
return null;

View File

@ -185,7 +185,7 @@ packages:
source: hosted
version: "3.1.3"
image:
dependency: transitive
dependency: "direct dev"
description:
name: image
url: "https://pub.flutter-io.cn"

View File

@ -48,6 +48,7 @@ dev_dependencies:
fluttertoast: ^3.1.3
intl: ^0.16.1
url_launcher: ^5.4.1
image: ^2.1.4
# For information on the generic Dart part of this file, see the