// Copyright 2020 Ben Hills and the project contributors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:pinepods_mobile/core/extensions.dart'; import 'package:pinepods_mobile/entities/funding.dart'; import 'package:pinepods_mobile/entities/person.dart'; import 'package:podcast_search/podcast_search.dart' as search; import 'episode.dart'; enum PodcastEpisodeFilter { none(id: 0), started(id: 1), played(id: 2), notPlayed(id: 3); const PodcastEpisodeFilter({required this.id}); final int id; } enum PodcastEpisodeSort { none(id: 0), latestFirst(id: 1), earliestFirst(id: 2), alphabeticalAscending(id: 3), alphabeticalDescending(id: 4); const PodcastEpisodeSort({required this.id}); final int id; } /// A class that represents an instance of a podcast. /// /// When persisted to disk this represents a podcast that is being followed. class Podcast { /// Database ID int? id; /// Unique identifier for podcast. final String? guid; /// The link to the podcast RSS feed. final String url; /// RSS link URL. final String? link; /// Podcast title. final String title; /// Podcast description. Can be either plain text or HTML. final String? description; /// URL to the full size artwork image. final String? imageUrl; /// URL for thumbnail version of artwork image. Not contained within /// the RSS but may be calculated or provided within search results. final String? thumbImageUrl; /// Copyright owner of the podcast. final String? copyright; /// Zero or more funding links. final List? funding; PodcastEpisodeFilter filter; PodcastEpisodeSort sort; /// Date and time user subscribed to the podcast. DateTime? subscribedDate; /// Date and time podcast was last updated/refreshed. DateTime? _lastUpdated; /// One or more episodes for this podcast. List episodes; final List? persons; bool newEpisodes; bool updatedEpisodes = false; Podcast({ required this.guid, required String url, required this.link, required this.title, this.id, this.description, String? imageUrl, String? thumbImageUrl, this.copyright, this.subscribedDate, this.funding, this.filter = PodcastEpisodeFilter.none, this.sort = PodcastEpisodeSort.none, this.episodes = const [], this.newEpisodes = false, this.persons, DateTime? lastUpdated, }) : url = url.forceHttps, imageUrl = imageUrl?.forceHttps, thumbImageUrl = thumbImageUrl?.forceHttps { _lastUpdated = lastUpdated; } factory Podcast.fromUrl({required String url}) => Podcast( url: url, guid: '', link: '', title: '', description: '', thumbImageUrl: null, imageUrl: null, copyright: '', funding: [], persons: [], ); factory Podcast.fromSearchResultItem(search.Item item) => Podcast( guid: item.guid ?? '', url: item.feedUrl ?? '', link: item.feedUrl, title: item.trackName!, description: '', imageUrl: item.bestArtworkUrl ?? item.artworkUrl, thumbImageUrl: item.thumbnailArtworkUrl, funding: const [], copyright: item.artistName, ); Map toMap() { return { 'guid': guid, 'title': title, 'copyright': copyright ?? '', 'description': description ?? '', 'url': url, 'link': link ?? '', 'imageUrl': imageUrl ?? '', 'thumbImageUrl': thumbImageUrl ?? '', 'subscribedDate': subscribedDate?.millisecondsSinceEpoch.toString() ?? '', 'filter': filter.id, 'sort': sort.id, 'funding': (funding ?? []).map((funding) => funding.toMap()).toList(growable: false), 'person': (persons ?? []).map((persons) => persons.toMap()).toList(growable: false), 'lastUpdated': _lastUpdated?.millisecondsSinceEpoch ?? DateTime.now().millisecondsSinceEpoch, }; } static Podcast fromMap(int key, Map podcast) { final sds = podcast['subscribedDate'] as String?; final lus = podcast['lastUpdated'] as int?; final funding = []; final persons = []; var filter = PodcastEpisodeFilter.none; var sort = PodcastEpisodeSort.none; var sd = DateTime.now(); var lastUpdated = DateTime(1971, 1, 1); if (sds != null && sds.isNotEmpty && int.tryParse(sds) != null) { sd = DateTime.fromMillisecondsSinceEpoch(int.parse(sds)); } if (lus != null) { lastUpdated = DateTime.fromMillisecondsSinceEpoch(lus); } if (podcast['funding'] != null) { for (var chapter in (podcast['funding'] as List)) { if (chapter is Map) { funding.add(Funding.fromMap(chapter)); } } } if (podcast['persons'] != null) { for (var person in (podcast['persons'] as List)) { if (person is Map) { persons.add(Person.fromMap(person)); } } } if (podcast['filter'] != null) { var filterValue = (podcast['filter'] as int); filter = switch (filterValue) { 1 => PodcastEpisodeFilter.started, 2 => PodcastEpisodeFilter.played, 3 => PodcastEpisodeFilter.notPlayed, _ => PodcastEpisodeFilter.none, }; } if (podcast['sort'] != null) { var sortValue = (podcast['sort'] as int); sort = switch (sortValue) { 1 => PodcastEpisodeSort.latestFirst, 2 => PodcastEpisodeSort.earliestFirst, 3 => PodcastEpisodeSort.alphabeticalAscending, 4 => PodcastEpisodeSort.alphabeticalDescending, _ => PodcastEpisodeSort.none, }; } return Podcast( id: key, guid: podcast['guid'] as String, link: podcast['link'] as String?, title: podcast['title'] as String, copyright: podcast['copyright'] as String?, description: podcast['description'] as String?, url: podcast['url'] as String, imageUrl: podcast['imageUrl'] as String?, thumbImageUrl: podcast['thumbImageUrl'] as String?, filter: filter, sort: sort, funding: funding, persons: persons, subscribedDate: sd, lastUpdated: lastUpdated, ); } bool get subscribed => id != null; DateTime get lastUpdated => _lastUpdated ?? DateTime(1970, 1, 1); set lastUpdated(DateTime value) { _lastUpdated = value; } @override bool operator ==(Object other) => identical(this, other) || other is Podcast && runtimeType == other.runtimeType && guid == other.guid && url == other.url; @override int get hashCode => guid.hashCode ^ url.hashCode; }