11import 'dart:io' ;
22
3- import 'package:collection/collection.dart' ;
4- import 'package:file_selector/file_selector.dart' ;
53import 'package:flutter/foundation.dart' ;
64import 'package:m3u_parser_nullsafe/m3u_parser_nullsafe.dart' ;
75import 'package:opml/opml.dart' ;
@@ -36,8 +34,8 @@ class CustomContentModel extends SafeChangeNotifier {
3634
3735 List <({List <Audio > audios, String id})> _playlists = [];
3836 List <({List <Audio > audios, String id})> get playlists => _playlists;
39- void setPlaylists ( List <({ List < Audio > audios, String id})> value) {
40- _playlists = value ;
37+ Future < void > addPlaylists () async {
38+ _playlists = [..._playlists, ... await loadPlaylists ()] ;
4139 notifyListeners ();
4240 }
4341
@@ -46,18 +44,11 @@ class CustomContentModel extends SafeChangeNotifier {
4644 notifyListeners ();
4745 }
4846
49- Future <void > addPlaylists ({List <XFile >? files}) async =>
50- setPlaylists ([..._playlists, ...await loadPlaylists (files: files)]);
51-
52- Future <List <({List <Audio > audios, String id})>> loadPlaylists ({
53- List <XFile >? files,
54- }) async {
47+ Future <List <({List <Audio > audios, String id})>> loadPlaylists () async {
5548 final List <({List <Audio > audios, String id})> lists = [];
5649
5750 try {
58- final paths =
59- files? .map ((e) => e.path) ??
60- await _externalPathService.getPathsOfFiles ();
51+ final paths = await _externalPathService.getPathsOfFiles ();
6152 for (var path in paths) {
6253 if (path.endsWith ('.m3u' )) {
6354 lists.add ((
@@ -131,52 +122,77 @@ class CustomContentModel extends SafeChangeNotifier {
131122 File (join (basePath, '$id .m3u' )).writeAsStringSync (m3uAsString.toString ());
132123 }
133124
134- bool _processing = false ;
135- bool get processing => _processing;
136- void setProcessing (bool value) {
137- if (_processing == value) return ;
138- _processing = value;
139- notifyListeners ();
140- }
141-
142125 Future <void > importPodcastsFromOpmlFile () async {
143- if (_processing) return ;
144- setProcessing (true );
126+ final podcasts =
127+ < ({String artist, String feedUrl, String ? imageUrl, String name})> [];
128+
145129 final path = await _externalPathService.getPathOfFile ();
146130
147131 if (path == null ) {
148- setProcessing (false );
149132 return ;
150133 }
151134 final file = File (path);
152135 if (! file.existsSync ()) return ;
153136 final xml = file.readAsStringSync ();
154137 final doc = OpmlDocument .parse (xml);
155138
156- for (var category in doc.body. where ((e) => e.children != null ) ) {
157- final children = category.children ! . where ((e) => e .xmlUrl != null );
158- final podcasts = < ( String feedUrl, List < Audio > audios) > [];
159- for ( var feed in children) {
160- final audios = await _podcastService. findEpisodes (
161- feedUrl : feed.xmlUrl ! ,
139+ for (var outline in doc.body) {
140+ if (outline .xmlUrl != null ) {
141+ final maybe = await _findPodcast (
142+ outline.xmlUrl ! ,
143+ text : outline.text,
144+ title : outline.title ,
162145 );
163- if (audios.isNotEmpty) {
164- podcasts.add ((feed.xmlUrl! , audios));
146+ if (maybe != null ) {
147+ podcasts.add (maybe);
148+ }
149+ } else {
150+ for (var outlineChild in (outline.children ?? < OpmlOutline > []).where (
151+ (e) => e.xmlUrl != null ,
152+ )) {
153+ final maybe = await _findPodcast (
154+ outlineChild.xmlUrl! ,
155+ text: outlineChild.text,
156+ title: outlineChild.title,
157+ );
158+ if (maybe != null ) {
159+ podcasts.add (maybe);
160+ }
165161 }
166162 }
167- if (podcasts.isNotEmpty) {
168- _libraryService.addPodcasts (podcasts);
169- }
170163 }
171- setProcessing (false );
164+
165+ if (podcasts.isNotEmpty) {
166+ await _libraryService.addPodcasts (podcasts);
167+ }
168+ }
169+
170+ Future <({String artist, String feedUrl, String ? imageUrl, String name})?>
171+ _findPodcast (String feed, {String ? text, String ? title}) async {
172+ if (title != null && text != null ) {
173+ return (feedUrl: feed, artist: text, imageUrl: null , name: title);
174+ }
175+
176+ // Only load the feed if the fields are not provided because this is expensive
177+ final audios = await _podcastService.findEpisodes (feedUrl: feed);
178+ final artist = audios.first.artist ?? '' ;
179+ final imageUrl = audios.first.albumArtUrl ?? audios.first.imageUrl;
180+ final name = audios.first.album ?? '' ;
181+ if (audios.isNotEmpty) {
182+ final value = (
183+ feedUrl: feed,
184+ artist: artist,
185+ imageUrl: imageUrl,
186+ name: name,
187+ );
188+ return value;
189+ }
190+ return null ;
172191 }
173192
174193 Future <bool > exportPodcastsToOpmlFile () async {
175- if (_processing) return false ;
176- setProcessing (true );
177194 final location = await _externalPathService.getPathOfDirectory ();
178195 if (location == null ) {
179- setProcessing (false );
180196 return false ;
181197 }
182198
@@ -188,32 +204,31 @@ class CustomContentModel extends SafeChangeNotifier {
188204 final body = < OpmlOutline > [];
189205 final category = OpmlOutlineBuilder ();
190206
191- for (var podcast in _libraryService.podcasts.entries) {
192- category.addChild (
193- OpmlOutlineBuilder ()
194- .type ('rss' )
195- .title (podcast.value.firstOrNull? .album ?? '' )
196- .text (podcast.value.firstOrNull? .artist ?? '' )
197- .xmlUrl (podcast.key)
198- .build (),
199- );
207+ for (var podcast in _libraryService.podcasts) {
208+ final name = _libraryService.getSubscribedPodcastName (podcast);
209+ final artist = _libraryService.getSubscribedPodcastArtist (podcast);
210+ final builder = OpmlOutlineBuilder ().type ('rss' ).xmlUrl (podcast);
211+ if (name != null ) {
212+ builder.title (name);
213+ }
214+ if (artist != null ) {
215+ builder.text (artist);
216+ }
217+ category.addChild (builder.build ());
200218 }
201219
202220 body.add (category.type ('rss' ).title ('Podcasts' ).text ('Podcasts' ).build ());
203221
204222 final opml = OpmlDocument (head: head, body: body);
205223 final xml = opml.toXmlString (pretty: true );
206224 file.writeAsStringSync (xml);
207- setProcessing ( false );
225+
208226 return true ;
209227 }
210228
211229 Future <bool > exportStarredStationsToOpmlFile () async {
212- if (_processing) return false ;
213- setProcessing (true );
214230 final location = await _externalPathService.getPathOfDirectory ();
215231 if (location == null ) {
216- setProcessing (false );
217232 return false ;
218233 }
219234
@@ -236,17 +251,14 @@ class CustomContentModel extends SafeChangeNotifier {
236251 final opml = OpmlDocument (head: head, body: body);
237252 final xml = opml.toXmlString (pretty: true );
238253 file.writeAsStringSync (xml);
239- setProcessing ( false );
254+
240255 return true ;
241256 }
242257
243258 Future <void > importStarredStationsFromOpmlFile () async {
244- if (_processing) return ;
245- setProcessing (true );
246259 final path = await _externalPathService.getPathOfFile ();
247260
248261 if (path == null ) {
249- setProcessing (false );
250262 return ;
251263 }
252264 final file = File (path);
@@ -264,7 +276,6 @@ class CustomContentModel extends SafeChangeNotifier {
264276 _libraryService.addStarredStations (starredStations);
265277 }
266278 }
267- setProcessing (false );
268279 }
269280
270281 String ? _playlistName;
@@ -278,7 +289,6 @@ class CustomContentModel extends SafeChangeNotifier {
278289 void reset () {
279290 _playlists = [];
280291 _playlistName = null ;
281- _processing = false ;
282292 notifyListeners ();
283293 }
284294}
0 commit comments