Change import ompl and refresh all to work in isolate
Add speed setting when playing Add real dark mode
This commit is contained in:
parent
3f989ac7b6
commit
1177d18b1a
13
README.md
13
README.md
|
@ -11,20 +11,21 @@ Enjoy podcasts with Tsacdop.
|
|||
|
||||
Tsacdop is a podcast player developed with flutter, a clean, simply beautiful and friendly app, only support Android right now.
|
||||
|
||||
|
||||
Credit to flutter team and all involved plugins, especially [webfeed](https://github.com/witochandra/webfeed) and [Just_Audio](https://pub.dev/packages/just_audio).
|
||||
|
||||
The podcasts search engine is powered by [ListenNotes](https://listennotes.com).
|
||||
|
||||
<a href='https://play.google.com/store/apps/details?id=com.stonegate.tsacdop&pcampaignid=pcampaignidMKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' src='https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png'/></a>
|
||||
|
||||
## Features
|
||||
* Podcasts group manage
|
||||
* Subscriptoin group management
|
||||
* Playlist support
|
||||
* Sleep timer
|
||||
* OMPL file export and import
|
||||
* Syncing in background
|
||||
* Listen and subscribe history data
|
||||
* Dark theme / Accent color
|
||||
* Donwload for offline playing
|
||||
* Auto syncing in background
|
||||
* Listen and subscribe history record
|
||||
* Dark mode/ Accent color personalization
|
||||
* Download for offline playing
|
||||
|
||||
More to come...
|
||||
|
||||
|
|
|
@ -1,2 +1,13 @@
|
|||
arguments=
|
||||
auto.sync=false
|
||||
build.scans.enabled=false
|
||||
connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
|
||||
connection.project.dir=
|
||||
eclipse.preferences.version=1
|
||||
gradle.user.home=
|
||||
java.home=C\:/Program Files/Java/jdk1.8.0_171
|
||||
jvm.arguments=
|
||||
offline.mode=false
|
||||
override.workspace.settings=true
|
||||
show.console.view=true
|
||||
show.executions.view=true
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
package io.flutter.plugins;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import io.flutter.embedding.engine.FlutterEngine;
|
||||
import io.flutter.embedding.engine.plugins.shim.ShimPluginRegistry;
|
||||
import io.flutter.plugin.common.PluginRegistry;
|
||||
import io.flutter.plugins.pathprovider.PathProviderPlugin;
|
||||
import com.tekartik.sqflite.SqflitePlugin;
|
||||
import io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin;
|
||||
|
||||
/**
|
||||
* Generated file. Do not edit. This file is generated by the Flutter tool based
|
||||
* on the plugins that support the Android platform.
|
||||
*/
|
||||
|
||||
@Keep
|
||||
public final class IsolatePluginRegistrant {
|
||||
public static void registerWith(PluginRegistry registry) {
|
||||
if (alreadyRegisteredWith(registry)) {
|
||||
return;
|
||||
}
|
||||
PathProviderPlugin.registerWith(registry.registrarFor("io.flutter.plugins.pathprovider.PathProviderPlugin"));
|
||||
SqflitePlugin.registerWith(registry.registrarFor("com.tekartik.sqflite.SqflitePlugin"));
|
||||
SharedPreferencesPlugin.registerWith(registry.registrarFor("io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin"));
|
||||
}
|
||||
|
||||
private static boolean alreadyRegisteredWith(PluginRegistry registry) {
|
||||
final String key = IsolatePluginRegistrant.class.getCanonicalName();
|
||||
if (registry.hasPlugin(key)) {
|
||||
return true;
|
||||
}
|
||||
registry.registrarFor(key);
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -5,16 +5,18 @@ import io.flutter.embedding.android.FlutterActivity
|
|||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.view.FlutterNativeView
|
||||
import io.flutter.plugins.GeneratedPluginRegistrant
|
||||
import io.flutter.plugins.IsolatePluginRegistrant
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
||||
import io.flutter.plugin.common.MethodChannel.Result
|
||||
import io.flutter.embedding.engine.dart.DartExecutor
|
||||
import io.flutter.embedding.engine.dart.DartExecutor.DartCallback
|
||||
import com.rmawatson.flutterisolate.FlutterIsolatePlugin
|
||||
|
||||
class MainActivity: FlutterActivity() {
|
||||
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
||||
GeneratedPluginRegistrant.registerWith(flutterEngine);
|
||||
|
||||
FlutterIsolatePlugin.setCustomIsolateRegistrant(IsolatePluginRegistrant::class.java);
|
||||
MethodChannel(flutterEngine.dartExecutor, "android_app_retain").apply {
|
||||
setMethodCallHandler { method, result ->
|
||||
if (method.method == "sendToBackground") {
|
||||
|
|
|
@ -133,6 +133,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
bool _autoPlay = true;
|
||||
DateTime _current;
|
||||
int _currentPosition;
|
||||
double _currentSpeed = 1;
|
||||
BehaviorSubject<List<MediaItem>> queueSubject;
|
||||
|
||||
BasicPlaybackState get audioState => _audioState;
|
||||
|
@ -152,6 +153,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
bool get autoPlay => _autoPlay;
|
||||
int get timeLeft => _timeLeft;
|
||||
double get switchValue => _switchValue;
|
||||
double get currentSpeed => _currentSpeed;
|
||||
|
||||
set setSwitchValue(double value) {
|
||||
_switchValue = value;
|
||||
|
@ -173,7 +175,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
super.addListener(listener);
|
||||
_queueUpdate = false;
|
||||
await AudioService.connect();
|
||||
bool running = AudioService.running;
|
||||
bool running = AudioService.running;
|
||||
if (running) {}
|
||||
}
|
||||
|
||||
|
@ -205,8 +207,6 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
await _queue.savePlaylist();
|
||||
} else {
|
||||
await _queue.getPlaylist();
|
||||
// _queue.playlist
|
||||
// .removeWhere((item) => item.enclosureUrl == episode.enclosureUrl);
|
||||
await _queue.delFromPlaylist(episode);
|
||||
await _queue.addToPlayListAt(episodeNew, 0);
|
||||
_backgroundAudioDuration = 0;
|
||||
|
@ -257,7 +257,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
}
|
||||
notifyListeners();
|
||||
});
|
||||
|
||||
|
||||
queueSubject = BehaviorSubject<List<MediaItem>>();
|
||||
queueSubject.addStream(
|
||||
AudioService.queueStream.distinct().where((event) => event != null));
|
||||
|
@ -304,14 +304,16 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
});
|
||||
|
||||
double s = _currentSpeed ?? 1.0;
|
||||
int getPosition = 0;
|
||||
Timer.periodic(Duration(milliseconds: 500), (timer) {
|
||||
if (_noSlide) {
|
||||
if (_audioState == BasicPlaybackState.playing) {
|
||||
if (_backgroundAudioPosition < _backgroundAudioDuration - 500)
|
||||
_backgroundAudioPosition = _currentPosition +
|
||||
DateTime.now().difference(_current).inMilliseconds;
|
||||
else
|
||||
_backgroundAudioPosition = _backgroundAudioDuration;
|
||||
getPosition = _currentPosition +
|
||||
(DateTime.now().difference(_current).inMilliseconds * s).toInt();
|
||||
_backgroundAudioPosition = getPosition < _backgroundAudioDuration
|
||||
? getPosition
|
||||
: _backgroundAudioDuration;
|
||||
} else
|
||||
_backgroundAudioPosition = _currentPosition;
|
||||
|
||||
|
@ -435,6 +437,12 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
setSpeed(double speed) async {
|
||||
await AudioService.customAction('setSpeed', speed);
|
||||
_currentSpeed = speed;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
//Set sleep timer
|
||||
sleepTimer(int mins) {
|
||||
if (_sleepTimerMode == SleepTimerMode.timer) {
|
||||
|
@ -710,7 +718,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
}
|
||||
|
||||
@override
|
||||
void onCustomAction(funtion, argument) {
|
||||
void onCustomAction(funtion, argument) async {
|
||||
switch (funtion) {
|
||||
case 'stopAtEnd':
|
||||
_stopAtEnd = true;
|
||||
|
@ -718,6 +726,8 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
case 'cancelStopAtEnd':
|
||||
_stopAtEnd = false;
|
||||
break;
|
||||
case 'setSpeed':
|
||||
await _audioPlayer.setSpeed(argument);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,8 +16,15 @@ class FiresideData {
|
|||
|
||||
DBHelper dbHelper = DBHelper();
|
||||
|
||||
String parseLink(String link) {
|
||||
if (link == "http://www.shengfm.cn/")
|
||||
return "https://guiguzaozhidao.fireside.fm/";
|
||||
else
|
||||
return link;
|
||||
}
|
||||
|
||||
Future fatchData() async {
|
||||
Response response = await Dio().get(link);
|
||||
Response response = await Dio().get(parseLink(link));
|
||||
if (response.statusCode == 200) {
|
||||
var doc = parse(response.data);
|
||||
RegExp reg = RegExp(r'https(.+)jpg');
|
||||
|
@ -54,11 +61,14 @@ class FiresideData {
|
|||
Future getData() async {
|
||||
List<String> data = await dbHelper.getFiresideData(id);
|
||||
_background = data[0];
|
||||
_hosts = json
|
||||
.decode(data[1])['hosts']
|
||||
.cast<Map<String, Object>>()
|
||||
.map<PodcastHost>(PodcastHost.fromJson)
|
||||
.toList();
|
||||
if (data[1] != '') {
|
||||
_hosts = json
|
||||
.decode(data[1])['hosts']
|
||||
.cast<Map<String, Object>>()
|
||||
.map<PodcastHost>(PodcastHost.fromJson)
|
||||
.toList();
|
||||
} else
|
||||
_hosts = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -107,7 +107,7 @@ class GroupList extends ChangeNotifier {
|
|||
await group.getPodcasts();
|
||||
});
|
||||
_orderChanged.clear();
|
||||
// notifyListeners();
|
||||
// notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,11 +129,12 @@ class GroupList extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
});
|
||||
}
|
||||
//update podcasts of each group
|
||||
Future updateGroups() async{
|
||||
|
||||
//update podcasts of each group
|
||||
Future updateGroups() async {
|
||||
await Future.forEach(_groups, (group) async {
|
||||
await group.getPodcasts();
|
||||
});
|
||||
await group.getPodcasts();
|
||||
});
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
|
@ -180,11 +181,11 @@ class GroupList extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
Future updatePodcast(PodcastLocal podcastLocal) async {
|
||||
List<int> counts = await dbHelper.getPodcastCounts(podcastLocal.id);
|
||||
Future updatePodcast(String id) async {
|
||||
List<int> counts = await dbHelper.getPodcastCounts(id);
|
||||
_groups.forEach((group) {
|
||||
if (group.podcastList.contains(podcastLocal.id)) {
|
||||
group.podcasts.firstWhere((podcast) => podcast.id == podcastLocal.id)
|
||||
if (group.podcastList.contains(id)) {
|
||||
group.podcasts.firstWhere((podcast) => podcast.id == id)
|
||||
..upateCount = counts[0]
|
||||
..episodeCount = counts[1];
|
||||
notifyListeners();
|
||||
|
@ -192,6 +193,13 @@ class GroupList extends ChangeNotifier {
|
|||
});
|
||||
}
|
||||
|
||||
Future subscribeNewPodcast(String id) async {
|
||||
_groups[0].podcastList.insert(0, id);
|
||||
_saveGroup();
|
||||
await _groups[0].getPodcasts();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
List<PodcastGroup> getPodcastGroup(String id) {
|
||||
List<PodcastGroup> result = [];
|
||||
_groups.forEach((group) {
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
import 'dart:isolate';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_isolate/flutter_isolate.dart';
|
||||
import 'package:tsacdop/local_storage/key_value_storage.dart';
|
||||
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
|
||||
|
||||
import 'podcastlocal.dart';
|
||||
|
||||
enum RefreshState { none, fetch, error }
|
||||
|
||||
class RefreshItem {
|
||||
String title;
|
||||
RefreshState refreshState;
|
||||
RefreshItem(this.title, this.refreshState);
|
||||
}
|
||||
|
||||
class RefreshWorker extends ChangeNotifier {
|
||||
FlutterIsolate refreshIsolate;
|
||||
ReceivePort receivePort;
|
||||
SendPort refreshSendPort;
|
||||
|
||||
RefreshItem _currentRefreshItem = RefreshItem('', RefreshState.none);
|
||||
bool _complete = false;
|
||||
RefreshItem get currentRefreshItem => _currentRefreshItem;
|
||||
bool get complete => _complete;
|
||||
|
||||
bool _created = false;
|
||||
|
||||
Future<void> _createIsolate() async {
|
||||
receivePort = ReceivePort();
|
||||
refreshIsolate = await FlutterIsolate.spawn(
|
||||
refreshIsolateEntryPoint, receivePort.sendPort);
|
||||
}
|
||||
|
||||
void _listen() {
|
||||
receivePort.distinct().listen((message) {
|
||||
if (message is List) {
|
||||
_currentRefreshItem =
|
||||
RefreshItem(message[0], RefreshState.values[message[1]]);
|
||||
notifyListeners();
|
||||
} else if (message is String && message == "done") {
|
||||
_currentRefreshItem = RefreshItem('', RefreshState.none);
|
||||
_complete = true;
|
||||
notifyListeners();
|
||||
refreshIsolate?.kill();
|
||||
refreshIsolate = null;
|
||||
_created = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> start() async {
|
||||
if (!_created) {
|
||||
_complete = false;
|
||||
_createIsolate();
|
||||
_listen();
|
||||
_created = true;
|
||||
}
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
refreshIsolate?.kill();
|
||||
refreshIsolate = null;
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> refreshIsolateEntryPoint(SendPort sendPort) async {
|
||||
var dbHelper = DBHelper();
|
||||
List<PodcastLocal> podcastList = await dbHelper.getPodcastLocalAll();
|
||||
int i = 0;
|
||||
await Future.forEach(podcastList, (podcastLocal) async {
|
||||
sendPort.send([podcastLocal.title, 1]);
|
||||
try {
|
||||
i += await dbHelper.updatePodcastRss(podcastLocal);
|
||||
print('Refresh ' + podcastLocal.title);
|
||||
} catch (e) {
|
||||
sendPort.send([podcastLocal.title, 2]);
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
}
|
||||
});
|
||||
KeyValueStorage refreshstorage = KeyValueStorage('refreshdate');
|
||||
await refreshstorage.saveInt(DateTime.now().millisecondsSinceEpoch);
|
||||
KeyValueStorage refreshcountstorage = KeyValueStorage('refreshcount');
|
||||
await refreshcountstorage.saveInt(i);
|
||||
sendPort.send("done");
|
||||
}
|
|
@ -28,11 +28,13 @@ class SettingState extends ChangeNotifier {
|
|||
KeyValueStorage downloadUsingDataStorage =
|
||||
KeyValueStorage('downloadUsingData');
|
||||
KeyValueStorage introStorage = KeyValueStorage('intro');
|
||||
KeyValueStorage realDarkStorage = KeyValueStorage('realDark');
|
||||
|
||||
Future initData() async {
|
||||
await _getTheme();
|
||||
await _getAccentSetColor();
|
||||
await _getShowIntro();
|
||||
await _getRealDark();
|
||||
}
|
||||
|
||||
ThemeMode _theme;
|
||||
|
@ -98,12 +100,21 @@ class SettingState extends ChangeNotifier {
|
|||
bool _showIntro;
|
||||
bool get showIntro => _showIntro;
|
||||
|
||||
bool _realDark;
|
||||
bool get realDark => _realDark;
|
||||
set setRealDark(bool boo) {
|
||||
_realDark = boo;
|
||||
_setRealDark();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void addListener(VoidCallback listener) {
|
||||
super.addListener(listener);
|
||||
_getTheme();
|
||||
_getAccentSetColor();
|
||||
_getAutoUpdate();
|
||||
_getRealDark();
|
||||
_getDownloadUsingData();
|
||||
_getUpdateInterval().then((value) {
|
||||
if (_initUpdateTag == 0) setWorkManager(24);
|
||||
|
@ -165,7 +176,17 @@ class SettingState extends ChangeNotifier {
|
|||
int i = await introStorage.getInt();
|
||||
_showIntro = i == 0 ? true : false;
|
||||
}
|
||||
Future saveShowIntro() async{
|
||||
|
||||
Future saveShowIntro() async {
|
||||
await introStorage.saveInt(1);
|
||||
}
|
||||
|
||||
Future _getRealDark() async {
|
||||
int i = await realDarkStorage.getInt();
|
||||
_realDark = i == 0 ? false : true;
|
||||
}
|
||||
|
||||
Future _setRealDark() async {
|
||||
await realDarkStorage.saveInt(_realDark ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,197 @@
|
|||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:image/image.dart' as img;
|
||||
import 'package:color_thief_flutter/color_thief_flutter.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter_isolate/flutter_isolate.dart';
|
||||
|
||||
import '../webfeed/webfeed.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import 'fireside_data.dart';
|
||||
import 'podcastlocal.dart';
|
||||
|
||||
enum SubscribeState { none, start, subscribe, fetch, stop, exist, error }
|
||||
|
||||
class SubscribeItem {
|
||||
String url;
|
||||
String title;
|
||||
SubscribeState subscribeState;
|
||||
String id;
|
||||
SubscribeItem(this.url, this.title,
|
||||
{this.subscribeState = SubscribeState.none, this.id = ''});
|
||||
}
|
||||
|
||||
class SubscribeWorker extends ChangeNotifier {
|
||||
FlutterIsolate subIsolate;
|
||||
ReceivePort receivePort;
|
||||
SendPort subSendPort;
|
||||
|
||||
SubscribeItem _subscribeItem;
|
||||
SubscribeItem _currentSubscribeItem = SubscribeItem('', '');
|
||||
bool _created = false;
|
||||
|
||||
setSubscribeItem(SubscribeItem item) async{
|
||||
_subscribeItem = item;
|
||||
await _start();
|
||||
}
|
||||
|
||||
_setCurrentSubscribeItem(SubscribeItem item) {
|
||||
_currentSubscribeItem = item;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
SubscribeItem get currentSubscribeItem => _currentSubscribeItem;
|
||||
|
||||
Future<void> _createIsolate() async {
|
||||
receivePort = ReceivePort();
|
||||
subIsolate =
|
||||
await FlutterIsolate.spawn(subIsolateEntryPoint, receivePort.sendPort);
|
||||
}
|
||||
|
||||
void listen() {
|
||||
receivePort.distinct().listen((message) {
|
||||
if (message is SendPort) {
|
||||
subSendPort = message;
|
||||
subSendPort.send([_subscribeItem.url, _subscribeItem.title]);
|
||||
} else if (message is List) {
|
||||
_setCurrentSubscribeItem(SubscribeItem(message[1], message[0],
|
||||
subscribeState: SubscribeState.values[message[2]],
|
||||
id: message.length == 4 ? message[3] : ''));
|
||||
print(message[2]);
|
||||
} else if (message is String && message == "done") {
|
||||
subIsolate?.kill();
|
||||
subIsolate = null;
|
||||
_created = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future _start() async {
|
||||
if (_created == false) {
|
||||
await _createIsolate();
|
||||
_created = true;
|
||||
listen();
|
||||
} else
|
||||
subSendPort.send([_subscribeItem.url, _subscribeItem.title]);
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
subIsolate?.kill();
|
||||
subIsolate = null;
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> subIsolateEntryPoint(SendPort sendPort) async {
|
||||
List<SubscribeItem> items = [];
|
||||
bool _running = false;
|
||||
ReceivePort subReceivePort = ReceivePort();
|
||||
sendPort.send(subReceivePort.sendPort);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Future<void> _subscribe(SubscribeItem item) async {
|
||||
var dbHelper = DBHelper();
|
||||
String rss = item.url;
|
||||
sendPort.send([item.title, item.url, 1]);
|
||||
BaseOptions options = new BaseOptions(
|
||||
connectTimeout: 20000,
|
||||
receiveTimeout: 20000,
|
||||
);
|
||||
print(rss);
|
||||
Response response = await Dio(options).get(rss);
|
||||
if (response.statusCode == 200) {
|
||||
var p = RssFeed.parse(response.data);
|
||||
|
||||
var dir = await getApplicationDocumentsDirectory();
|
||||
|
||||
String realUrl =
|
||||
response.redirects.isEmpty ? rss : response.realUri.toString();
|
||||
|
||||
print(realUrl);
|
||||
bool checkUrl = await dbHelper.checkPodcast(realUrl);
|
||||
|
||||
if (checkUrl) {
|
||||
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"));
|
||||
String author = p.itunes.author ?? p.author ?? '';
|
||||
String provider = p.generator ?? '';
|
||||
String link = p.link ?? '';
|
||||
PodcastLocal podcastLocal = PodcastLocal(p.title, p.itunes.image.href,
|
||||
realUrl, primaryColor, author, uuid, imagePath, provider, link,
|
||||
description: p.description);
|
||||
|
||||
// await groupList.subscribe(podcastLocal);
|
||||
await dbHelper.savePodcastLocal(podcastLocal);
|
||||
sendPort.send([item.title, item.url, 2, uuid]);
|
||||
if (provider.contains('fireside')) {
|
||||
FiresideData data = FiresideData(uuid, link);
|
||||
try {
|
||||
await data.fatchData();
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
int count = await dbHelper.savePodcastRss(p, uuid);
|
||||
|
||||
sendPort.send([item.title, item.url, 3, uuid]);
|
||||
|
||||
await Future.delayed(Duration(seconds: 5));
|
||||
|
||||
sendPort.send([item.title, item.url, 4]);
|
||||
items.removeWhere((element) => element.url == item.url);
|
||||
if (items.length > 0) {
|
||||
await _subscribe(items.first);
|
||||
} else
|
||||
sendPort.send("done");
|
||||
} else {
|
||||
sendPort.send([item.title, item.url, 5]);
|
||||
await Future.delayed(Duration(seconds: 5));
|
||||
sendPort.send([item.title, item.url, 4]);
|
||||
items.removeWhere((element) => element.url == item.url);
|
||||
if (items.length > 0) {
|
||||
await _subscribe(items.first);
|
||||
} else
|
||||
sendPort.send("done");
|
||||
}
|
||||
} else {
|
||||
sendPort.send([item.title, item.url, 6]);
|
||||
await Future.delayed(Duration(seconds: 5));
|
||||
sendPort.send([item.title, item.url, 4]);
|
||||
items.removeWhere((element) => element.url == item.url);
|
||||
if (items.length > 0) {
|
||||
await _subscribe(items.first);
|
||||
} else
|
||||
sendPort.send("done");
|
||||
}
|
||||
}
|
||||
|
||||
subReceivePort.distinct().listen((message) {
|
||||
if (message is List<String>) {
|
||||
items.add(SubscribeItem(message[0], message[1]));
|
||||
if (!_running) {
|
||||
_subscribe(items.first);
|
||||
_running = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
|
@ -11,6 +11,7 @@ import 'package:intl/intl.dart';
|
|||
import 'package:tuple/tuple.dart';
|
||||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
import 'package:tsacdop/class/audiostate.dart';
|
||||
import 'package:tsacdop/class/episodebrief.dart';
|
||||
|
@ -165,7 +166,7 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
|
|||
child: Text(
|
||||
(widget.episodeItem.duration ~/ 60)
|
||||
.toString() +
|
||||
'mins',
|
||||
'min',
|
||||
style: textstyle),
|
||||
)
|
||||
: Center(),
|
||||
|
@ -207,7 +208,12 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
|
|||
? Html(
|
||||
padding: EdgeInsets.only(
|
||||
left: 20.0, right: 20, bottom: 10),
|
||||
defaultTextStyle: TextStyle(height: 1.8),
|
||||
defaultTextStyle:
|
||||
GoogleFonts.libreBaskerville(
|
||||
textStyle: TextStyle(
|
||||
height: 1.8,
|
||||
),
|
||||
),
|
||||
data: _description,
|
||||
linkStyle: TextStyle(
|
||||
color: Theme.of(context).accentColor,
|
||||
|
@ -230,8 +236,10 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
|
|||
_launchUrl(link.url);
|
||||
},
|
||||
text: _description,
|
||||
style: TextStyle(
|
||||
height: 1.8,
|
||||
style: GoogleFonts.libreBaskerville(
|
||||
textStyle: TextStyle(
|
||||
height: 1.8,
|
||||
),
|
||||
),
|
||||
linkStyle: TextStyle(
|
||||
color:
|
||||
|
|
|
@ -24,7 +24,6 @@ class _DownloadButtonState extends State<DownloadButton> {
|
|||
bool _permissionReady;
|
||||
bool _usingData;
|
||||
StreamSubscription _connectivity;
|
||||
EpisodeTask _task;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
|
@ -72,7 +72,7 @@ class AboutApp extends StatelessWidget {
|
|||
image: AssetImage('assets/logo.png'),
|
||||
height: 80,
|
||||
),
|
||||
Text('Version: 0.1.8'),
|
||||
Text('Version: 0.1.9'),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:io';
|
||||
import 'dart:convert';
|
||||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:color_thief_flutter/color_thief_flutter.dart';
|
||||
|
@ -10,17 +11,22 @@ import 'package:provider/provider.dart';
|
|||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:image/image.dart' as img;
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:tsacdop/class/audiostate.dart';
|
||||
import 'package:tsacdop/class/download_state.dart';
|
||||
import 'package:tsacdop/class/fireside_data.dart';
|
||||
import 'package:tsacdop/class/refresh_podcast.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import 'package:tsacdop/class/importompl.dart';
|
||||
import 'package:tsacdop/class/podcast_group.dart';
|
||||
import 'package:tsacdop/class/searchpodcast.dart';
|
||||
import 'package:tsacdop/class/podcastlocal.dart';
|
||||
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
|
||||
import 'package:tsacdop/home/appbar/popupmenu.dart';
|
||||
import 'package:tsacdop/webfeed/webfeed.dart';
|
||||
import 'package:tsacdop/.env.dart';
|
||||
import '../../class/importompl.dart';
|
||||
import '../../class/podcast_group.dart';
|
||||
import '../../class/searchpodcast.dart';
|
||||
import '../../class/podcastlocal.dart';
|
||||
import '../../class/subscribe_podcast.dart';
|
||||
import '../../local_storage/sqflite_localpodcast.dart';
|
||||
import '../../home/appbar/popupmenu.dart';
|
||||
import '../../util/context_extension.dart';
|
||||
import '../../webfeed/webfeed.dart';
|
||||
import '../../.env.dart';
|
||||
import '../nested_home.dart';
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
|
@ -52,6 +58,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||
child: Scaffold(
|
||||
key: _scaffoldKey,
|
||||
appBar: AppBar(
|
||||
//elevation: 1.0,
|
||||
centerTitle: true,
|
||||
leading: IconButton(
|
||||
tooltip: 'Add',
|
||||
|
@ -74,15 +81,16 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||
],
|
||||
),
|
||||
body: WillPopScope(
|
||||
onWillPop: () async {
|
||||
if (Platform.isAndroid) {
|
||||
_androidAppRetain.invokeMethod('sendToBackground');
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
child: Home()),
|
||||
onWillPop: () async {
|
||||
if (Platform.isAndroid) {
|
||||
_androidAppRetain.invokeMethod('sendToBackground');
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
child: Home(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -105,6 +113,32 @@ class _MyHomePageDelegate extends SearchDelegate<int> {
|
|||
return searchResult.results;
|
||||
}
|
||||
|
||||
static Future getRss(String url) async {
|
||||
try {
|
||||
BaseOptions options = new BaseOptions(
|
||||
connectTimeout: 10000,
|
||||
receiveTimeout: 10000,
|
||||
);
|
||||
Response response = await Dio(options).get(url);
|
||||
var p = RssFeed.parse(response.data);
|
||||
return OnlinePodcast(
|
||||
rss: url,
|
||||
title: p.title,
|
||||
publisher: p.author,
|
||||
description: p.description,
|
||||
image: p.itunes.image.href);
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
RegExp rssExp = RegExp(r'^(https?):\/\/(.*)');
|
||||
Widget invalidRss() => Container(
|
||||
height: 50,
|
||||
alignment: Alignment.center,
|
||||
child: Text('Invalid rss link'),
|
||||
);
|
||||
|
||||
@override
|
||||
ThemeData appBarTheme(BuildContext context) => Theme.of(context);
|
||||
|
||||
|
@ -135,27 +169,46 @@ class _MyHomePageDelegate extends SearchDelegate<int> {
|
|||
height: 20,
|
||||
),
|
||||
));
|
||||
return FutureBuilder(
|
||||
future: getList(query),
|
||||
builder: (BuildContext context, AsyncSnapshot<List> snapshot) {
|
||||
if (!snapshot.hasData && query != null)
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: 200),
|
||||
alignment: Alignment.topCenter,
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
List content = snapshot.data;
|
||||
return ListView.builder(
|
||||
scrollDirection: Axis.vertical,
|
||||
itemCount: content.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
else if (rssExp.stringMatch(query) != null)
|
||||
return FutureBuilder(
|
||||
future: getRss(rssExp.stringMatch(query)),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError)
|
||||
return invalidRss();
|
||||
else if (snapshot.hasData)
|
||||
return SearchResult(
|
||||
onlinePodcast: content[index],
|
||||
onlinePodcast: snapshot.data,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
else
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: 200),
|
||||
alignment: Alignment.topCenter,
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
},
|
||||
);
|
||||
else
|
||||
return FutureBuilder(
|
||||
future: getList(query),
|
||||
builder: (BuildContext context, AsyncSnapshot<List> snapshot) {
|
||||
if (!snapshot.hasData && query != null)
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: 200),
|
||||
alignment: Alignment.topCenter,
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
List content = snapshot.data;
|
||||
return ListView.builder(
|
||||
scrollDirection: Axis.vertical,
|
||||
itemCount: content.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return SearchResult(
|
||||
onlinePodcast: content[index],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -189,27 +242,46 @@ class _MyHomePageDelegate extends SearchDelegate<int> {
|
|||
),
|
||||
),
|
||||
);
|
||||
return FutureBuilder(
|
||||
future: getList(query),
|
||||
builder: (BuildContext context, AsyncSnapshot<List> snapshot) {
|
||||
if (!snapshot.hasData && query != null)
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: 200),
|
||||
alignment: Alignment.topCenter,
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
List content = snapshot.data;
|
||||
return ListView.builder(
|
||||
scrollDirection: Axis.vertical,
|
||||
itemCount: content.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
else if (rssExp.stringMatch(query) != null)
|
||||
return FutureBuilder(
|
||||
future: getRss(rssExp.stringMatch(query)),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError)
|
||||
return invalidRss();
|
||||
else if (snapshot.hasData)
|
||||
return SearchResult(
|
||||
onlinePodcast: content[index],
|
||||
onlinePodcast: snapshot.data,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
else
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: 200),
|
||||
alignment: Alignment.topCenter,
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
},
|
||||
);
|
||||
else
|
||||
return FutureBuilder(
|
||||
future: getList(query),
|
||||
builder: (BuildContext context, AsyncSnapshot<List> snapshot) {
|
||||
if (!snapshot.hasData && query != null)
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: 200),
|
||||
alignment: Alignment.topCenter,
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
List content = snapshot.data;
|
||||
return ListView.builder(
|
||||
scrollDirection: Axis.vertical,
|
||||
itemCount: content.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return SearchResult(
|
||||
onlinePodcast: content[index],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -220,21 +292,32 @@ class SearchResult extends StatefulWidget {
|
|||
_SearchResultState createState() => _SearchResultState();
|
||||
}
|
||||
|
||||
class _SearchResultState extends State<SearchResult> {
|
||||
class _SearchResultState extends State<SearchResult>
|
||||
with SingleTickerProviderStateMixin {
|
||||
bool _issubscribe;
|
||||
bool _adding;
|
||||
bool _showDes;
|
||||
|
||||
AnimationController _controller;
|
||||
Animation _animation;
|
||||
double _value;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_issubscribe = false;
|
||||
_adding = false;
|
||||
_showDes = false;
|
||||
_value = 0;
|
||||
_controller =
|
||||
AnimationController(vsync: this, duration: Duration(milliseconds: 300));
|
||||
_animation = Tween<double>(begin: 0.0, end: 1.0).animate(_controller)
|
||||
..addListener(() {
|
||||
setState(() {
|
||||
_value = _animation.value;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
@ -248,91 +331,98 @@ class _SearchResultState extends State<SearchResult> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var importOmpl = Provider.of<ImportOmpl>(context, listen: false);
|
||||
var groupList = Provider.of<GroupList>(context, listen: false);
|
||||
savePodcast(String rss) async {
|
||||
if (mounted) setState(() => _adding = true);
|
||||
// var importOmpl = Provider.of<ImportOmpl>(context, listen: false);
|
||||
// var groupList = Provider.of<GroupList>(context, listen: false);
|
||||
var subscribeWorker = Provider.of<SubscribeWorker>(context, listen: false);
|
||||
|
||||
importOmpl.importState = ImportState.import;
|
||||
try {
|
||||
BaseOptions options = new BaseOptions(
|
||||
connectTimeout: 30000,
|
||||
receiveTimeout: 30000,
|
||||
);
|
||||
Response response = await Dio(options).get(rss);
|
||||
var dbHelper = DBHelper();
|
||||
String _realUrl = response.realUri.toString();
|
||||
|
||||
bool _checkUrl = await dbHelper.checkPodcast(_realUrl);
|
||||
|
||||
if (_checkUrl) {
|
||||
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"));
|
||||
String _author = _p.itunes.author ?? _p.author ?? '';
|
||||
String _provider = _p.generator ?? '';
|
||||
String _link = _p.link ?? '';
|
||||
PodcastLocal podcastLocal = PodcastLocal(
|
||||
_p.title,
|
||||
_p.itunes.image.href,
|
||||
_realUrl,
|
||||
_primaryColor,
|
||||
_author,
|
||||
_uuid,
|
||||
_imagePath,
|
||||
_provider,
|
||||
_link,
|
||||
description: _p.description);
|
||||
await groupList.subscribe(podcastLocal);
|
||||
|
||||
if (_provider.contains('fireside')) {
|
||||
FiresideData data = FiresideData(_uuid, _link);
|
||||
await data.fatchData();
|
||||
}
|
||||
|
||||
importOmpl.importState = ImportState.parse;
|
||||
|
||||
await dbHelper.savePodcastRss(_p, _uuid);
|
||||
groupList.updatePodcast(podcastLocal);
|
||||
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.TOP,
|
||||
);
|
||||
await Future.delayed(Duration(seconds: 5));
|
||||
importOmpl.importState = ImportState.stop;
|
||||
}
|
||||
savePodcast(OnlinePodcast podcast) async {
|
||||
SubscribeItem item = SubscribeItem(podcast.rss, podcast.title);
|
||||
subscribeWorker.setSubscribeItem(item);
|
||||
}
|
||||
|
||||
// savePodcast(String rss) async {
|
||||
// if (mounted) setState(() => _adding = true);
|
||||
|
||||
// importOmpl.importState = ImportState.import;
|
||||
// try {
|
||||
// BaseOptions options = new BaseOptions(
|
||||
// connectTimeout: 30000,
|
||||
// receiveTimeout: 30000,
|
||||
// );
|
||||
// Response response = await Dio(options).get(rss);
|
||||
// var dbHelper = DBHelper();
|
||||
// String _realUrl = response.realUri.toString();
|
||||
|
||||
// bool _checkUrl = await dbHelper.checkPodcast(_realUrl);
|
||||
|
||||
// if (_checkUrl) {
|
||||
// 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"));
|
||||
// String _author = _p.itunes.author ?? _p.author ?? '';
|
||||
// String _provider = _p.generator ?? '';
|
||||
// String _link = _p.link ?? '';
|
||||
// PodcastLocal podcastLocal = PodcastLocal(
|
||||
// _p.title,
|
||||
// _p.itunes.image.href,
|
||||
// _realUrl,
|
||||
// _primaryColor,
|
||||
// _author,
|
||||
// _uuid,
|
||||
// _imagePath,
|
||||
// _provider,
|
||||
// _link,
|
||||
// description: _p.description);
|
||||
// await groupList.subscribe(podcastLocal);
|
||||
|
||||
// if (_provider.contains('fireside')) {
|
||||
// FiresideData data = FiresideData(_uuid, _link);
|
||||
// await data.fatchData();
|
||||
// }
|
||||
|
||||
// importOmpl.importState = ImportState.parse;
|
||||
|
||||
// await dbHelper.savePodcastRss(_p, _uuid);
|
||||
// groupList.updatePodcast(podcastLocal.id);
|
||||
// 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.TOP,
|
||||
// );
|
||||
// await Future.delayed(Duration(seconds: 5));
|
||||
// importOmpl.importState = ImportState.stop;
|
||||
// }
|
||||
// }
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
|
@ -348,6 +438,10 @@ class _SearchResultState extends State<SearchResult> {
|
|||
onTap: () {
|
||||
setState(() {
|
||||
_showDes = !_showDes;
|
||||
if (_value == 0)
|
||||
_controller.forward();
|
||||
else
|
||||
_controller.reverse();
|
||||
});
|
||||
},
|
||||
leading: ClipRRect(
|
||||
|
@ -361,51 +455,33 @@ class _SearchResultState extends State<SearchResult> {
|
|||
),
|
||||
),
|
||||
title: Text(widget.onlinePodcast.title),
|
||||
subtitle: Text(widget.onlinePodcast.publisher),
|
||||
subtitle: Text(widget.onlinePodcast.publisher ?? ''),
|
||||
trailing: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Icon(_showDes
|
||||
? Icons.keyboard_arrow_up
|
||||
: Icons.keyboard_arrow_down),
|
||||
Transform.rotate(
|
||||
angle: math.pi * _value,
|
||||
child: Icon(Icons.keyboard_arrow_down),
|
||||
),
|
||||
Padding(padding: EdgeInsets.only(right: 10.0)),
|
||||
Container(
|
||||
width: 100,
|
||||
height: 35,
|
||||
child: !_issubscribe
|
||||
? !_adding
|
||||
? OutlineButton(
|
||||
highlightedBorderColor:
|
||||
Theme.of(context).accentColor,
|
||||
splashColor: Theme.of(context)
|
||||
.accentColor
|
||||
.withOpacity(0.8),
|
||||
child: Text('Subscribe',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).accentColor)),
|
||||
onPressed: () {
|
||||
importOmpl.rssTitle =
|
||||
widget.onlinePodcast.title;
|
||||
savePodcast(widget.onlinePodcast.rss);
|
||||
})
|
||||
: OutlineButton(
|
||||
highlightedBorderColor:
|
||||
Theme.of(context).accentColor,
|
||||
splashColor: Theme.of(context)
|
||||
.accentColor
|
||||
.withOpacity(0.8),
|
||||
child: SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation(
|
||||
Theme.of(context).accentColor),
|
||||
)),
|
||||
onPressed: () {},
|
||||
)
|
||||
? OutlineButton(
|
||||
highlightedBorderColor: Theme.of(context).accentColor,
|
||||
splashColor:
|
||||
Theme.of(context).accentColor.withOpacity(0.8),
|
||||
child: Text('Subscribe',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).accentColor)),
|
||||
onPressed: () {
|
||||
savePodcast(widget.onlinePodcast);
|
||||
setState(() => _issubscribe = true);
|
||||
})
|
||||
: OutlineButton(
|
||||
color: context.accentColor.withOpacity(0.8),
|
||||
highlightedBorderColor: Colors.grey[500],
|
||||
disabledTextColor: Colors.grey[500],
|
||||
child: Text('Subscribe'),
|
||||
|
|
|
@ -1,75 +1,72 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tsacdop/class/importompl.dart';
|
||||
import '../../class/podcast_group.dart';
|
||||
import '../../class/subscribe_podcast.dart';
|
||||
import '../../class/refresh_podcast.dart';
|
||||
import '../../util/context_extension.dart';
|
||||
|
||||
class Import extends StatelessWidget {
|
||||
Widget importColumn(String text, BuildContext context) {
|
||||
return Container(
|
||||
color: context.primaryColorDark,
|
||||
child: 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.centerLeft,
|
||||
child: Text(text),
|
||||
),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<ImportOmpl>(
|
||||
builder: (_, importOmpl, __) => Container(
|
||||
color: Theme.of(context).primaryColorDark,
|
||||
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.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,
|
||||
padding: EdgeInsets.symmetric(horizontal: 20.0),
|
||||
alignment: Alignment.centerLeft,
|
||||
child:
|
||||
Text('Connetting: ' + (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('Fetch data: ' + (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()),
|
||||
GroupList groupList = Provider.of<GroupList>(context, listen: false);
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
Consumer<SubscribeWorker>(
|
||||
builder: (_, subscribeWorker, __) {
|
||||
SubscribeItem item = subscribeWorker.currentSubscribeItem;
|
||||
switch (item.subscribeState) {
|
||||
case SubscribeState.start:
|
||||
return importColumn("Subscribe: ${item.title}", context);
|
||||
case SubscribeState.subscribe:
|
||||
groupList.subscribeNewPodcast(item.id);
|
||||
return importColumn("Fetch data ${item.title}", context);
|
||||
case SubscribeState.fetch:
|
||||
groupList.updatePodcast(item.id);
|
||||
return importColumn("Subscribe success ${item.title}", context);
|
||||
case SubscribeState.exist:
|
||||
return importColumn(
|
||||
"Subscribe failed, podcast existed ${item.title}", context);
|
||||
case SubscribeState.error:
|
||||
return importColumn(
|
||||
"Subscribe failed, network error ${item.title}", context);
|
||||
default:
|
||||
return Center();
|
||||
}
|
||||
},
|
||||
),
|
||||
Consumer<RefreshWorker>(
|
||||
builder: (context, refreshWorker, child) {
|
||||
RefreshItem item = refreshWorker.currentRefreshItem;
|
||||
if (refreshWorker.complete) groupList.updateGroups();
|
||||
switch (item.refreshState) {
|
||||
case RefreshState.fetch:
|
||||
return importColumn("Fetch data ${item.title}", context);
|
||||
case RefreshState.error:
|
||||
return importColumn("Update error ${item.title}", context);
|
||||
default:
|
||||
return Center();
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ import 'package:flutter/material.dart';
|
|||
import 'package:dio/dio.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tsacdop/class/fireside_data.dart';
|
||||
import 'package:tsacdop/class/refresh_podcast.dart';
|
||||
import 'package:tsacdop/class/subscribe_podcast.dart';
|
||||
import 'package:tsacdop/local_storage/key_value_storage.dart';
|
||||
import 'package:xml/xml.dart' as xml;
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
|
@ -84,108 +86,109 @@ class _PopupMenuState extends State<PopupMenu> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
ImportOmpl importOmpl = Provider.of<ImportOmpl>(context, listen: false);
|
||||
GroupList groupList = Provider.of<GroupList>(context, listen: false);
|
||||
|
||||
_refreshAll() async {
|
||||
var dbHelper = DBHelper();
|
||||
List<PodcastLocal> podcastList = await dbHelper.getPodcastLocalAll();
|
||||
int i = 0;
|
||||
await Future.forEach(podcastList, (podcastLocal) async {
|
||||
importOmpl.rssTitle = podcastLocal.title;
|
||||
importOmpl.importState = ImportState.parse;
|
||||
i += await dbHelper.updatePodcastRss(podcastLocal);
|
||||
print('Refresh ' + podcastLocal.title);
|
||||
});
|
||||
KeyValueStorage refreshstorage = KeyValueStorage('refreshdate');
|
||||
await refreshstorage.saveInt(DateTime.now().millisecondsSinceEpoch);
|
||||
KeyValueStorage refreshcountstorage = KeyValueStorage('refreshcount');
|
||||
await refreshcountstorage.saveInt(i);
|
||||
importOmpl.importState = ImportState.complete;
|
||||
groupList.updateGroups();
|
||||
}
|
||||
|
||||
saveOmpl(String rss) async {
|
||||
var dbHelper = DBHelper();
|
||||
importOmpl.importState = ImportState.import;
|
||||
BaseOptions options = new BaseOptions(
|
||||
connectTimeout: 20000,
|
||||
receiveTimeout: 20000,
|
||||
);
|
||||
Response response = await Dio(options).get(rss);
|
||||
if (response.statusCode == 200) {
|
||||
var _p = RssFeed.parse(response.data);
|
||||
|
||||
var dir = await getApplicationDocumentsDirectory();
|
||||
|
||||
String _realUrl =
|
||||
response.redirects.isEmpty ? rss : response.realUri.toString();
|
||||
|
||||
print(_realUrl);
|
||||
bool _checkUrl = await dbHelper.checkPodcast(_realUrl);
|
||||
|
||||
if (_checkUrl) {
|
||||
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"));
|
||||
String _author = _p.itunes.author ?? _p.author ?? '';
|
||||
String _provider = _p.generator ?? '';
|
||||
String _link = _p.link ?? '';
|
||||
PodcastLocal podcastLocal = PodcastLocal(
|
||||
_p.title,
|
||||
_p.itunes.image.href,
|
||||
_realUrl,
|
||||
_primaryColor,
|
||||
_author,
|
||||
_uuid,
|
||||
_imagePath,
|
||||
_provider,
|
||||
_link,
|
||||
description: _p.description);
|
||||
|
||||
await groupList.subscribe(podcastLocal);
|
||||
|
||||
if (_provider.contains('fireside')) {
|
||||
FiresideData data = FiresideData(_uuid, _link);
|
||||
await data.fatchData();
|
||||
}
|
||||
|
||||
importOmpl.importState = ImportState.parse;
|
||||
|
||||
await dbHelper.savePodcastRss(_p, _uuid);
|
||||
groupList.updatePodcast(podcastLocal);
|
||||
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.TOP,
|
||||
);
|
||||
await Future.delayed(Duration(seconds: 5));
|
||||
importOmpl.importState = ImportState.stop;
|
||||
}
|
||||
}
|
||||
|
||||
// ImportOmpl importOmpl = Provider.of<ImportOmpl>(context, listen: false);
|
||||
// GroupList groupList = Provider.of<GroupList>(context, listen: false);
|
||||
var refreshWorker = Provider.of<RefreshWorker>(context, listen: false);
|
||||
var subscribeWorker = Provider.of<SubscribeWorker>(context, listen: false);
|
||||
// _refreshAll() async {
|
||||
// var dbHelper = DBHelper();
|
||||
// List<PodcastLocal> podcastList = await dbHelper.getPodcastLocalAll();
|
||||
// int i = 0;
|
||||
// await Future.forEach(podcastList, (podcastLocal) async {
|
||||
// importOmpl.rssTitle = podcastLocal.title;
|
||||
// importOmpl.importState = ImportState.parse;
|
||||
// i += await dbHelper.updatePodcastRss(podcastLocal);
|
||||
// print('Refresh ' + podcastLocal.title);
|
||||
// });
|
||||
// KeyValueStorage refreshstorage = KeyValueStorage('refreshdate');
|
||||
// await refreshstorage.saveInt(DateTime.now().millisecondsSinceEpoch);
|
||||
// KeyValueStorage refreshcountstorage = KeyValueStorage('refreshcount');
|
||||
// await refreshcountstorage.saveInt(i);
|
||||
// importOmpl.importState = ImportState.complete;
|
||||
// groupList.updateGroups();
|
||||
// }
|
||||
//
|
||||
// saveOmpl(String rss) async {
|
||||
// var dbHelper = DBHelper();
|
||||
// importOmpl.importState = ImportState.import;
|
||||
// BaseOptions options = new BaseOptions(
|
||||
// connectTimeout: 20000,
|
||||
// receiveTimeout: 20000,
|
||||
// );
|
||||
// Response response = await Dio(options).get(rss);
|
||||
// if (response.statusCode == 200) {
|
||||
// var _p = RssFeed.parse(response.data);
|
||||
//
|
||||
// var dir = await getApplicationDocumentsDirectory();
|
||||
//
|
||||
// String _realUrl =
|
||||
// response.redirects.isEmpty ? rss : response.realUri.toString();
|
||||
//
|
||||
// print(_realUrl);
|
||||
// bool _checkUrl = await dbHelper.checkPodcast(_realUrl);
|
||||
//
|
||||
// if (_checkUrl) {
|
||||
// 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"));
|
||||
// String _author = _p.itunes.author ?? _p.author ?? '';
|
||||
// String _provider = _p.generator ?? '';
|
||||
// String _link = _p.link ?? '';
|
||||
// PodcastLocal podcastLocal = PodcastLocal(
|
||||
// _p.title,
|
||||
// _p.itunes.image.href,
|
||||
// _realUrl,
|
||||
// _primaryColor,
|
||||
// _author,
|
||||
// _uuid,
|
||||
// _imagePath,
|
||||
// _provider,
|
||||
// _link,
|
||||
// description: _p.description);
|
||||
//
|
||||
// await groupList.subscribe(podcastLocal);
|
||||
//
|
||||
// if (_provider.contains('fireside')) {
|
||||
// FiresideData data = FiresideData(_uuid, _link);
|
||||
// await data.fatchData();
|
||||
// }
|
||||
//
|
||||
// importOmpl.importState = ImportState.parse;
|
||||
//
|
||||
// await dbHelper.savePodcastRss(_p, _uuid);
|
||||
// groupList.updatePodcast(podcastLocal.id);
|
||||
// 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.TOP,
|
||||
// );
|
||||
// await Future.delayed(Duration(seconds: 5));
|
||||
// importOmpl.importState = ImportState.stop;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
void _saveOmpl(String path) async {
|
||||
File file = File(path);
|
||||
try {
|
||||
|
@ -198,18 +201,18 @@ class _PopupMenuState extends State<PopupMenu> {
|
|||
.toList();
|
||||
if (total.length == 0) {
|
||||
Fluttertoast.showToast(
|
||||
msg: 'File Not Valid',
|
||||
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;
|
||||
try {
|
||||
await saveOmpl(total[i].xmlUrl);
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
// importOmpl.rssTitle = total[i].text;
|
||||
//await saveOmpl(total[i].xmlUrl);
|
||||
SubscribeItem item =
|
||||
SubscribeItem(total[i].xmlUrl, total[i].text);
|
||||
await subscribeWorker.setSubscribeItem(item);
|
||||
await Future.delayed(Duration(milliseconds: 500));
|
||||
print(total[i].text);
|
||||
}
|
||||
}
|
||||
|
@ -221,8 +224,8 @@ class _PopupMenuState extends State<PopupMenu> {
|
|||
msg: 'File error, Subscribe failed',
|
||||
gravity: ToastGravity.TOP,
|
||||
);
|
||||
await Future.delayed(Duration(seconds: 5));
|
||||
importOmpl.importState = ImportState.stop;
|
||||
//await Future.delayed(Duration(seconds: 5));
|
||||
// importOmpl.importState = ImportState.stop;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -233,7 +236,11 @@ class _PopupMenuState extends State<PopupMenu> {
|
|||
return;
|
||||
}
|
||||
print('File Path' + filePath);
|
||||
importOmpl.importState = ImportState.start;
|
||||
//importOmpl.importState = ImportState.start;
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Read file successfully',
|
||||
gravity: ToastGravity.TOP,
|
||||
);
|
||||
_saveOmpl(filePath);
|
||||
} on PlatformException catch (e) {
|
||||
print(e.toString());
|
||||
|
@ -338,7 +345,8 @@ class _PopupMenuState extends State<PopupMenu> {
|
|||
} else if (value == 2) {
|
||||
_getFilePath();
|
||||
} else if (value == 1) {
|
||||
_refreshAll();
|
||||
//_refreshAll();
|
||||
refreshWorker.start();
|
||||
} else if (value == 3) {
|
||||
// setting.theme != 2 ? setting.setTheme(2) : setting.setTheme(1);
|
||||
} else if (value == 4) {
|
||||
|
|
|
@ -134,294 +134,6 @@ class PlayerWidget extends StatefulWidget {
|
|||
class _PlayerWidgetState extends State<PlayerWidget> {
|
||||
final GlobalKey<AnimatedListState> miniPlaylistKey = GlobalKey();
|
||||
|
||||
Widget _controlPanel(BuildContext context) {
|
||||
var audio = Provider.of<AudioPlayerNotifier>(context, listen: false);
|
||||
return Container(
|
||||
color: Theme.of(context).primaryColor,
|
||||
height: 300,
|
||||
padding: EdgeInsets.symmetric(horizontal: 10.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Consumer<AudioPlayerNotifier>(
|
||||
builder: (_, data, __) {
|
||||
Color _c = (Theme.of(context).brightness == Brightness.light)
|
||||
? data.episode.primaryColor.colorizedark()
|
||||
: data.episode.primaryColor.colorizeLight();
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: 10, left: 10, right: 10),
|
||||
child: SliderTheme(
|
||||
data: SliderTheme.of(context).copyWith(
|
||||
activeTrackColor:
|
||||
Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.black38
|
||||
: Colors.grey[400],
|
||||
inactiveTrackColor:
|
||||
Theme.of(context).primaryColorDark,
|
||||
trackHeight: 20.0,
|
||||
trackShape: MyRectangularTrackShape(),
|
||||
thumbColor: Theme.of(context).accentColor,
|
||||
thumbShape: MyRoundSliderThumpShape(
|
||||
enabledThumbRadius: 10.0,
|
||||
disabledThumbRadius: 10.0,
|
||||
thumbCenterColor: _c),
|
||||
overlayColor:
|
||||
Theme.of(context).accentColor.withAlpha(32),
|
||||
overlayShape:
|
||||
RoundSliderOverlayShape(overlayRadius: 4.0),
|
||||
),
|
||||
child: Slider(
|
||||
value: data.seekSliderValue,
|
||||
onChanged: (double val) {
|
||||
audio.sliderSeek(val);
|
||||
}),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: 20.0,
|
||||
padding: EdgeInsets.symmetric(horizontal: 30.0),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Text(
|
||||
_stringForSeconds(
|
||||
data.backgroundAudioPosition / 1000) ??
|
||||
'',
|
||||
style: TextStyle(fontSize: 10),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
child: data.remoteErrorMessage != null
|
||||
? Text(data.remoteErrorMessage,
|
||||
style: const TextStyle(
|
||||
color: const Color(0xFFFF0000)))
|
||||
: Text(
|
||||
data.audioState ==
|
||||
BasicPlaybackState
|
||||
.buffering ||
|
||||
data.audioState ==
|
||||
BasicPlaybackState
|
||||
.connecting ||
|
||||
data.audioState ==
|
||||
BasicPlaybackState.none
|
||||
? 'Buffring...'
|
||||
: '',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).accentColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
_stringForSeconds(
|
||||
data.backgroundAudioDuration / 1000) ??
|
||||
'',
|
||||
style: TextStyle(fontSize: 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
Container(
|
||||
height: 100,
|
||||
child: Selector<AudioPlayerNotifier, BasicPlaybackState>(
|
||||
selector: (_, audio) => audio.audioState,
|
||||
builder: (_, backplay, __) {
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
padding: EdgeInsets.symmetric(horizontal: 30.0),
|
||||
onPressed: backplay == BasicPlaybackState.playing
|
||||
? () => audio.forwardAudio(-10)
|
||||
: null,
|
||||
iconSize: 32.0,
|
||||
icon: Icon(Icons.replay_10),
|
||||
color: Colors.grey[500]),
|
||||
Container(
|
||||
margin: EdgeInsets.symmetric(horizontal: 30),
|
||||
height: 60,
|
||||
width: 60,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).primaryColor,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: Theme.of(context).brightness ==
|
||||
Brightness.dark
|
||||
? Colors.black12
|
||||
: Colors.white10,
|
||||
width: 1),
|
||||
boxShadow: Theme.of(context).brightness ==
|
||||
Brightness.dark
|
||||
? _customShadowNight
|
||||
: _customShadow),
|
||||
child: backplay == BasicPlaybackState.playing
|
||||
? Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(30)),
|
||||
onTap:
|
||||
backplay == BasicPlaybackState.playing
|
||||
? () {
|
||||
audio.pauseAduio();
|
||||
}
|
||||
: null,
|
||||
child: SizedBox(
|
||||
height: 60,
|
||||
width: 60,
|
||||
child: Icon(
|
||||
Icons.pause,
|
||||
size: 40,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(30)),
|
||||
onTap:
|
||||
backplay == BasicPlaybackState.playing
|
||||
? null
|
||||
: () {
|
||||
audio.resumeAudio();
|
||||
},
|
||||
child: SizedBox(
|
||||
height: 60,
|
||||
width: 60,
|
||||
child: Icon(
|
||||
Icons.play_arrow,
|
||||
size: 40,
|
||||
color: Theme.of(context).accentColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
padding: EdgeInsets.symmetric(horizontal: 30.0),
|
||||
onPressed: backplay == BasicPlaybackState.playing
|
||||
? () => audio.forwardAudio(30)
|
||||
: null,
|
||||
iconSize: 32.0,
|
||||
icon: Icon(Icons.forward_30),
|
||||
color: Colors.grey[500]),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: 70.0,
|
||||
padding: EdgeInsets.all(20),
|
||||
alignment: Alignment.center,
|
||||
child: Selector<AudioPlayerNotifier, String>(
|
||||
selector: (_, audio) => audio.episode.title,
|
||||
builder: (_, title, __) {
|
||||
return Container(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, size) {
|
||||
var span = TextSpan(
|
||||
text: title,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold, fontSize: 20));
|
||||
var tp = TextPainter(
|
||||
text: span,
|
||||
maxLines: 1,
|
||||
textDirection: TextDirection.ltr);
|
||||
tp.layout(maxWidth: size.maxWidth);
|
||||
if (tp.didExceedMaxLines) {
|
||||
return Marquee(
|
||||
text: title,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold, fontSize: 18),
|
||||
scrollAxis: Axis.horizontal,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
blankSpace: 30.0,
|
||||
velocity: 50.0,
|
||||
pauseAfterRound: Duration(seconds: 1),
|
||||
startPadding: 30.0,
|
||||
accelerationDuration: Duration(seconds: 1),
|
||||
accelerationCurve: Curves.linear,
|
||||
decelerationDuration: Duration(milliseconds: 500),
|
||||
decelerationCurve: Curves.easeOut,
|
||||
);
|
||||
} else {
|
||||
return Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold, fontSize: 20),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
Selector<AudioPlayerNotifier, Tuple3<EpisodeBrief, bool, bool>>(
|
||||
selector: (_, audio) => Tuple3(
|
||||
audio.episode, audio.stopOnComplete, audio.startSleepTimer),
|
||||
builder: (_, data, __) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(5.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
height: 30.0,
|
||||
width: 30.0,
|
||||
child: CircleAvatar(
|
||||
backgroundImage:
|
||||
FileImage(File("${data.item1.imagePath}")),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 5.0),
|
||||
width: 150,
|
||||
child: Text(
|
||||
data.item1.feedTitle,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.fade,
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
LastPosition(),
|
||||
IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: () => Navigator.push(
|
||||
context,
|
||||
SlideUptRoute(
|
||||
page: EpisodeDetail(
|
||||
episodeItem: data.item1,
|
||||
heroTag: 'playpanel')),
|
||||
),
|
||||
icon: Icon(Icons.info),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _playlist(BuildContext context) {
|
||||
var audio = Provider.of<AudioPlayerNotifier>(context, listen: false);
|
||||
return Container(
|
||||
|
@ -446,7 +158,7 @@ class _PlayerWidgetState extends State<PlayerWidget> {
|
|||
// color: context.primaryColorDark,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
'Queue',
|
||||
'Playlist',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).accentColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
@ -684,7 +396,7 @@ class _PlayerWidgetState extends State<PlayerWidget> {
|
|||
TabBarView(
|
||||
children: <Widget>[
|
||||
SleepMode(),
|
||||
_controlPanel(context),
|
||||
ControlPanel(),
|
||||
_playlist(context),
|
||||
],
|
||||
),
|
||||
|
@ -1432,3 +1144,434 @@ class SleepModeState extends State<SleepMode>
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ControlPanel extends StatefulWidget {
|
||||
ControlPanel({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_ControlPanelState createState() => _ControlPanelState();
|
||||
}
|
||||
|
||||
class _ControlPanelState extends State<ControlPanel>
|
||||
with SingleTickerProviderStateMixin {
|
||||
final _speedToSelect = [0.5, 0.6, 0.8, 1.0, 1.2, 1.5, 2.0];
|
||||
double _speedSelected;
|
||||
double _setSpeed;
|
||||
AnimationController _controller;
|
||||
Animation<double> _animation;
|
||||
List<BoxShadow> customShadow(double scale) => [
|
||||
BoxShadow(
|
||||
blurRadius: 26 * (1 - scale),
|
||||
offset: Offset(-6, -6) * (1 - scale),
|
||||
color: Colors.white),
|
||||
BoxShadow(
|
||||
blurRadius: 8 * (1 - scale),
|
||||
offset: Offset(2, 2) * (1 - scale),
|
||||
color: Colors.grey[600].withOpacity(0.4))
|
||||
];
|
||||
List<BoxShadow> customShadowNight(double scale) => [
|
||||
BoxShadow(
|
||||
blurRadius: 6 * (1 - scale),
|
||||
offset: Offset(-1, -1) * (1 - scale),
|
||||
color: Colors.grey[100].withOpacity(0.3)),
|
||||
BoxShadow(
|
||||
blurRadius: 8 * (1 - scale),
|
||||
offset: Offset(2, 2) * (1 - scale),
|
||||
color: Colors.black)
|
||||
];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_speedSelected = 0;
|
||||
_setSpeed = 0;
|
||||
_controller =
|
||||
AnimationController(vsync: this, duration: Duration(milliseconds: 400));
|
||||
_animation = Tween<double>(begin: 0.0, end: 1.0).animate(_controller)
|
||||
..addListener(() {
|
||||
setState(() => _setSpeed = _animation.value);
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var audio = Provider.of<AudioPlayerNotifier>(context, listen: false);
|
||||
return Container(
|
||||
color: Theme.of(context).primaryColor,
|
||||
height: 300,
|
||||
padding: EdgeInsets.symmetric(horizontal: 10.0),
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Consumer<AudioPlayerNotifier>(
|
||||
builder: (_, data, __) {
|
||||
Color _c =
|
||||
(Theme.of(context).brightness == Brightness.light)
|
||||
? data.episode.primaryColor.colorizedark()
|
||||
: data.episode.primaryColor.colorizeLight();
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
padding:
|
||||
EdgeInsets.only(top: 10, left: 10, right: 10),
|
||||
child: SliderTheme(
|
||||
data: SliderTheme.of(context).copyWith(
|
||||
activeTrackColor: Theme.of(context).brightness ==
|
||||
Brightness.dark
|
||||
? Colors.black38
|
||||
: Colors.grey[400],
|
||||
inactiveTrackColor:
|
||||
Theme.of(context).primaryColorDark,
|
||||
trackHeight: 20.0,
|
||||
trackShape: MyRectangularTrackShape(),
|
||||
thumbColor: Theme.of(context).accentColor,
|
||||
thumbShape: MyRoundSliderThumpShape(
|
||||
enabledThumbRadius: 10.0,
|
||||
disabledThumbRadius: 10.0,
|
||||
thumbCenterColor: _c),
|
||||
overlayColor:
|
||||
Theme.of(context).accentColor.withAlpha(32),
|
||||
overlayShape:
|
||||
RoundSliderOverlayShape(overlayRadius: 4.0),
|
||||
),
|
||||
child: Slider(
|
||||
value: data.seekSliderValue,
|
||||
onChanged: (double val) {
|
||||
audio.sliderSeek(val);
|
||||
}),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: 20.0,
|
||||
padding: EdgeInsets.symmetric(horizontal: 30.0),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Text(
|
||||
_stringForSeconds(
|
||||
data.backgroundAudioPosition / 1000) ??
|
||||
'',
|
||||
style: TextStyle(fontSize: 10),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
child: data.remoteErrorMessage != null
|
||||
? Text(data.remoteErrorMessage,
|
||||
style: const TextStyle(
|
||||
color: const Color(0xFFFF0000)))
|
||||
: Text(
|
||||
data.audioState ==
|
||||
BasicPlaybackState
|
||||
.buffering ||
|
||||
data.audioState ==
|
||||
BasicPlaybackState
|
||||
.connecting ||
|
||||
data.audioState ==
|
||||
BasicPlaybackState.none
|
||||
? 'Buffring...'
|
||||
: '',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.accentColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
_stringForSeconds(
|
||||
data.backgroundAudioDuration / 1000) ??
|
||||
'',
|
||||
style: TextStyle(fontSize: 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
Container(
|
||||
height: 100,
|
||||
child: Selector<AudioPlayerNotifier, BasicPlaybackState>(
|
||||
selector: (_, audio) => audio.audioState,
|
||||
builder: (_, backplay, __) {
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
padding: EdgeInsets.symmetric(horizontal: 30.0),
|
||||
onPressed:
|
||||
backplay == BasicPlaybackState.playing
|
||||
? () => audio.forwardAudio(-10)
|
||||
: null,
|
||||
iconSize: 32.0,
|
||||
icon: Icon(Icons.replay_10),
|
||||
color: Colors.grey[500]),
|
||||
Container(
|
||||
margin: EdgeInsets.symmetric(horizontal: 30),
|
||||
height: 60,
|
||||
width: 60,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).primaryColor,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: Theme.of(context).brightness ==
|
||||
Brightness.dark
|
||||
? Colors.black12
|
||||
: Colors.white10,
|
||||
width: 1),
|
||||
boxShadow: Theme.of(context).brightness ==
|
||||
Brightness.dark
|
||||
? _customShadowNight
|
||||
: _customShadow),
|
||||
child: backplay == BasicPlaybackState.playing
|
||||
? Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(30)),
|
||||
onTap: backplay ==
|
||||
BasicPlaybackState.playing
|
||||
? () {
|
||||
audio.pauseAduio();
|
||||
}
|
||||
: null,
|
||||
child: SizedBox(
|
||||
height: 60,
|
||||
width: 60,
|
||||
child: Icon(
|
||||
Icons.pause,
|
||||
size: 40,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(30)),
|
||||
onTap: backplay ==
|
||||
BasicPlaybackState.playing
|
||||
? null
|
||||
: () {
|
||||
audio.resumeAudio();
|
||||
},
|
||||
child: SizedBox(
|
||||
height: 60,
|
||||
width: 60,
|
||||
child: Icon(
|
||||
Icons.play_arrow,
|
||||
size: 40,
|
||||
color:
|
||||
Theme.of(context).accentColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
padding: EdgeInsets.symmetric(horizontal: 30.0),
|
||||
onPressed:
|
||||
backplay == BasicPlaybackState.playing
|
||||
? () => audio.forwardAudio(30)
|
||||
: null,
|
||||
iconSize: 32.0,
|
||||
icon: Icon(Icons.forward_30),
|
||||
color: Colors.grey[500]),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: 70.0,
|
||||
padding:
|
||||
EdgeInsets.only(left: 60, right: 60, bottom: 10, top: 10),
|
||||
alignment: Alignment.center,
|
||||
child: Selector<AudioPlayerNotifier, String>(
|
||||
selector: (_, audio) => audio.episode.title,
|
||||
builder: (_, title, __) {
|
||||
return Container(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, size) {
|
||||
var span = TextSpan(
|
||||
text: title,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold, fontSize: 20));
|
||||
var tp = TextPainter(
|
||||
text: span,
|
||||
maxLines: 1,
|
||||
textDirection: TextDirection.ltr);
|
||||
tp.layout(maxWidth: size.maxWidth);
|
||||
if (tp.didExceedMaxLines) {
|
||||
return Marquee(
|
||||
text: title,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold, fontSize: 18),
|
||||
scrollAxis: Axis.horizontal,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
blankSpace: 30.0,
|
||||
velocity: 50.0,
|
||||
pauseAfterRound: Duration(seconds: 1),
|
||||
startPadding: 30.0,
|
||||
accelerationDuration: Duration(seconds: 1),
|
||||
accelerationCurve: Curves.linear,
|
||||
decelerationDuration:
|
||||
Duration(milliseconds: 500),
|
||||
decelerationCurve: Curves.easeOut,
|
||||
);
|
||||
} else {
|
||||
return Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold, fontSize: 20),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
Selector<AudioPlayerNotifier, Tuple3<EpisodeBrief, bool, bool>>(
|
||||
selector: (_, audio) => Tuple3(audio.episode,
|
||||
audio.stopOnComplete, audio.startSleepTimer),
|
||||
builder: (_, data, __) {
|
||||
return Container(
|
||||
padding:
|
||||
EdgeInsets.only(left: 5.0, right: 5.0, bottom: 5.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
InkWell(
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
SlideUptRoute(
|
||||
page: EpisodeDetail(
|
||||
episodeItem: data.item1,
|
||||
heroTag: 'playpanel'))),
|
||||
child: Container(
|
||||
height: 30.0,
|
||||
width: 30.0,
|
||||
child: CircleAvatar(
|
||||
backgroundImage:
|
||||
FileImage(File("${data.item1.imagePath}")),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 5.0),
|
||||
width: 150,
|
||||
child: Text(
|
||||
data.item1.feedTitle,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.fade,
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
LastPosition(),
|
||||
IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: () {
|
||||
if (_setSpeed == 0)
|
||||
_controller.forward();
|
||||
else
|
||||
_controller.reverse();
|
||||
},
|
||||
icon: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: <Widget>[
|
||||
Transform.rotate(
|
||||
angle: math.pi * _setSpeed,
|
||||
child: Text('X')),
|
||||
Selector<AudioPlayerNotifier, double>(
|
||||
selector: (_, audio) =>
|
||||
audio.currentSpeed ?? 1.0,
|
||||
builder: (context, value, child) =>
|
||||
Text(value.toString()),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
]),
|
||||
Positioned(
|
||||
right: 0,
|
||||
bottom: 50.0,
|
||||
child: _setSpeed == 0
|
||||
? Center()
|
||||
: SingleChildScrollView(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: _speedToSelect
|
||||
.map<Widget>((e) => InkWell(
|
||||
onTap: () {
|
||||
if (_setSpeed == 1) {
|
||||
setState(() => _speedSelected = e);
|
||||
audio.setSpeed(e);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
height: 30,
|
||||
width: 30,
|
||||
margin: EdgeInsets.symmetric(horizontal: 5),
|
||||
decoration:
|
||||
e == _speedSelected && _setSpeed > 0
|
||||
? BoxDecoration(
|
||||
color: context.accentColor,
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: context.brightness ==
|
||||
Brightness.light
|
||||
? customShadow(1.0)
|
||||
: customShadowNight(1.0),
|
||||
)
|
||||
: BoxDecoration(
|
||||
color: context.primaryColor,
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: context.brightness ==
|
||||
Brightness.light
|
||||
? customShadow(1 - _setSpeed)
|
||||
: customShadowNight(
|
||||
1 - _setSpeed)),
|
||||
alignment: Alignment.center,
|
||||
child: _setSpeed > 0
|
||||
? Text(e.toString(),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: e == _speedSelected
|
||||
? Colors.white
|
||||
: null))
|
||||
: Center(),
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,17 +9,17 @@ import 'package:tsacdop/class/audiostate.dart';
|
|||
import 'package:tsacdop/util/custompaint.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
import 'package:tsacdop/class/episodebrief.dart';
|
||||
import 'package:tsacdop/class/importompl.dart';
|
||||
import 'package:tsacdop/class/podcast_group.dart';
|
||||
import 'package:tsacdop/class/podcastlocal.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/util/colorize.dart';
|
||||
import 'package:tsacdop/util/context_extension.dart';
|
||||
|
||||
import '../class/episodebrief.dart';
|
||||
import '../class/podcast_group.dart';
|
||||
import '../class/podcastlocal.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../episodes/episodedetail.dart';
|
||||
import '../podcasts/podcastdetail.dart';
|
||||
import '../podcasts/podcastmanage.dart';
|
||||
import '../util/pageroute.dart';
|
||||
import '../util/colorize.dart';
|
||||
import '../util/context_extension.dart';
|
||||
|
||||
class ScrollPodcasts extends StatefulWidget {
|
||||
@override
|
||||
|
@ -284,31 +284,29 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
|
|||
],
|
||||
),
|
||||
),
|
||||
Consumer<ImportOmpl>(
|
||||
builder: (_, ompl, __) => Container(
|
||||
height: (_width - 20) / 3 + 40,
|
||||
margin: EdgeInsets.only(left: 10, right: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
),
|
||||
child: TabBarView(
|
||||
children: groups[_groupIndex]
|
||||
.podcasts
|
||||
.map<Widget>((PodcastLocal podcastLocal) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).brightness ==
|
||||
Brightness.light
|
||||
? Theme.of(context).primaryColor
|
||||
: Colors.black12),
|
||||
margin: EdgeInsets.symmetric(horizontal: 5.0),
|
||||
key: ObjectKey(podcastLocal.title),
|
||||
child: PodcastPreview(
|
||||
podcastLocal: podcastLocal,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
Container(
|
||||
height: (_width - 20) / 3 + 40,
|
||||
margin: EdgeInsets.only(left: 10, right: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
),
|
||||
child: TabBarView(
|
||||
children: groups[_groupIndex]
|
||||
.podcasts
|
||||
.map<Widget>((PodcastLocal podcastLocal) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).brightness ==
|
||||
Brightness.light
|
||||
? Theme.of(context).primaryColor
|
||||
: Colors.black12),
|
||||
margin: EdgeInsets.symmetric(horizontal: 5.0),
|
||||
key: ObjectKey(podcastLocal.title),
|
||||
child: PodcastPreview(
|
||||
podcastLocal: podcastLocal,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -557,12 +555,16 @@ class ShowEpisode extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
Spacer(),
|
||||
Selector<AudioPlayerNotifier, EpisodeBrief>(
|
||||
selector: (_, audio) => audio.episode,
|
||||
Selector<AudioPlayerNotifier,
|
||||
Tuple2<EpisodeBrief, bool>>(
|
||||
selector: (_, audio) => Tuple2(
|
||||
audio.episode, audio.playerRunning),
|
||||
builder: (_, data, __) {
|
||||
return (episodes[index]
|
||||
.enclosureUrl ==
|
||||
data?.enclosureUrl)
|
||||
.enclosureUrl ==
|
||||
data.item1
|
||||
?.enclosureUrl &&
|
||||
data.item2)
|
||||
? Container(
|
||||
height: 20,
|
||||
width: 20,
|
||||
|
@ -624,11 +626,10 @@ class ShowEpisode extends StatelessWidget {
|
|||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
_stringForSeconds(
|
||||
episodes[index]
|
||||
.duration
|
||||
.toDouble())
|
||||
.toString() +
|
||||
'|',
|
||||
episodes[index]
|
||||
.duration
|
||||
.toDouble())
|
||||
.toString(),
|
||||
style: TextStyle(
|
||||
fontSize: _width / 35,
|
||||
// color: _c,
|
||||
|
@ -637,6 +638,20 @@ class ShowEpisode extends StatelessWidget {
|
|||
),
|
||||
)
|
||||
: Center(),
|
||||
episodes[index].duration == 0 ||
|
||||
episodes[index].enclosureLength ==
|
||||
null ||
|
||||
episodes[index].enclosureLength ==
|
||||
0
|
||||
? Center()
|
||||
: Text(
|
||||
'|',
|
||||
style: TextStyle(
|
||||
fontSize: _width / 35,
|
||||
// color: _c,
|
||||
// fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
episodes[index].enclosureLength != null &&
|
||||
episodes[index].enclosureLength !=
|
||||
0
|
||||
|
|
|
@ -162,7 +162,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
|
|||
PlaylistButton(),
|
||||
],
|
||||
),
|
||||
Container(height: 2, color: Theme.of(context).primaryColorDark),
|
||||
Container(height: 2, color: context.primaryColor),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
@ -405,7 +405,7 @@ class _DismissibleContainerState extends State<DismissibleContainer> {
|
|||
widget.episode.duration != 0
|
||||
? _episodeTag(
|
||||
(widget.episode.duration ~/ 60).toString() +
|
||||
'mins',
|
||||
'min',
|
||||
Colors.cyan[300])
|
||||
: Center(),
|
||||
widget.episode.enclosureLength != null
|
||||
|
|
|
@ -28,12 +28,10 @@ class _SlideIntroState extends State<SlideIntro> {
|
|||
color: Colors.grey[600].withOpacity(0.4))
|
||||
];
|
||||
PageController _controller;
|
||||
int _index;
|
||||
double _position;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_index = 0;
|
||||
_position = 0;
|
||||
_controller = PageController()
|
||||
..addListener(() {
|
||||
|
@ -78,7 +76,8 @@ class _SlideIntroState extends State<SlideIntro> {
|
|||
color: Colors.grey[100].withOpacity(0.5),
|
||||
width: MediaQuery.of(context).size.width,
|
||||
// alignment: Alignment.center,
|
||||
padding: EdgeInsets.only(left: 40, right: 20, bottom: 30, top: 20),
|
||||
padding:
|
||||
EdgeInsets.only(left: 40, right: 20, bottom: 30, top: 20),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
|
@ -182,7 +181,7 @@ class _SlideIntroState extends State<SlideIntro> {
|
|||
height: 40,
|
||||
width: 80,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(width:1, color: Colors.white),
|
||||
border: Border.all(width: 1, color: Colors.white),
|
||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||
color: Colors.white,
|
||||
boxShadow: _customShadow,
|
||||
|
@ -194,7 +193,7 @@ class _SlideIntroState extends State<SlideIntro> {
|
|||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(20)),
|
||||
onTap: () => _controller.animateToPage(
|
||||
_index + 1,
|
||||
_position.toInt() + 1,
|
||||
duration: Duration(milliseconds: 200),
|
||||
curve: Curves.bounceIn),
|
||||
child: SizedBox(
|
||||
|
|
|
@ -119,7 +119,6 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future savePodcastLocal(PodcastLocal podcastLocal) async {
|
||||
print('podcast saved in sqllite');
|
||||
int _milliseconds = DateTime.now().millisecondsSinceEpoch;
|
||||
var dbClient = await database;
|
||||
await dbClient.transaction((txn) async {
|
||||
|
@ -163,8 +162,11 @@ class DBHelper {
|
|||
var dbClient = await database;
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
'SELECT background_image, hosts FROM PodcastLocal WHERE id = ?', [id]);
|
||||
List<String> data = [list.first['background_image'], list.first['hosts']];
|
||||
return data;
|
||||
if (list.length > 0) {
|
||||
List<String> data = [list.first['background_image'], list.first['hosts']];
|
||||
return data;
|
||||
}
|
||||
return ['', ''];
|
||||
}
|
||||
|
||||
Future delPodcastLocal(String id) async {
|
||||
|
@ -301,7 +303,7 @@ class DBHelper {
|
|||
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]');
|
||||
RegExp mmDd = RegExp(r'([1-2][0-9]{3}\-[0-1]|\s)[0-9]\-[0-3][0-9]');
|
||||
// RegExp timezone
|
||||
RegExp z = RegExp(r'(\+|\-)[0-1][0-9]00');
|
||||
String timezone = z.stringMatch(pubDate);
|
||||
|
@ -331,8 +333,10 @@ class DBHelper {
|
|||
.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);
|
||||
date = DateFormat('yyyy-MM-dd HH:mm', 'en_US')
|
||||
.parse(month + ' ' + time);
|
||||
print(month);
|
||||
print(date.toString());
|
||||
} else {
|
||||
date = DateTime.now().toUtc();
|
||||
}
|
||||
|
@ -426,65 +430,78 @@ class DBHelper {
|
|||
}
|
||||
|
||||
Future<int> updatePodcastRss(PodcastLocal podcastLocal) async {
|
||||
Response response = await Dio().get(podcastLocal.rssUrl);
|
||||
var feed = RssFeed.parse(response.data);
|
||||
String url, description;
|
||||
feed.items.removeWhere((item) => item == null);
|
||||
int result = feed.items.length;
|
||||
BaseOptions options = new BaseOptions(
|
||||
connectTimeout: 20000,
|
||||
receiveTimeout: 20000,
|
||||
);
|
||||
Response response = await Dio(options).get(podcastLocal.rssUrl);
|
||||
if (response.statusCode == 200) {
|
||||
var feed = RssFeed.parse(response.data);
|
||||
String url, description;
|
||||
feed.items.removeWhere((item) => item == null);
|
||||
int result = feed.items.length;
|
||||
|
||||
var dbClient = await database;
|
||||
int count = Sqflite.firstIntValue(await dbClient.rawQuery(
|
||||
'SELECT COUNT(*) FROM Episodes WHERE feed_id = ?', [podcastLocal.id]));
|
||||
var dbClient = await database;
|
||||
int count = Sqflite.firstIntValue(await dbClient.rawQuery(
|
||||
'SELECT COUNT(*) FROM Episodes WHERE feed_id = ?',
|
||||
[podcastLocal.id]));
|
||||
|
||||
await dbClient.rawUpdate(
|
||||
"UPDATE Episodes SET is_new = 0 WHERE feed_id = ?", [podcastLocal.id]);
|
||||
await dbClient.rawUpdate(
|
||||
"UPDATE Episodes SET is_new = 0 WHERE feed_id = ?",
|
||||
[podcastLocal.id]);
|
||||
|
||||
for (int i = 0; i < result; i++) {
|
||||
print(feed.items[i].title);
|
||||
description = _getDescription(feed.items[i].content.value ?? '',
|
||||
feed.items[i].description ?? '', feed.items[i].itunes.summary ?? '');
|
||||
for (int i = 0; i < result; i++) {
|
||||
print(feed.items[i].title);
|
||||
description = _getDescription(
|
||||
feed.items[i].content.value ?? '',
|
||||
feed.items[i].description ?? '',
|
||||
feed.items[i].itunes.summary ?? '');
|
||||
|
||||
if (feed.items[i].enclosure?.url != null) {
|
||||
_isXimalaya(feed.items[i].enclosure.url)
|
||||
? url = feed.items[i].enclosure.url.split('=').last
|
||||
: url = feed.items[i].enclosure.url;
|
||||
}
|
||||
if (feed.items[i].enclosure?.url != null) {
|
||||
_isXimalaya(feed.items[i].enclosure.url)
|
||||
? url = feed.items[i].enclosure.url.split('=').last
|
||||
: url = feed.items[i].enclosure.url;
|
||||
}
|
||||
|
||||
final title = feed.items[i].itunes.title ?? feed.items[i].title;
|
||||
final length = feed.items[i]?.enclosure?.length ?? 0;
|
||||
final pubDate = feed.items[i].pubDate;
|
||||
final date = _parsePubDate(pubDate);
|
||||
final milliseconds = date.millisecondsSinceEpoch;
|
||||
final duration = feed.items[i].itunes.duration?.inSeconds ?? 0;
|
||||
final explicit = _getExplicit(feed.items[i].itunes.explicit);
|
||||
final title = feed.items[i].itunes.title ?? feed.items[i].title;
|
||||
final length = feed.items[i]?.enclosure?.length ?? 0;
|
||||
final pubDate = feed.items[i].pubDate;
|
||||
final date = _parsePubDate(pubDate);
|
||||
final milliseconds = date.millisecondsSinceEpoch;
|
||||
final duration = feed.items[i].itunes.duration?.inSeconds ?? 0;
|
||||
final explicit = _getExplicit(feed.items[i].itunes.explicit);
|
||||
|
||||
if (url != null) {
|
||||
await dbClient.transaction((txn) async {
|
||||
await txn.rawInsert(
|
||||
"""INSERT OR IGNORE INTO Episodes(title, enclosure_url, enclosure_length, pubDate,
|
||||
if (url != null) {
|
||||
await dbClient.transaction((txn) async {
|
||||
await txn.rawInsert(
|
||||
"""INSERT OR IGNORE INTO Episodes(title, enclosure_url, enclosure_length, pubDate,
|
||||
description, feed_id, milliseconds, duration, explicit, media_id, is_new) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)""",
|
||||
[
|
||||
title,
|
||||
url,
|
||||
length,
|
||||
pubDate,
|
||||
description,
|
||||
podcastLocal.id,
|
||||
milliseconds,
|
||||
duration,
|
||||
explicit,
|
||||
url,
|
||||
]);
|
||||
});
|
||||
[
|
||||
title,
|
||||
url,
|
||||
length,
|
||||
pubDate,
|
||||
description,
|
||||
podcastLocal.id,
|
||||
milliseconds,
|
||||
duration,
|
||||
explicit,
|
||||
url,
|
||||
]);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
int countUpdate = Sqflite.firstIntValue(await dbClient.rawQuery(
|
||||
'SELECT COUNT(*) FROM Episodes WHERE feed_id = ?', [podcastLocal.id]));
|
||||
int countUpdate = Sqflite.firstIntValue(await dbClient.rawQuery(
|
||||
'SELECT COUNT(*) FROM Episodes WHERE feed_id = ?',
|
||||
[podcastLocal.id]));
|
||||
|
||||
await dbClient.rawUpdate(
|
||||
"""UPDATE PodcastLocal SET update_count = ?, episode_count = ? WHERE id = ?""",
|
||||
[countUpdate - count, countUpdate, podcastLocal.id]);
|
||||
return countUpdate - count;
|
||||
await dbClient.rawUpdate(
|
||||
"""UPDATE PodcastLocal SET update_count = ?, episode_count = ? WHERE id = ?""",
|
||||
[countUpdate - count, countUpdate, podcastLocal.id]);
|
||||
return countUpdate - count;
|
||||
} else {
|
||||
throw ("network error");
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<EpisodeBrief>> getRssItem(String id, int i, bool reverse) async {
|
||||
|
|
|
@ -3,13 +3,13 @@ import 'package:provider/provider.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_downloader/flutter_downloader.dart';
|
||||
|
||||
import 'package:tsacdop/class/podcast_group.dart';
|
||||
import 'package:tsacdop/home/appbar/addpodcast.dart';
|
||||
import 'package:tsacdop/class/audiostate.dart';
|
||||
import 'package:tsacdop/class/importompl.dart';
|
||||
import 'package:tsacdop/class/settingstate.dart';
|
||||
import 'package:tsacdop/class/download_state.dart';
|
||||
|
||||
import 'class/podcast_group.dart';
|
||||
import 'home/appbar/addpodcast.dart';
|
||||
import 'class/audiostate.dart';
|
||||
import 'class/settingstate.dart';
|
||||
import 'class/download_state.dart';
|
||||
import 'class/refresh_podcast.dart';
|
||||
import 'class/subscribe_podcast.dart';
|
||||
import 'intro_slider/app_intro.dart';
|
||||
|
||||
final SettingState themeSetting = SettingState();
|
||||
|
@ -23,9 +23,11 @@ Future main() async {
|
|||
ChangeNotifierProvider(create: (_) => themeSetting),
|
||||
ChangeNotifierProvider(create: (_) => AudioPlayerNotifier()),
|
||||
ChangeNotifierProvider(create: (_) => GroupList()),
|
||||
ChangeNotifierProvider(create: (_) => ImportOmpl()),
|
||||
ChangeNotifierProvider(create: (_) => DownloadState(),
|
||||
)
|
||||
ChangeNotifierProvider(create: (_) => SubscribeWorker()),
|
||||
ChangeNotifierProvider(create: (_) => RefreshWorker()),
|
||||
ChangeNotifierProvider(
|
||||
create: (_) => DownloadState(),
|
||||
),
|
||||
],
|
||||
child: MyApp(),
|
||||
),
|
||||
|
@ -72,7 +74,10 @@ class MyApp extends StatelessWidget {
|
|||
darkTheme: ThemeData.dark().copyWith(
|
||||
accentColor: setting.accentSetColor,
|
||||
primaryColorDark: Colors.grey[800],
|
||||
// scaffoldBackgroundColor: Colors.black87,
|
||||
scaffoldBackgroundColor: setting.realDark ? Colors.black87 : null,
|
||||
primaryColor: setting.realDark ? Colors.black : null,
|
||||
popupMenuTheme: PopupMenuThemeData()
|
||||
.copyWith(color: setting.realDark ? Colors.black87 : null),
|
||||
appBarTheme: AppBarTheme(elevation: 0),
|
||||
),
|
||||
home: setting.showIntro ? SlideIntro(goto: Goto.home) : MyHomePage(),
|
||||
|
|
|
@ -38,19 +38,26 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
List<PodcastHost> hosts;
|
||||
Future _updateRssItem(PodcastLocal podcastLocal) async {
|
||||
var dbHelper = DBHelper();
|
||||
final result = await dbHelper.updatePodcastRss(podcastLocal);
|
||||
if (result == 0) {
|
||||
try {
|
||||
final result = await dbHelper.updatePodcastRss(podcastLocal);
|
||||
if (result == 0) {
|
||||
Fluttertoast.showToast(
|
||||
msg: 'No Update',
|
||||
gravity: ToastGravity.TOP,
|
||||
);
|
||||
} else {
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Updated $result Episodes',
|
||||
gravity: ToastGravity.TOP,
|
||||
);
|
||||
Provider.of<GroupList>(context, listen: false)
|
||||
.updatePodcast(podcastLocal.id);
|
||||
}
|
||||
} catch (e) {
|
||||
Fluttertoast.showToast(
|
||||
msg: 'No Update',
|
||||
msg: 'Update failed, network error',
|
||||
gravity: ToastGravity.TOP,
|
||||
);
|
||||
} else {
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Updated $result Episodes',
|
||||
gravity: ToastGravity.TOP,
|
||||
);
|
||||
Provider.of<GroupList>(context, listen: false)
|
||||
.updatePodcast(podcastLocal);
|
||||
}
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
@ -449,11 +456,13 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
value: 0,
|
||||
child: Text('Newest first'),
|
||||
child:
|
||||
Text('Newest first'),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 1,
|
||||
child: Text('Oldest first'),
|
||||
child:
|
||||
Text('Oldest first'),
|
||||
)
|
||||
],
|
||||
onSelected: (value) {
|
||||
|
@ -661,4 +670,3 @@ class _AboutPodcastState extends State<AboutPodcast> {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -301,11 +301,6 @@ class _PodcastCardState extends State<PodcastCard> {
|
|||
Brightness.light
|
||||
? Color.fromRGBO(113, 113, 113, 1)
|
||||
: Color.fromRGBO(15, 15, 15, 1),
|
||||
// statusBarColor:
|
||||
// Theme.of(context).brightness ==
|
||||
// Brightness.light
|
||||
// ? Color.fromRGBO(113, 113, 113, 1)
|
||||
// : Color.fromRGBO(5, 5, 5, 1),
|
||||
),
|
||||
child: AlertDialog(
|
||||
elevation: 1,
|
||||
|
|
|
@ -59,6 +59,28 @@ class Libries extends StatelessWidget {
|
|||
},
|
||||
).toList(),
|
||||
),
|
||||
Container(
|
||||
height: 30.0,
|
||||
padding: EdgeInsets.symmetric(horizontal: 80),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text('Fonts',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyText1
|
||||
.copyWith(color: Theme.of(context).accentColor)),
|
||||
),
|
||||
Column(
|
||||
children: fonts.map<Widget>(
|
||||
(e) {
|
||||
return ListTile(
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 80),
|
||||
onTap: () => _launchUrl(e.link),
|
||||
title: Text(e.name),
|
||||
subtitle: Text(e.license),
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
),
|
||||
Container(
|
||||
height: 30.0,
|
||||
padding: EdgeInsets.symmetric(horizontal: 80),
|
||||
|
|
|
@ -1,50 +1,72 @@
|
|||
const String apacheLicense = "Apache License 2.0";
|
||||
const String apacheLicense = "Apache License 2.0";
|
||||
const String mit = "MIT License";
|
||||
const String bsd ="BSD 3-Clause";
|
||||
const String bsd = "BSD 3-Clause";
|
||||
const String gpl = "GPL 3.0";
|
||||
const String font = "Open Font License";
|
||||
|
||||
class Libries {
|
||||
String name; String license; String link;
|
||||
String name;
|
||||
String license;
|
||||
String link;
|
||||
Libries(this.name, this.license, this.link);
|
||||
}
|
||||
|
||||
List<Libries> google = [
|
||||
Libries('Android X', apacheLicense, 'https://source.android.com/setup/start/licenses'),
|
||||
Libries('Flutter', bsd, 'https://github.com/flutter/flutter/blob/master/LICENSE')
|
||||
Libries('Android X', apacheLicense,
|
||||
'https://source.android.com/setup/start/licenses'),
|
||||
Libries(
|
||||
'Flutter', bsd, 'https://github.com/flutter/flutter/blob/master/LICENSE')
|
||||
];
|
||||
|
||||
List<Libries> fonts = [
|
||||
Libries('Libre Baskerville', font,
|
||||
"https://fonts.google.com/specimen/Libre+Baskerville"),
|
||||
Libries('Teko', font, "https://fonts.google.com/specimen/Teko")
|
||||
];
|
||||
|
||||
List<Libries> plugins = [
|
||||
Libries('webfeed', mit, 'https://pub.dev/packages/webfeed'),
|
||||
Libries('json_annotation',bsd, 'https://pub.dev/packages/json_annotation'),
|
||||
Libries('json_annotation', bsd, 'https://pub.dev/packages/json_annotation'),
|
||||
Libries('sqflite', mit, 'https://pub.dev/packages/sqflite'),
|
||||
Libries('flutter_html', mit, 'https://pub.dev/packages/flutter_html'),
|
||||
Libries('path_provider', bsd, 'https://pub.dev/packages/path_provider'),
|
||||
Libries('color_thief_flutter', mit, 'https://pub.dev/packages/color_thief_flutter'),
|
||||
Libries('color_thief_flutter', mit,
|
||||
'https://pub.dev/packages/color_thief_flutter'),
|
||||
Libries('provider', mit, 'https://pub.dev/packages/provider'),
|
||||
Libries('google_fonts', apacheLicense, 'https://pub.dev/packages/google_fonts'),
|
||||
Libries(
|
||||
'google_fonts', apacheLicense, 'https://pub.dev/packages/google_fonts'),
|
||||
Libries('dio', mit, 'https://pub.dev/packages/dio'),
|
||||
Libries('file_picker', mit, 'https://pub.dev/packages/file_picker'),
|
||||
Libries('xml', mit, 'https://pub.dev/packages/xml'),
|
||||
Libries('marquee', mit, 'https://pub.dev/packages/marquee'),
|
||||
Libries('flutter_downloader', bsd, 'https://pub.dev/packages/flutter_downloader'),
|
||||
Libries('permission_handler', mit, 'https://pub.dev/packages/permission_handler'),
|
||||
Libries(
|
||||
'flutter_downloader', bsd, 'https://pub.dev/packages/flutter_downloader'),
|
||||
Libries(
|
||||
'permission_handler', mit, 'https://pub.dev/packages/permission_handler'),
|
||||
Libries('fluttertoast', mit, 'https://pub.dev/packages/fluttertoast'),
|
||||
Libries('intl', bsd, 'https://pub.dev/packages/intl'),
|
||||
Libries('url_launcher', bsd, 'https://pub.dev/packages/url_launcher'),
|
||||
Libries('image', apacheLicense, 'https://pub.dev/packages/image'),
|
||||
Libries('shared_preferences', bsd, 'https://pub.dev/packages/shared_preferences'),
|
||||
Libries(
|
||||
'shared_preferences', bsd, 'https://pub.dev/packages/shared_preferences'),
|
||||
Libries('uuid', mit, 'https://pub.dev/packages/uuid'),
|
||||
Libries('tuple', bsd, 'https://pub.dev/packages/tuple'),
|
||||
Libries('cached_network_image', mit, 'https://pub.dev/packages/cached_network_image'),
|
||||
Libries('cached_network_image', mit,
|
||||
'https://pub.dev/packages/cached_network_image'),
|
||||
Libries('workmanager', mit, 'https://pub.dev/packages/workmanager'),
|
||||
Libries('flutter_colorpicker', mit, 'https://pub.dev/packages/flutter_colorpicker'),
|
||||
Libries('flutter_colorpicker', mit,
|
||||
'https://pub.dev/packages/flutter_colorpicker'),
|
||||
Libries('app_settings', mit, 'https://pub.dev/packages/app_settings'),
|
||||
Libries('fl_chart', bsd, 'https://pub.dev/packages/fl_chart'),
|
||||
Libries('audio_service', mit, 'https://pub.dev/packages/audio_service'),
|
||||
Libries('just_audio', apacheLicense, 'https://pub.dev/packages/just_audio'),
|
||||
Libries('line_icons', gpl, 'https://pub.dev/packages/line_icons'),
|
||||
Libries('flutter_file_dialog', bsd, 'https://pub.dev/packages/flutter_file_dialog'),
|
||||
Libries('flutter_file_dialog', bsd,
|
||||
'https://pub.dev/packages/flutter_file_dialog'),
|
||||
Libries('flutter_linkify', mit, 'https://pub.dev/packages/flutter_linkify'),
|
||||
Libries('extended_nested_scroll_view', mit, 'https://pub.dev/packages/extended_nested_scroll_view'),
|
||||
Libries('extended_nested_scroll_view', mit,
|
||||
'https://pub.dev/packages/extended_nested_scroll_view'),
|
||||
Libries('connectivity', bsd, 'https://pub.dev/packages/connectivity'),
|
||||
Libries('Rxdart', apacheLicense, 'https://pub.dev/packages/rxdart')
|
||||
];
|
||||
Libries('Rxdart', apacheLicense, 'https://pub.dev/packages/rxdart'),
|
||||
Libries('flutter_isolate', mit, 'https://pub.dev/packages/flutter_isolate'),
|
||||
];
|
||||
|
|
|
@ -33,7 +33,7 @@ class Settings extends StatelessWidget {
|
|||
_exportOmpl() async {
|
||||
var dbHelper = DBHelper();
|
||||
List<PodcastLocal> podcastList = await dbHelper.getPodcastLocalAll();
|
||||
var ompl = omplBuilder(podcastList);
|
||||
var ompl = omplBuilder(podcastList.reversed.toList());
|
||||
var tempdir = await getTemporaryDirectory();
|
||||
var file = File(join(tempdir.path, 'tsacdop_ompl.xml'));
|
||||
print(file.path);
|
||||
|
@ -226,7 +226,8 @@ class Settings extends StatelessWidget {
|
|||
onTap: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => SlideIntro(goto: Goto.settings))),
|
||||
builder: (context) =>
|
||||
SlideIntro(goto: Goto.settings))),
|
||||
contentPadding:
|
||||
EdgeInsets.symmetric(horizontal: 25.0),
|
||||
leading: Icon(LineIcons.columns_solid),
|
||||
|
|
|
@ -62,16 +62,11 @@ class ThemeSetting extends StatelessWidget {
|
|||
AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle(
|
||||
statusBarIconBrightness: Brightness.light,
|
||||
systemNavigationBarColor:
|
||||
Theme.of(context).brightness ==
|
||||
Brightness.light
|
||||
? Color.fromRGBO(113, 113, 113, 1)
|
||||
: Color.fromRGBO(15, 15, 15, 1),
|
||||
// statusBarColor:
|
||||
// Theme.of(context).brightness ==
|
||||
// Brightness.light
|
||||
// ? Color.fromRGBO(113, 113, 113, 1)
|
||||
// : Color.fromRGBO(5, 5, 5, 1),
|
||||
systemNavigationBarColor:
|
||||
Theme.of(context).brightness ==
|
||||
Brightness.light
|
||||
? Color.fromRGBO(113, 113, 113, 1)
|
||||
: Color.fromRGBO(15, 15, 15, 1),
|
||||
),
|
||||
child: AlertDialog(
|
||||
titlePadding: EdgeInsets.only(
|
||||
|
@ -124,6 +119,22 @@ class ThemeSetting extends StatelessWidget {
|
|||
title: Text('Theme'),
|
||||
subtitle: Text('System default'),
|
||||
),
|
||||
ListTile(
|
||||
contentPadding:
|
||||
EdgeInsets.only(left: 80.0, right: 20, bottom: 10),
|
||||
// leading: Icon(Icons.colorize),
|
||||
title: Text('Real Dark'),
|
||||
subtitle: Text(
|
||||
'Turn on if you think the night is not dark enough'),
|
||||
trailing: Selector<SettingState, bool>(
|
||||
selector: (_, setting) => setting.realDark,
|
||||
builder: (_, data, __) => Switch(
|
||||
value: data,
|
||||
onChanged: (boo) async {
|
||||
settings.setRealDark = boo;
|
||||
}),
|
||||
),
|
||||
),
|
||||
Divider(height: 2),
|
||||
ListTile(
|
||||
onTap: () => showGeneralDialog(
|
||||
|
@ -139,16 +150,11 @@ class ThemeSetting extends StatelessWidget {
|
|||
AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle(
|
||||
statusBarIconBrightness: Brightness.light,
|
||||
systemNavigationBarColor:
|
||||
Theme.of(context).brightness ==
|
||||
Brightness.light
|
||||
? Color.fromRGBO(113, 113, 113, 1)
|
||||
: Color.fromRGBO(15, 15, 15, 1),
|
||||
// statusBarColor:
|
||||
// Theme.of(context).brightness ==
|
||||
// Brightness.light
|
||||
// ? Color.fromRGBO(113, 113, 113, 1)
|
||||
// : Color.fromRGBO(5, 5, 5, 1),
|
||||
systemNavigationBarColor:
|
||||
Theme.of(context).brightness ==
|
||||
Brightness.light
|
||||
? Color.fromRGBO(113, 113, 113, 1)
|
||||
: Color.fromRGBO(15, 15, 15, 1),
|
||||
),
|
||||
child: AlertDialog(
|
||||
elevation: 1,
|
||||
|
|
|
@ -217,13 +217,16 @@ class EpisodeGrid extends StatelessWidget {
|
|||
),
|
||||
Spacer(),
|
||||
Selector<AudioPlayerNotifier,
|
||||
EpisodeBrief>(
|
||||
selector: (_, audio) =>
|
||||
Tuple2<EpisodeBrief, bool>>(
|
||||
selector: (_, audio) => Tuple2(
|
||||
audio.episode,
|
||||
audio.playerRunning),
|
||||
builder: (_, data, __) {
|
||||
return (episodes[index]
|
||||
.enclosureUrl ==
|
||||
data?.enclosureUrl)
|
||||
.enclosureUrl ==
|
||||
data.item1
|
||||
?.enclosureUrl &&
|
||||
data.item2)
|
||||
? Container(
|
||||
height: 20,
|
||||
width: 20,
|
||||
|
@ -357,15 +360,31 @@ class EpisodeGrid extends StatelessWidget {
|
|||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
_stringForSeconds(
|
||||
episodes[index]
|
||||
.duration
|
||||
.toDouble()) +
|
||||
'|',
|
||||
episodes[index]
|
||||
.duration
|
||||
.toDouble()),
|
||||
style: TextStyle(
|
||||
fontSize: _width / 35),
|
||||
),
|
||||
)
|
||||
: Center(),
|
||||
episodes[index].duration == 0 ||
|
||||
episodes[index]
|
||||
.enclosureLength ==
|
||||
null ||
|
||||
episodes[index]
|
||||
.enclosureLength ==
|
||||
0 ||
|
||||
layout == Layout.three
|
||||
? Center()
|
||||
: Text(
|
||||
'|',
|
||||
style: TextStyle(
|
||||
fontSize: _width / 35,
|
||||
// color: _c,
|
||||
// fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
layout == Layout.two &&
|
||||
episodes[index]
|
||||
.enclosureLength !=
|
||||
|
|
17
pubspec.yaml
17
pubspec.yaml
|
@ -11,7 +11,7 @@ description: An easy-use podacasts player.
|
|||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 0.1.8
|
||||
version: 0.1.9
|
||||
|
||||
environment:
|
||||
sdk: ">=2.6.0 <3.0.0"
|
||||
|
@ -22,7 +22,7 @@ dependencies:
|
|||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^0.1.2
|
||||
cupertino_icons: ^0.1.3
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
@ -30,12 +30,12 @@ dev_dependencies:
|
|||
json_annotation: ^3.0.1
|
||||
sqflite: ^1.3.0
|
||||
flutter_html: ^0.11.1
|
||||
path_provider: ^1.6.1
|
||||
path_provider: ^1.6.5
|
||||
color_thief_flutter: ^1.0.2
|
||||
provider: ^4.0.5
|
||||
google_fonts: ^0.4.0
|
||||
google_fonts: ^0.5.0+1
|
||||
dio: ^3.0.9
|
||||
file_picker: ^1.6.2
|
||||
file_picker: ^1.6.3+1
|
||||
xml: ^3.5.0
|
||||
marquee: ^1.3.1
|
||||
flutter_downloader: ^1.4.3
|
||||
|
@ -47,12 +47,12 @@ dev_dependencies:
|
|||
shared_preferences: ^0.5.6+1
|
||||
uuid: ^2.0.4
|
||||
tuple: ^1.0.3
|
||||
cached_network_image: ^2.0.0
|
||||
cached_network_image: ^2.1.0+1
|
||||
workmanager: ^0.2.2
|
||||
flutter_colorpicker: ^0.3.2
|
||||
app_settings: ^3.0.1
|
||||
fl_chart: ^0.9.0
|
||||
audio_service: ^0.7.1
|
||||
audio_service: ^0.7.2
|
||||
just_audio:
|
||||
git:
|
||||
url: https://github.com/stonega/just_audio.git
|
||||
|
@ -64,7 +64,8 @@ dev_dependencies:
|
|||
extended_nested_scroll_view: ^0.4.0
|
||||
connectivity: ^0.4.8+2
|
||||
flare_flutter: ^2.0.1
|
||||
rxdart: ^0.23.1
|
||||
rxdart: ^0.24.0
|
||||
flutter_isolate: ^1.0.0+11
|
||||
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
|
|
Loading…
Reference in New Issue