2020-10-28 13:10:43 +01:00
|
|
|
import 'dart:core';
|
|
|
|
import 'dart:developer' as developer;
|
|
|
|
import 'dart:io';
|
|
|
|
import 'dart:isolate';
|
|
|
|
import 'dart:math' as math;
|
|
|
|
import 'dart:ui';
|
|
|
|
|
|
|
|
import 'package:color_thief_flutter/color_thief_flutter.dart';
|
|
|
|
import 'package:dio/dio.dart';
|
|
|
|
import 'package:equatable/equatable.dart';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:flutter_isolate/flutter_isolate.dart';
|
|
|
|
import 'package:image/image.dart' as img;
|
|
|
|
import 'package:path_provider/path_provider.dart';
|
|
|
|
import 'package:uuid/uuid.dart';
|
|
|
|
import 'package:webfeed/webfeed.dart';
|
|
|
|
import 'package:workmanager/workmanager.dart';
|
|
|
|
|
|
|
|
import '../local_storage/key_value_storage.dart';
|
|
|
|
import '../local_storage/sqflite_localpodcast.dart';
|
|
|
|
import '../service/gpodder_api.dart';
|
|
|
|
import '../type/fireside_data.dart';
|
|
|
|
import '../type/podcastlocal.dart';
|
|
|
|
|
|
|
|
void callbackDispatcher() {
|
|
|
|
if (Platform.isAndroid) {
|
|
|
|
Workmanager.executeTask((task, inputData) async {
|
|
|
|
final gpodder = Gpodder();
|
|
|
|
final status = await gpodder.getChanges();
|
|
|
|
if (status == 200) {
|
|
|
|
await gpodder.updateChange();
|
|
|
|
developer.log('Gpodder sync successfully');
|
|
|
|
}
|
|
|
|
return Future.value(true);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class GroupEntity {
|
|
|
|
final String name;
|
|
|
|
final String id;
|
|
|
|
final String color;
|
|
|
|
final List<String> podcastList;
|
|
|
|
|
|
|
|
GroupEntity(this.name, this.id, this.color, this.podcastList);
|
|
|
|
|
|
|
|
Map<String, Object> toJson() {
|
|
|
|
return {'name': name, 'id': id, 'color': color, 'podcastList': podcastList};
|
|
|
|
}
|
|
|
|
|
|
|
|
static GroupEntity fromJson(Map<String, Object> json) {
|
|
|
|
var list = List<String>.from(json['podcastList']);
|
|
|
|
return GroupEntity(json['name'] as String, json['id'] as String,
|
|
|
|
json['color'] as String, list);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class PodcastGroup extends Equatable {
|
|
|
|
/// Group name.
|
|
|
|
final String name;
|
|
|
|
|
|
|
|
final String id;
|
|
|
|
|
|
|
|
/// Group theme color, not used.
|
|
|
|
final String color;
|
|
|
|
|
|
|
|
/// Id lists of podcasts in group.
|
2020-12-20 10:35:39 +01:00
|
|
|
final List<String> podcastList;
|
|
|
|
|
|
|
|
final List<PodcastLocal> podcasts;
|
2020-10-28 13:10:43 +01:00
|
|
|
|
|
|
|
PodcastGroup(this.name,
|
2020-12-20 10:35:39 +01:00
|
|
|
{this.color = '#000000',
|
|
|
|
String id,
|
|
|
|
List<String> podcastList,
|
|
|
|
List<PodcastLocal> podcasts})
|
2020-10-28 13:10:43 +01:00
|
|
|
: id = id ?? Uuid().v4(),
|
2020-12-20 10:35:39 +01:00
|
|
|
podcastList = podcastList ?? [],
|
|
|
|
podcasts = podcasts ?? [];
|
|
|
|
|
|
|
|
final _dbHelper = DBHelper();
|
2020-10-28 13:10:43 +01:00
|
|
|
|
|
|
|
Future<void> getPodcasts() async {
|
2020-12-20 10:35:39 +01:00
|
|
|
podcasts.clear();
|
|
|
|
if (podcastList.isNotEmpty) {
|
2020-10-28 13:10:43 +01:00
|
|
|
try {
|
2020-12-20 10:35:39 +01:00
|
|
|
var result = await _dbHelper.getPodcastLocal(podcastList);
|
|
|
|
if (podcasts.isEmpty) podcasts.addAll(result);
|
2020-10-28 13:10:43 +01:00
|
|
|
} catch (e) {
|
|
|
|
await Future.delayed(Duration(milliseconds: 200));
|
|
|
|
try {
|
2020-12-20 10:35:39 +01:00
|
|
|
var result = await _dbHelper.getPodcastLocal(podcastList);
|
|
|
|
if (podcasts.isEmpty) podcasts.addAll(result);
|
2020-10-28 13:10:43 +01:00
|
|
|
} catch (e) {
|
|
|
|
developer.log(e.toString());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-20 10:35:39 +01:00
|
|
|
Future<PodcastGroup> updatePodcast(PodcastLocal podcast) async {
|
|
|
|
var count = await _dbHelper.getPodcastCounts(podcast.id);
|
|
|
|
var list = [
|
|
|
|
for (var p in podcasts)
|
|
|
|
p == podcast ? podcast.copyWith(updateCount: count) : p
|
|
|
|
];
|
|
|
|
return PodcastGroup(name,
|
|
|
|
id: id, color: color, podcastList: podcastList, podcasts: list);
|
|
|
|
}
|
|
|
|
|
|
|
|
void reorderGroup(int oldIndex, int newIndex) {
|
|
|
|
if (newIndex > oldIndex) {
|
|
|
|
newIndex -= 1;
|
|
|
|
}
|
|
|
|
final podcast = podcasts.removeAt(oldIndex);
|
|
|
|
podcasts.insert(newIndex, podcast);
|
|
|
|
podcastList.removeAt(oldIndex);
|
|
|
|
podcastList.insert(newIndex, podcast.id);
|
|
|
|
}
|
|
|
|
|
|
|
|
void addToGroup(PodcastLocal podcast) {
|
|
|
|
if (!podcasts.contains(podcast)) {
|
|
|
|
podcasts.add(podcast);
|
|
|
|
podcastList.add(podcast.id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void addToGroupAt(PodcastLocal podcast, {int index = 0}) {
|
|
|
|
if (!podcasts.contains(podcast)) {
|
|
|
|
podcasts.insert(index, podcast);
|
|
|
|
podcastList.insert(index, podcast.id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void deleteFromGroup(PodcastLocal podcast) {
|
|
|
|
podcasts.remove(podcast);
|
|
|
|
podcastList.remove(podcast.id);
|
|
|
|
}
|
|
|
|
|
2020-10-28 13:10:43 +01:00
|
|
|
Color getColor() {
|
|
|
|
if (color != '#000000') {
|
|
|
|
var colorInt = int.parse('FF${color.toUpperCase()}', radix: 16);
|
|
|
|
return Color(colorInt).withOpacity(1.0);
|
|
|
|
} else {
|
|
|
|
return Colors.blue[400];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
///Ordered podcast list.
|
2020-12-20 10:35:39 +01:00
|
|
|
//List<PodcastLocal> _orderedPodcasts;
|
|
|
|
//List<PodcastLocal> get orderedPodcasts => _orderedPodcasts;
|
2020-10-28 13:10:43 +01:00
|
|
|
|
2020-12-20 10:35:39 +01:00
|
|
|
//set orderedPodcasts(list) => _orderedPodcasts = list;
|
2020-10-28 13:10:43 +01:00
|
|
|
|
|
|
|
GroupEntity toEntity() {
|
|
|
|
return GroupEntity(name, id, color, podcastList);
|
|
|
|
}
|
|
|
|
|
|
|
|
static PodcastGroup fromEntity(GroupEntity entity) {
|
|
|
|
return PodcastGroup(
|
|
|
|
entity.name,
|
|
|
|
id: entity.id,
|
|
|
|
color: entity.color,
|
2020-12-20 10:35:39 +01:00
|
|
|
podcastList: entity.podcastList.toSet().toList(),
|
2020-10-28 13:10:43 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
List<Object> get props => [id, name];
|
|
|
|
}
|
|
|
|
|
|
|
|
enum SubscribeState { none, start, subscribe, fetch, stop, exist, error }
|
|
|
|
|
|
|
|
class SubscribeItem {
|
|
|
|
///Rss url.
|
|
|
|
String url;
|
|
|
|
|
|
|
|
///Rss title.
|
|
|
|
String title;
|
|
|
|
|
|
|
|
/// Subscribe status.
|
|
|
|
SubscribeState subscribeState;
|
|
|
|
|
|
|
|
/// Podcast id.
|
|
|
|
String id;
|
|
|
|
|
|
|
|
///Avatar image link.
|
|
|
|
String imgUrl;
|
|
|
|
|
|
|
|
///Podcast group, default Home.
|
|
|
|
String group;
|
|
|
|
|
|
|
|
SubscribeItem(
|
|
|
|
this.url,
|
|
|
|
this.title, {
|
|
|
|
this.subscribeState = SubscribeState.none,
|
|
|
|
this.id = '',
|
|
|
|
this.imgUrl = '',
|
|
|
|
this.group = '',
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
class GroupList extends ChangeNotifier {
|
|
|
|
/// List of all gourps.
|
2020-12-20 10:35:39 +01:00
|
|
|
List<PodcastGroup> _groups = [];
|
2020-10-28 13:10:43 +01:00
|
|
|
List<PodcastGroup> get groups => _groups;
|
|
|
|
|
2020-12-20 10:35:39 +01:00
|
|
|
List<PodcastGroup> _orderChanged = [];
|
2020-10-28 13:10:43 +01:00
|
|
|
List<PodcastGroup> get orderChanged => _orderChanged;
|
2020-12-20 10:35:39 +01:00
|
|
|
//GroupList({List<PodcastGroup> groups}) : _groups = groups ?? [];
|
2020-10-28 13:10:43 +01:00
|
|
|
|
|
|
|
/// Subscribe worker isolate
|
|
|
|
FlutterIsolate subIsolate;
|
|
|
|
ReceivePort receivePort;
|
|
|
|
SendPort subSendPort;
|
|
|
|
|
|
|
|
/// Current subsribe item from isolate.
|
|
|
|
SubscribeItem _currentSubscribeItem = SubscribeItem('', '');
|
|
|
|
SubscribeItem get currentSubscribeItem => _currentSubscribeItem;
|
|
|
|
|
|
|
|
/// Default false, true if subscribe isolate is created.
|
2020-12-20 10:35:39 +01:00
|
|
|
bool _created = false;
|
2020-10-28 13:10:43 +01:00
|
|
|
bool get created => _created;
|
|
|
|
|
2020-12-20 10:35:39 +01:00
|
|
|
final DBHelper _dbHelper = DBHelper();
|
|
|
|
|
|
|
|
/// Groups save in shared_prefrences.
|
|
|
|
final KeyValueStorage _groupStorage = KeyValueStorage(groupsKey);
|
|
|
|
|
|
|
|
@override
|
|
|
|
void addListener(VoidCallback listener) {
|
|
|
|
if (_groups.isEmpty) {
|
|
|
|
loadGroups().then((value) => super.addListener(listener));
|
|
|
|
gpodderSyncNow();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void dispose() {
|
|
|
|
subIsolate?.kill();
|
|
|
|
subIsolate = null;
|
|
|
|
super.dispose();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Subscribe podcast via isolate.
|
2020-10-28 13:10:43 +01:00
|
|
|
/// Add subsribe item
|
|
|
|
SubscribeItem _subscribeItem;
|
|
|
|
setSubscribeItem(SubscribeItem item, {bool syncGpodder = true}) async {
|
|
|
|
_subscribeItem = item;
|
|
|
|
if (syncGpodder) _syncAdd(item.url);
|
|
|
|
await _start();
|
|
|
|
}
|
|
|
|
|
|
|
|
_setCurrentSubscribeItem(SubscribeItem item) {
|
|
|
|
_currentSubscribeItem = item;
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> _syncAdd(String rssUrl) async {
|
|
|
|
final check = await _checkGpodderLoggedin();
|
|
|
|
if (check) {
|
|
|
|
await _addStorage.addList([rssUrl]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-01 16:42:59 +01:00
|
|
|
Future<void> _start() async {
|
2020-10-28 13:10:43 +01:00
|
|
|
if (!_created) {
|
|
|
|
await _createIsolate();
|
|
|
|
_created = true;
|
|
|
|
listen();
|
|
|
|
} else {
|
|
|
|
subSendPort.send([
|
|
|
|
_subscribeItem.url,
|
|
|
|
_subscribeItem.title,
|
|
|
|
_subscribeItem.imgUrl,
|
|
|
|
_subscribeItem.group,
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> _createIsolate() async {
|
|
|
|
receivePort = ReceivePort();
|
|
|
|
subIsolate =
|
|
|
|
await FlutterIsolate.spawn(subIsolateEntryPoint, receivePort.sendPort);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Isolate listener to get subscrribe status.
|
|
|
|
void listen() {
|
|
|
|
receivePort.distinct().listen((message) {
|
|
|
|
if (message is SendPort) {
|
|
|
|
subSendPort = message;
|
|
|
|
subSendPort.send([
|
|
|
|
_subscribeItem.url,
|
|
|
|
_subscribeItem.title,
|
|
|
|
_subscribeItem.imgUrl,
|
|
|
|
_subscribeItem.group,
|
|
|
|
]);
|
|
|
|
} else if (message is List) {
|
|
|
|
_setCurrentSubscribeItem(SubscribeItem(
|
|
|
|
message[1],
|
|
|
|
message[0],
|
|
|
|
subscribeState: SubscribeState.values[message[2]],
|
|
|
|
));
|
|
|
|
if (message.length == 5) {
|
|
|
|
_subscribeNewPodcast(id: message[3], groupName: message[4]);
|
|
|
|
}
|
|
|
|
} else if (message is String && message == "done") {
|
|
|
|
subIsolate.kill();
|
|
|
|
subIsolate = null;
|
|
|
|
_currentSubscribeItem = SubscribeItem('', '');
|
|
|
|
_created = false;
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
///Set gpodder sync
|
|
|
|
final _loginInfp = KeyValueStorage(gpodderApiKey);
|
|
|
|
final _addStorage = KeyValueStorage(gpodderAddKey);
|
|
|
|
final _removeStorage = KeyValueStorage(gpodderRemoveKey);
|
|
|
|
final _remoteAddStorage = KeyValueStorage(gpodderRemoteAddKey);
|
|
|
|
final _remoteRemoveStorage = KeyValueStorage(gpodderRemoteRemoveKey);
|
|
|
|
|
|
|
|
Future<bool> _checkGpodderLoggedin() async {
|
|
|
|
final loginInfo = await _loginInfp.getStringList();
|
|
|
|
return loginInfo.isNotEmpty;
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> gpodderSyncNow() async {
|
|
|
|
final addList = await _remoteAddStorage.getStringList();
|
|
|
|
final removeList = await _remoteRemoveStorage.getStringList();
|
|
|
|
|
|
|
|
if (removeList.isNotEmpty) {
|
|
|
|
for (var rssLink in removeList) {
|
|
|
|
final exist = await _dbHelper.checkPodcast(rssLink);
|
|
|
|
if (exist != '') {
|
2020-12-20 10:35:39 +01:00
|
|
|
var podcast = await _dbHelper.getPodcastWithUrl(rssLink);
|
|
|
|
await _unsubscribe(podcast);
|
2020-10-28 13:10:43 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
await _remoteAddStorage.clearList();
|
|
|
|
}
|
|
|
|
if (addList.isNotEmpty) {
|
|
|
|
for (var rssLink in addList) {
|
|
|
|
final exist = await _dbHelper.checkPodcast(rssLink);
|
|
|
|
if (exist == '') {
|
|
|
|
var item = SubscribeItem(rssLink, rssLink, group: 'Home');
|
|
|
|
_subscribeItem = item;
|
|
|
|
await _start();
|
|
|
|
|
|
|
|
await Future.delayed(Duration(milliseconds: 200));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
await _remoteRemoveStorage.clearList();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void setWorkManager() {
|
|
|
|
Workmanager.initialize(
|
|
|
|
callbackDispatcher,
|
|
|
|
isInDebugMode: false,
|
|
|
|
);
|
|
|
|
Workmanager.registerPeriodicTask("2", "gpodder_sync",
|
|
|
|
frequency: Duration(hours: 4),
|
|
|
|
initialDelay: Duration(seconds: 10),
|
|
|
|
constraints: Constraints(
|
|
|
|
networkType: NetworkType.connected,
|
|
|
|
));
|
|
|
|
developer.log('work manager init done + (gpodder sync)');
|
|
|
|
}
|
|
|
|
|
|
|
|
Future cancelWork() async {
|
|
|
|
await Workmanager.cancelByUniqueName('2');
|
|
|
|
developer.log('work job cancelled');
|
|
|
|
}
|
|
|
|
|
2020-12-20 10:35:39 +01:00
|
|
|
/// Mange groups states in app.
|
2020-10-28 13:10:43 +01:00
|
|
|
/// Load groups from storage at start.
|
|
|
|
Future<void> loadGroups() async {
|
|
|
|
_groupStorage.getGroups().then((loadgroups) async {
|
|
|
|
_groups.addAll(loadgroups.map(PodcastGroup.fromEntity));
|
|
|
|
for (var group in _groups) {
|
|
|
|
await group.getPodcasts();
|
|
|
|
}
|
2020-12-20 10:35:39 +01:00
|
|
|
_groups = [...groups];
|
2020-10-28 13:10:43 +01:00
|
|
|
notifyListeners();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-12-20 10:35:39 +01:00
|
|
|
void addToOrderChanged(PodcastGroup group) {
|
|
|
|
if (_orderChanged.contains(group)) {
|
|
|
|
_orderChanged = [for (var g in _orderChanged) g == group ? group : g];
|
|
|
|
} else {
|
|
|
|
_orderChanged = [..._orderChanged, group];
|
|
|
|
}
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
|
|
|
|
void drlFromOrderChanged(String name) {
|
|
|
|
_orderChanged = [
|
|
|
|
for (var group in _orderChanged)
|
|
|
|
if (group.name != name) group
|
|
|
|
];
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
|
|
|
|
void clearOrderChanged() {
|
|
|
|
_orderChanged.clear();
|
|
|
|
}
|
|
|
|
|
2020-10-28 13:10:43 +01:00
|
|
|
/// Update podcasts of each group
|
|
|
|
Future<void> updateGroups() async {
|
|
|
|
for (var group in _groups) {
|
|
|
|
await group.getPodcasts();
|
|
|
|
}
|
2020-12-20 10:35:39 +01:00
|
|
|
_groups = [..._groups];
|
2020-10-28 13:10:43 +01:00
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Add new group.
|
|
|
|
Future<void> addGroup(PodcastGroup podcastGroup) async {
|
2020-12-20 10:35:39 +01:00
|
|
|
_groups = [..._groups, podcastGroup];
|
2020-10-28 13:10:43 +01:00
|
|
|
notifyListeners();
|
2020-12-20 10:35:39 +01:00
|
|
|
await _saveGroup();
|
2020-10-28 13:10:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Remove group.
|
|
|
|
Future<void> delGroup(PodcastGroup podcastGroup) async {
|
2020-12-20 10:35:39 +01:00
|
|
|
for (var podcast in podcastGroup.podcasts) {
|
|
|
|
if (!_groups.first.podcasts.contains(podcast)) {
|
|
|
|
_groups.first.addToGroup(podcast);
|
2020-10-28 13:10:43 +01:00
|
|
|
}
|
|
|
|
}
|
2020-12-20 10:35:39 +01:00
|
|
|
_groups = [
|
|
|
|
for (var group in _groups)
|
2021-01-02 09:47:05 +01:00
|
|
|
if (group.id != podcastGroup.id) group
|
2020-12-20 10:35:39 +01:00
|
|
|
];
|
2020-10-28 13:10:43 +01:00
|
|
|
notifyListeners();
|
2020-12-20 10:35:39 +01:00
|
|
|
await _saveGroup();
|
2020-10-28 13:10:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> updateGroup(PodcastGroup podcastGroup) async {
|
2020-12-20 10:35:39 +01:00
|
|
|
_groups = [
|
|
|
|
for (var group in _groups) group == podcastGroup ? podcastGroup : group
|
|
|
|
];
|
2020-10-28 13:10:43 +01:00
|
|
|
notifyListeners();
|
|
|
|
_saveGroup();
|
|
|
|
}
|
|
|
|
|
2020-12-20 10:35:39 +01:00
|
|
|
Future<void> _updateGroups() async {
|
|
|
|
_groups = [..._groups];
|
|
|
|
notifyListeners();
|
|
|
|
await _saveGroup();
|
|
|
|
}
|
|
|
|
|
2020-10-28 13:10:43 +01:00
|
|
|
Future<void> _saveGroup() async {
|
|
|
|
await _groupStorage.saveGroup(_groups.map((it) => it.toEntity()).toList());
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Subscribe podcast from search result.
|
2020-12-20 10:35:39 +01:00
|
|
|
Future subscribe(PodcastLocal podcast) async {
|
|
|
|
await _dbHelper.savePodcastLocal(podcast);
|
|
|
|
_groups.first.addToGroupAt(podcast);
|
|
|
|
_updateGroups();
|
2020-10-28 13:10:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Subscribe podcast from OPML.
|
|
|
|
Future<bool> _subscribeNewPodcast(
|
|
|
|
{String id, String groupName = 'Home'}) async {
|
|
|
|
//List<String> groupNames = _groups.map((e) => e.name).toList();
|
2020-12-20 10:35:39 +01:00
|
|
|
var podcasts = await _dbHelper.getPodcastLocal([id]);
|
2020-10-28 13:10:43 +01:00
|
|
|
for (var group in _groups) {
|
|
|
|
if (group.name == groupName) {
|
|
|
|
if (group.podcastList.contains(id)) {
|
|
|
|
return true;
|
|
|
|
} else {
|
2020-12-20 10:35:39 +01:00
|
|
|
group.addToGroupAt(podcasts.first);
|
|
|
|
_updateGroups();
|
2020-10-28 13:10:43 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-12-20 10:35:39 +01:00
|
|
|
_groups = [
|
|
|
|
..._groups,
|
|
|
|
PodcastGroup(groupName, podcastList: [id], podcasts: podcasts)
|
|
|
|
];
|
2020-10-28 13:10:43 +01:00
|
|
|
notifyListeners();
|
|
|
|
await _saveGroup();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
List<PodcastGroup> getPodcastGroup(String id) {
|
|
|
|
var result = <PodcastGroup>[];
|
|
|
|
for (var group in _groups) {
|
|
|
|
if (group.podcastList.contains(id)) {
|
|
|
|
result.add(group);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Change podcast groups
|
2020-12-20 10:35:39 +01:00
|
|
|
Future<void> changeGroup(
|
|
|
|
PodcastLocal podcast, List<PodcastGroup> list) async {
|
|
|
|
for (var group in getPodcastGroup(podcast.id)) {
|
2020-10-28 13:10:43 +01:00
|
|
|
if (list.contains(group)) {
|
|
|
|
list.remove(group);
|
|
|
|
} else {
|
2020-12-20 10:35:39 +01:00
|
|
|
group.deleteFromGroup(podcast);
|
2020-10-28 13:10:43 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
for (var s in list) {
|
2020-12-20 10:35:39 +01:00
|
|
|
s.addToGroup(podcast);
|
2020-10-28 13:10:43 +01:00
|
|
|
}
|
2020-12-20 10:35:39 +01:00
|
|
|
_updateGroups();
|
2020-10-28 13:10:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Unsubscribe podcast
|
|
|
|
Future<void> _syncRemove(String rssUrl) async {
|
|
|
|
final check = await _checkGpodderLoggedin();
|
|
|
|
if (check) {
|
|
|
|
await _removeStorage.addList([rssUrl]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-20 10:35:39 +01:00
|
|
|
Future<void> _unsubscribe(PodcastLocal podcast) async {
|
2020-10-28 13:10:43 +01:00
|
|
|
for (var group in _groups) {
|
2020-12-20 10:35:39 +01:00
|
|
|
group.deleteFromGroup(podcast);
|
2020-10-28 13:10:43 +01:00
|
|
|
}
|
2020-12-20 10:35:39 +01:00
|
|
|
_updateGroups();
|
|
|
|
await _dbHelper.delPodcastLocal(podcast.id);
|
2020-10-28 13:10:43 +01:00
|
|
|
}
|
2020-12-20 10:35:39 +01:00
|
|
|
|
2020-11-03 18:54:03 +01:00
|
|
|
/// Delete podcsat from device.
|
2020-10-28 13:10:43 +01:00
|
|
|
Future<void> removePodcast(
|
|
|
|
PodcastLocal podcast,
|
|
|
|
) async {
|
|
|
|
_syncRemove(podcast.rssUrl);
|
2020-12-20 10:35:39 +01:00
|
|
|
await _unsubscribe(podcast);
|
2020-11-03 18:54:03 +01:00
|
|
|
await File(podcast.imagePath)?.delete();
|
2020-10-28 13:10:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> saveOrder(PodcastGroup group) async {
|
2020-12-20 10:35:39 +01:00
|
|
|
// group.podcastList = group.orderedPodcasts.map((e) => e.id).toList();
|
|
|
|
var orderedGroup;
|
|
|
|
for (var g in _orderChanged) {
|
|
|
|
if (g == group) orderedGroup = g;
|
|
|
|
}
|
|
|
|
_groups = [for (var g in _groups) g == orderedGroup ? orderedGroup : g];
|
2020-10-28 13:10:43 +01:00
|
|
|
notifyListeners();
|
2020-12-20 10:35:39 +01:00
|
|
|
await _saveGroup();
|
2020-10-28 13:10:43 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> subIsolateEntryPoint(SendPort sendPort) async {
|
|
|
|
var items = <SubscribeItem>[];
|
|
|
|
var _running = false;
|
|
|
|
final listColor = <String>[
|
|
|
|
'388E3C',
|
|
|
|
'1976D2',
|
|
|
|
'D32F2F',
|
|
|
|
'00796B',
|
|
|
|
];
|
|
|
|
var 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);
|
|
|
|
var primaryColor = color.toString();
|
|
|
|
return primaryColor;
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> _subscribe(SubscribeItem item) async {
|
|
|
|
var dbHelper = DBHelper();
|
|
|
|
var rss = item.url;
|
|
|
|
sendPort.send([item.title, item.url, 1]);
|
|
|
|
var options = BaseOptions(
|
|
|
|
connectTimeout: 30000,
|
|
|
|
receiveTimeout: 90000,
|
|
|
|
);
|
|
|
|
|
|
|
|
try {
|
|
|
|
var response = await Dio(options).get(rss);
|
|
|
|
RssFeed p;
|
|
|
|
try {
|
|
|
|
p = RssFeed.parse(response.data);
|
|
|
|
} catch (e) {
|
|
|
|
sendPort.send([item.title, item.url, 6]);
|
|
|
|
await Future.delayed(Duration(seconds: 2));
|
|
|
|
sendPort.send([item.title, item.url, 4]);
|
|
|
|
items.removeWhere((element) => element.url == item.url);
|
|
|
|
if (items.isNotEmpty) {
|
|
|
|
await _subscribe(items.first);
|
|
|
|
} else {
|
|
|
|
sendPort.send("done");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var dir = await getApplicationDocumentsDirectory();
|
|
|
|
|
|
|
|
var realUrl =
|
|
|
|
response.redirects.isEmpty ? rss : response.realUri.toString();
|
|
|
|
|
|
|
|
var checkUrl = await dbHelper.checkPodcast(realUrl);
|
|
|
|
|
|
|
|
/// If url not existe in database.
|
|
|
|
if (checkUrl == '') {
|
|
|
|
img.Image thumbnail;
|
|
|
|
String imageUrl;
|
|
|
|
try {
|
|
|
|
var imageResponse = await Dio().get<List<int>>(p.itunes.image.href,
|
|
|
|
options: Options(
|
|
|
|
responseType: ResponseType.bytes,
|
|
|
|
receiveTimeout: 90000,
|
|
|
|
));
|
|
|
|
imageUrl = p.itunes.image.href;
|
|
|
|
var image = img.decodeImage(imageResponse.data);
|
|
|
|
thumbnail = img.copyResize(image, width: 300);
|
|
|
|
} catch (e) {
|
|
|
|
try {
|
|
|
|
var imageResponse = await Dio().get<List<int>>(item.imgUrl,
|
|
|
|
options: Options(
|
|
|
|
responseType: ResponseType.bytes,
|
|
|
|
receiveTimeout: 90000,
|
|
|
|
));
|
|
|
|
imageUrl = item.imgUrl;
|
|
|
|
var image = img.decodeImage(imageResponse.data);
|
|
|
|
thumbnail = img.copyResize(image, width: 300);
|
|
|
|
} catch (e) {
|
|
|
|
developer.log(e.toString(), name: 'Download image error');
|
|
|
|
try {
|
|
|
|
var index = math.Random().nextInt(3);
|
|
|
|
var imageResponse = await Dio().get<List<int>>(
|
|
|
|
"https://ui-avatars.com/api/?size=300&background="
|
|
|
|
"${listColor[index]}&color=fff&name=${item.title}&length=2&bold=true",
|
|
|
|
options: Options(responseType: ResponseType.bytes));
|
|
|
|
imageUrl = "https://ui-avatars.com/api/?size=300&background="
|
|
|
|
"${listColor[index]}&color=fff&name=${item.title}&length=2&bold=true";
|
|
|
|
thumbnail = img.decodeImage(imageResponse.data);
|
|
|
|
} catch (e) {
|
|
|
|
developer.log(e.toString(), name: 'Donwload image error');
|
|
|
|
sendPort.send([item.title, item.url, 6]);
|
|
|
|
await Future.delayed(Duration(seconds: 2));
|
|
|
|
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");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var uuid = Uuid().v4();
|
2021-01-28 17:32:02 +01:00
|
|
|
File("${dir.path}/$uuid.png")..writeAsBytesSync(img.encodePng(thumbnail));
|
|
|
|
var imagePath = "${dir.path}/$uuid.png";
|
2021-01-17 16:02:30 +01:00
|
|
|
var primaryColor = await _getColor(File(imagePath));
|
2020-10-28 13:10:43 +01:00
|
|
|
var author = p.itunes.author ?? p.author ?? '';
|
|
|
|
var provider = p.generator ?? '';
|
|
|
|
var link = p.link ?? '';
|
2021-01-09 11:09:01 +01:00
|
|
|
var funding = p.podcastFunding.isNotEmpty
|
|
|
|
? [for (var f in p.podcastFunding) f.url]
|
|
|
|
: <String>[];
|
2020-10-28 13:10:43 +01:00
|
|
|
var podcastLocal = PodcastLocal(p.title, imageUrl, realUrl,
|
2021-01-09 11:09:01 +01:00
|
|
|
primaryColor, author, uuid, imagePath, provider, link, funding,
|
2020-10-28 13:10:43 +01:00
|
|
|
description: p.description);
|
|
|
|
|
|
|
|
await dbHelper.savePodcastLocal(podcastLocal);
|
2021-01-28 17:32:02 +01:00
|
|
|
|
2020-10-28 13:10:43 +01:00
|
|
|
sendPort.send([item.title, item.url, 2, uuid, item.group]);
|
2021-01-28 17:32:02 +01:00
|
|
|
|
2020-10-28 13:10:43 +01:00
|
|
|
if (provider.contains('fireside')) {
|
|
|
|
var data = FiresideData(uuid, link);
|
|
|
|
try {
|
|
|
|
await data.fatchData();
|
|
|
|
} catch (e) {
|
|
|
|
developer.log(e.toString(), name: 'Fatch fireside data error');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
await dbHelper.savePodcastRss(p, uuid);
|
|
|
|
|
|
|
|
sendPort.send([item.title, item.url, 3, uuid]);
|
|
|
|
|
|
|
|
await Future.delayed(Duration(seconds: 2));
|
|
|
|
|
|
|
|
sendPort.send([item.title, item.url, 4]);
|
|
|
|
items.removeAt(0);
|
|
|
|
if (items.length > 0) {
|
|
|
|
await _subscribe(items.first);
|
|
|
|
} else {
|
|
|
|
sendPort.send("done");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
sendPort.send([item.title, realUrl, 5, checkUrl, item.group]);
|
|
|
|
await Future.delayed(Duration(seconds: 2));
|
|
|
|
sendPort.send([item.title, item.url, 4]);
|
|
|
|
items.removeAt(0);
|
|
|
|
if (items.length > 0) {
|
|
|
|
await _subscribe(items.first);
|
|
|
|
} else {
|
|
|
|
sendPort.send("done");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
developer.log('$e confirm');
|
|
|
|
sendPort.send([item.title, item.url, 6]);
|
|
|
|
await Future.delayed(Duration(seconds: 2));
|
|
|
|
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<dynamic>) {
|
|
|
|
items.add(SubscribeItem(message[0], message[1],
|
|
|
|
imgUrl: message[2], group: message[3]));
|
|
|
|
if (!_running) {
|
|
|
|
_subscribe(items.first);
|
|
|
|
_running = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|