diff --git a/.blackbox/.gitattributes b/.blackbox/.gitattributes new file mode 100644 index 00000000..96029590 --- /dev/null +++ b/.blackbox/.gitattributes @@ -0,0 +1,2 @@ +blackbox-admins.txt text eol=lf +blackbox-files.txt text eol=lf diff --git a/.blackbox/blackbox-admins.txt b/.blackbox/blackbox-admins.txt new file mode 100644 index 00000000..34c8b6e1 --- /dev/null +++ b/.blackbox/blackbox-admins.txt @@ -0,0 +1 @@ +me@jagandeepbrar.io diff --git a/.blackbox/blackbox-files.txt b/.blackbox/blackbox-files.txt new file mode 100644 index 00000000..b1176143 --- /dev/null +++ b/.blackbox/blackbox-files.txt @@ -0,0 +1,5 @@ +android/app/google-services.json +android/key.jks +android/key.properties +ios/ExportOptions.plist +ios/GoogleService-Info.plist diff --git a/.blackbox/pubring.kbx b/.blackbox/pubring.kbx new file mode 100644 index 00000000..c8cb9177 Binary files /dev/null and b/.blackbox/pubring.kbx differ diff --git a/.blackbox/trustdb.gpg b/.blackbox/trustdb.gpg new file mode 100644 index 00000000..36cb360b Binary files /dev/null and b/.blackbox/trustdb.gpg differ diff --git a/.gitignore b/.gitignore index 36599d0c..607b3459 100644 --- a/.gitignore +++ b/.gitignore @@ -38,19 +38,21 @@ lib/generated_plugin_registrant.dart ios/Flutter/.last_build_id # LunaSea -LunaSea.ipa -LunaSea.aab -LunaSea-armeabi-v7a-release.apk -LunaSea-arm64-v8a-release.apk -LunaSea-x86_64-release.apk -ios/build/ -_changelog.txt - -# Build Configurations -ExportOptions.plist -key.properties - -# Firebase -android/app/google-services.json -ios/GoogleService-Info.plist output/ +*.ipa +*.aab +*.apk +ios/build/ + +# Build Output + + +# Blackbox +/.blackbox/pubring.gpg~ +/.blackbox/pubring.kbx~ +/.blackbox/secring.gpg +/ios/ExportOptions.plist +/ios/GoogleService-Info.plist +/android/key.properties +/android/app/google-services.json +/android/key.jks diff --git a/CHANGELOG.md b/CHANGELOG.md index 74bd826c..fdff6bc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # LunaSea Changelog +## GitHub + +#### MEW +- `[Radarr/Details]` Show the monitor state of the movie in the overview table +- `[Radarr/Manual Import]` Ability to manually import content from the filesystem (quick) + +#### TWEAKS +- `[Everything]` Unified the design of all expandable cards in their expanded state +- `[Radarr]` Sticky primary button actions to the bottom navigation bar area +- `[UI/UX]` Normalize the size of all buttons + +#### FIXES +- `[Dialogs]` Checkbox tiles were padded too aggressively in the X-axis +- `[Networking]` Headers would not get attached in some networking configurations +- `[Radarr/Details]` The overview table would not update when editing a movie +- `[Search]` Indexers would not get restored with backups +- `[Tautulli/Activity]` Activity header included some incorrectly localized strings +- `[Webhooks]` Push the module's home route when receiving malformed data blocks from webhooks + +--- + ## v5.0.0 (50000013) #### NEW diff --git a/android/app/google-services.json.gpg b/android/app/google-services.json.gpg new file mode 100644 index 00000000..f2119018 Binary files /dev/null and b/android/app/google-services.json.gpg differ diff --git a/android/key.jks.gpg b/android/key.jks.gpg new file mode 100644 index 00000000..7789b07e Binary files /dev/null and b/android/key.jks.gpg differ diff --git a/android/key.properties.gpg b/android/key.properties.gpg new file mode 100644 index 00000000..b22bef50 Binary files /dev/null and b/android/key.properties.gpg differ diff --git a/android/key.properties.sample b/android/key.properties.sample index e07ace0a..69bd084d 100644 --- a/android/key.properties.sample +++ b/android/key.properties.sample @@ -1,4 +1,4 @@ storePassword= keyPassword= keyAlias= -storeFile= +storeFile=../key.jks diff --git a/assets/changelog.json b/assets/changelog.json index 512ba84a..9dfc7f98 100644 --- a/assets/changelog.json +++ b/assets/changelog.json @@ -1,130 +1,6 @@ { - "motd": "This build introduces a revamped logging system. While most changes are not user-facing, the logs are now much more versatile and carry more information within the stack traces, which will allow for easier and better debugging of errors.\n\nThis build also introduces Firebase Analytics, which is being used with Firebase Crashlytics to give breadcrumb tracking for crashes. Breadcrumbs let me know how you got to the point of a crash, and do not contain any identifying information. Just as you can disable Firebase Crashlytics, a new option has been added to completely disable Firebase Analytics.\n\nIf you have previously disabled Firebase Crashlytics, you may need to disable Firebase Crashlytics in the settings again.", - "new": [ - { - "module": "-Arr/\nReleases", - "changes": [ - "Match torrent seeder colours to the web GUI" - ] - }, - { - "module": "Firebase/\nAnalytics", - "changes": [ - "Added analytics for breadcrumb tracking in crashes" - ] - }, - { - "module": "Logger", - "changes": [ - "Utilize a new, custom built on-device logging system", - "Compact/delete old log entries once the log count passes 100", - "Exported logs are now in JSON format" - ] - }, - { - "module": "Routing", - "changes": [ - "Protect all module routes by showing a \"Not Enabled\" screen when the module is not enabled" - ] - }, - { - "module": "Settings/\nSystem", - "changes": [ - "Added toggle to disable Firebase Analytics" - ] - }, - { - "module": "Tautulli/\nActivity", - "changes": [ - "Show custom season titles from Plex's new TV agent" - ] - } - ], - "tweaks": [ - { - "module": "Radarr/\nFiles", - "changes": [ - "Add languages to file block", - "Small changes to table titles" - ] - }, - { - "module": "System/\nLogs", - "changes": [ - "Reimplemented logger view", - "Removed viewing the stack trace within the application (still viewable within the exported logs)", - "Added the exception to the expanded table instead of a separate dialog popup" - ] - }, - { - "module": "Tautulli/\nActivity", - "changes": [ - "Minor UI tweaks including showing the full season title and italicizing the episode title" - ] - }, - { - "module": "UI/\nNavbar", - "changes": [ - "Jump instead of animate to page when tapping a navbar item" - ] - } - ], - "fixes": [ - { - "module": "Firebase/\nCrashes", - "changes": [ - "Do not log DioError/networking exceptions" - ] - }, - { - "module": "Flutter", - "changes": [ - "Updated packages", - "Upgrade all Comet.Tools packages to null-safety/NNBD" - ] - }, - { - "module": "Radarr/\nReleases", - "changes": [ - "Negative format scores were not being shown" - ] - }, - { - "module": "Share", - "changes": [ - "The sharesheet could break after the first time it was opened" - ] - }, - { - "module": "Strings", - "changes": [ - "Safe-guard many substring operations" - ] - }, - { - "module": "Tautulli/\nActivity", - "changes": [ - "Play/paused/buffering icon was not properly left aligned to the text" - ] - }, - { - "module": "Tautulli/\nUsers", - "changes": [ - "User images would not be fetched on newer versions of Tautulli" - ] - }, - { - "module": "UI/UX", - "changes": [ - "Modal scroll controller was not being attached to the ListView in a bottom modal sheet", - "Do not log errors when network images fail to load" - ] - }, - { - "module": "URLs", - "changes": [ - "Safe-guard crashing related to launching specific invalid URLs" - ] - } - ] + "motd": "", + "new": [], + "tweaks": [], + "fixes": [] } diff --git a/assets/localization/de.json b/assets/localization/de.json index bf318c30..0920b7e2 100644 --- a/assets/localization/de.json +++ b/assets/localization/de.json @@ -19,6 +19,7 @@ "radarr.All": "Alle", "radarr.Alphabetical": "Alphabetisch", "radarr.Approved": "Genehmigt", + "radarr.Automatic": "Automatisch", "radarr.AvailabilityUnknown": "Verfügbarkeit Unbekannt", "radarr.AvailableIn": "Verfügbar in {}", "radarr.AvailableToday": "Heute Verfügbar", @@ -36,6 +37,9 @@ "radarr.HistoryDescription": "Zeige kürzliche Aktivität", "radarr.InCinemasIn": "In den Kinos in {}", "radarr.InCinemasToday": "Heute in den Kinos", + "radarr.Interactive": "Interaktiv", + "radarr.ManualImport": "Manueller Import", + "radarr.ManualImportDescription": "Importiere Filme vom Dateisystem", "radarr.MinimumAvailability": "Minimale Verfügbarkeit", "radarr.Missing": "Fehlend", "radarr.MonitorMovie": "Überwache Film", @@ -51,10 +55,13 @@ "radarr.NoHistoryFound": "Kein Verlauf gefunden", "radarr.NoMoviesFound": "Keine Filme gefunden", "radarr.NoResultsFound": "Keine Ergebnisse gefunden", + "radarr.NoSubdirectoriesFound": "Keine Unterverzeichnisse gefunden", "radarr.NoSummaryIsAvailable": "Keine Zusammenfassung verfügbar.", + "radarr.ParentDirectory": "Übergeordnetes Verzeichnis", "radarr.QualityProfile": "Qualitätsprofil", "radarr.Queue": "Warteschlange", "radarr.QueueDescription": "Zeige aktiven & eingereihten Inhalt", + "radarr.Quick": "Schnell", "radarr.RefreshMovie": "Film aktualisieren", "radarr.Rejected": "Abgelehnt", "radarr.Released": "Veröffentlicht", @@ -116,12 +123,12 @@ "tautulli.Video": "Video", "tautulli.Users": "Benutzer", "tautulli.User": "Benutzer", - "tautulli.Transcodes": "Transkodierungen", - "tautulli.Transcode": "Transkodieren", + "tautulli.Transcodes": "Umwandlungen", + "tautulli.Transcode": "Umwandeln", "tautulli.Title": "Titel", - "tautulli.Throttled": "Throttled", + "tautulli.Throttled": "Gebremst", "tautulli.TerminationMessage": "Abbruch-Begründung", - "tautulli.TerminationAttachMessage": "Du kannst optional unten eine Abbruch-Begründung angeben.", + "tautulli.TerminationAttachMessage": "Du kannst optional eine Abbruch-Begründung angeben.", "tautulli.Subtitle": "Untertitel", "tautulli.Stream": "Stream", "tautulli.SessionsMany": "{} Sitzungen", diff --git a/assets/localization/en.json b/assets/localization/en.json index 88e2381c..6ca22df2 100644 --- a/assets/localization/en.json +++ b/assets/localization/en.json @@ -19,6 +19,7 @@ "radarr.AddMovieAndSearch": "Add + Search", "radarr.Alphabetical": "Alphabetical", "radarr.Approved": "Approved", + "radarr.Automatic": "Automatic", "radarr.AvailabilityUnknown": "Availability Unknown", "radarr.AvailableIn": "Available in {}", "radarr.AvailableToday": "Available Today", @@ -26,16 +27,21 @@ "radarr.CinemaDateUnknown": "Cinema Date Unknown", "radarr.CutoffUnmet": "Cutoff Unmet", "radarr.DateAdded": "Date Added", + "radarr.DirectoryNotFound": "Directory Not Found", "radarr.Discover": "Discover", "radarr.DownloadFailed": "Download Failed", "radarr.DownloadIgnored": "Download Ignored", "radarr.EditMovie": "Edit Movie", + "radarr.FileBrowser": "File Browser…", "radarr.FilterCatalogue": "Filter Catalogue", "radarr.GrabbedFrom": "Grabbed from {}", "radarr.History": "History", "radarr.HistoryDescription": "View Recent Activity", "radarr.InCinemasIn": "In Cinemas in {}", "radarr.InCinemasToday": "In Cinemas Today", + "radarr.Interactive": "Interactive", + "radarr.ManualImport": "Manual Import", + "radarr.ManualImportDescription": "Import Movies from the Filesystem", "radarr.MinimumAvailability": "Minimum Availability", "radarr.Missing": "Missing", "radarr.Monitored": "Monitored", @@ -48,13 +54,17 @@ "radarr.MovieImported": "Movie Imported ({})", "radarr.MovieNotFound": "Movie Not Found", "radarr.MoviePath": "Movie Path", + "radarr.NoFilesFound": "No Files Found", "radarr.NoHistoryFound": "No History Found", "radarr.NoMoviesFound": "No Movies Found", "radarr.NoResultsFound": "No Results Found", + "radarr.NoSubdirectoriesFound": "No Subdirectories Found", "radarr.NoSummaryIsAvailable": "No summary is available.", + "radarr.ParentDirectory": "Parent Directory", "radarr.QualityProfile": "Quality Profile", "radarr.Queue": "Queue", "radarr.QueueDescription": "View Active & Queued Content", + "radarr.Quick": "Quick", "radarr.RefreshMovie": "Refresh Movie", "radarr.Rejected": "Rejected", "radarr.Released": "Released", diff --git a/assets/localization/fr.json b/assets/localization/fr.json index 0d1fd530..1b5988e4 100644 --- a/assets/localization/fr.json +++ b/assets/localization/fr.json @@ -19,6 +19,7 @@ "radarr.All": "Tout", "radarr.Alphabetical": "Alphabétique", "radarr.Approved": "Approuvé", + "radarr.Automatic": "Automatique", "radarr.AvailabilityUnknown": "Disponibilité inconnue", "radarr.AvailableIn": "Disponible dans {}", "radarr.AvailableToday": "Disponible aujourd'hui", @@ -36,6 +37,9 @@ "radarr.HistoryDescription": "Voir les activités récentes", "radarr.InCinemasIn": "Dans les cinémas en {}", "radarr.InCinemasToday": "Au cinéma aujourd'hui", + "radarr.Interactive": "Interactive", + "radarr.ManualImport": "Importation manuelle", + "radarr.ManualImportDescription": "Importer des films depuis le système de fichiers", "radarr.MinimumAvailability": "Disponibilité minimale", "radarr.Missing": "Manquant", "radarr.MonitorMovie": "Surveiller le film", @@ -51,10 +55,13 @@ "radarr.NoHistoryFound": "Pas d'historique trouvé", "radarr.NoMoviesFound": "Aucun film trouvé", "radarr.NoResultsFound": "Aucun résultat", + "radarr.NoSubdirectoriesFound": "Aucun sous-répertoire trouvé", "radarr.NoSummaryIsAvailable": "Aucun résumé n'est disponible.", + "radarr.ParentDirectory": "Répertoire parent", "radarr.QualityProfile": "Profil de qualité", "radarr.Queue": "File d'attente", "radarr.QueueDescription": "Voir le contenu actif et en attente", + "radarr.Quick": "Rapide", "radarr.RefreshMovie": "Rafraichir le film", "radarr.Rejected": "Rejeté", "radarr.Released": "Sortie", diff --git a/assets/localization/nb-NO.json b/assets/localization/nb-NO.json index 67d36190..71543e01 100644 --- a/assets/localization/nb-NO.json +++ b/assets/localization/nb-NO.json @@ -1,6 +1,10 @@ { "lunasea.AnErrorHasOccurred": "En feil har inntruffet", + "lunasea.Cancel": "Avbryt", + "lunasea.ChangeProfiles": "Endre profiler", + "lunasea.CheckLogsMessage": "Sjekk loggføring for flere detaljer", "lunasea.ComingSoon": "Kommer snart", + "lunasea.Dashboard": "Oversikt", "lunasea.GoBack": "Gå tilbake", "lunasea.ModuleIsNotEnabled": "{} er ikke påskrudd", "lunasea.Refresh": "Gjenoppfrisk", @@ -8,6 +12,7 @@ "lunasea.SearchTextBar": "Søk …", "lunasea.Settings": "Innstillinger", "lunasea.TryAgain": "Prøv igjen", + "lunasea.Unknown": "Ukjent", "radarr.AddMovie": "Legg til film", "radarr.AddMovieAndSearch": "Legg til + søk", "radarr.Age": "Alder", @@ -99,5 +104,73 @@ "search.Category": "Kategori", "search.Alphabetical": "Alfabetisk", "search.AllSubcategories": "Alle underkategorier", - "search.Age": "Alder" + "search.Age": "Alder", + "tautulli.ETA": "Estimert tid til ankomst", + "tautulli.Throttled": "Strupet", + "tautulli.TerminationAttachMessage": "Du kan alternativt legge ved en avsluttingsmelding nedenfor.", + "tautulli.Location": "Sted", + "tautulli.DeletingTemporarySessionsFailed": "Klarte ikke å slette midlertidige økter", + "tautulli.DeletingTemporarySessionsDescription": "Midlertidige økter slettes …", + "tautulli.DeletingTemporarySessions": "Sletter midlertidige økter …", + "tautulli.DeletingImageCacheFailed": "Klarte ikke å slette bildehurtiglager", + "tautulli.DeletingImageCacheDescription": "Tautulli-bildehurtiglageret blir slettet …", + "tautulli.Burn": "Brenn", + "tautulli.BackupDatabase": "Sikkerhetskopier database", + "tautulli.BackupConfiguration": "Sikkhetskopier oppsett", + "tautulli.BackingUpDatabaseDescription": "Sikkerhetskopierer databasen din i bakgrunnen …", + "tautulli.Year": "År", + "tautulli.ViewWebGUI": "Vis nettbasert grensesnitt", + "tautulli.Video": "Video", + "tautulli.Users": "Brukere", + "tautulli.User": "Bruker", + "tautulli.Transcodes": "Transkoder", + "tautulli.Transcode": "Transkod", + "tautulli.Title": "Tittel", + "tautulli.TerminateSessionFailed": "Klarte ikke å avslutte økt", + "tautulli.TerminateSession": "Avslutt økt", + "tautulli.TerminatedSession": "Økt avsluttet", + "tautulli.TerminationMessage": "Avsluttingsmelding", + "tautulli.TerminationConfirmMessage": "Ønsker du å avslutte denne økten?", + "tautulli.Terminate": "Avslutt", + "tautulli.Subtitle": "Undertekst", + "tautulli.Stream": "Strøm", + "tautulli.SessionsMany": "{} økter", + "tautulli.SessionsOne": "Én økt", + "tautulli.Sessions": "Økter", + "tautulli.SessionEnded": "Sesong slutt", + "tautulli.Season": "Sesong {}", + "tautulli.Quality": "Kvalitet", + "tautulli.Product": "Produkt", + "tautulli.Player": "Spiller", + "tautulli.Platform": "Plattform", + "tautulli.NoActiveStreams": "Ingen aktive strømmer", + "tautulli.None": "Ingen", + "tautulli.More": "Mer", + "tautulli.Metadata": "Metadata", + "tautulli.Library": "Bibliotek", + "tautulli.History": "Historikk", + "tautulli.Episode": "Episode {}", + "tautulli.Duration": "Varighet", + "tautulli.DirectStreams": "Direkte strømmer", + "tautulli.DirectStream": "Direkte strøm", + "tautulli.DirectPlays": "Direkte avspillinger", + "tautulli.DirectPlay": "Direkte avspilling", + "tautulli.DeletingImageCache": "Sletter bildehurtiglager …", + "tautulli.DeletingCacheFailed": "Klarte ikke å slette hurtiglager", + "tautulli.DeletingCacheDescription": "Tautulli-hurtiglageret blir slettet …", + "tautulli.DeletingCache": "Sletter hurtiglager …", + "tautulli.DeleteTemporarySessions": "Slett midlertidige økter", + "tautulli.DeleteImageCache": "Slett bildehurtiglager", + "tautulli.DeleteCache": "Slett hurtiglager", + "tautulli.Copy": "Kopier", + "tautulli.Container": "Beholder", + "tautulli.Bandwidth": "Båndbredde", + "tautulli.BackingUpConfigurationFailed": "Klarte ikke å sikkerhetskopiere oppsett", + "tautulli.BackingUpDatabaseFailed": "Klarte ikke å sikkerhetskopiere database", + "tautulli.BackingUpDatabase": "Sikkerhetskopierer database …", + "tautulli.BackingUpConfigurationDescription": "Sikkerhetskopierer oppsetet ditt i bakgrunnen", + "tautulli.BackingUpConfiguration": "Sikkerhetskopierer oppsett …", + "tautulli.Audio": "Lyd", + "tautulli.ActivityDetails": "Aktivitetsdetaljer", + "tautulli.Activity": "Aktivitet" } \ No newline at end of file diff --git a/assets/localization/ru.json b/assets/localization/ru.json index bf1acd0c..6aaeb674 100644 --- a/assets/localization/ru.json +++ b/assets/localization/ru.json @@ -1,5 +1,6 @@ { "lunasea.AnErrorHasOccurred": "Произошла ошибка", + "lunasea.Cancel": "Отмена", "lunasea.ChangeProfiles": "Изменить профили", "lunasea.ComingSoon": "Скоро будет", "lunasea.Dashboard": "Панель управления", @@ -67,5 +68,44 @@ "search.Download": "Скачать", "search.Comments": "Комментарии", "search.Categories": "Категории", - "search.Category": "Категория" + "search.Category": "Категория", + "tautulli.Year": "Год", + "tautulli.Video": "Видео", + "tautulli.Users": "Пользователи", + "tautulli.User": "Пользователь", + "tautulli.Title": "Название", + "tautulli.Subtitle": "Субтитры", + "tautulli.Season": "Сезон {}", + "tautulli.Quality": "Качество", + "tautulli.Product": "Продукт", + "tautulli.Player": "Плеер", + "tautulli.Platform": "Платформа", + "tautulli.None": "Ничего", + "tautulli.More": "Ещё", + "tautulli.Metadata": "Метаданные", + "tautulli.Location": "Местоположение", + "tautulli.Library": "Библиотека", + "tautulli.History": "История", + "tautulli.Episode": "Эпизод {}", + "tautulli.Duration": "Продолжительность", + "tautulli.DeletingTemporarySessionsFailed": "Не удалось удалить временные сеансы", + "tautulli.DeletingTemporarySessionsDescription": "Временные сеансы удаляются", + "tautulli.DeletingImageCache": "Удаление кэша изображения…", + "tautulli.DeletingCacheFailed": "Не удалось удалить кэш", + "tautulli.DeletingCache": "Удаление кэша…", + "tautulli.DeleteImageCache": "Удалить кэш изображения", + "tautulli.DeleteCache": "Удалить кэш", + "tautulli.Copy": "Копировать", + "tautulli.Container": "Контейнер", + "tautulli.Bandwidth": "Пропускная способность", + "tautulli.BackupDatabase": "Резервная база данных", + "tautulli.BackupConfiguration": "Конфигурация резервного копирования", + "tautulli.BackingUpDatabaseFailed": "Не удалось создать резервную копию базы данных", + "tautulli.BackingUpDatabaseDescription": "Резервное копирование базы данных в фоновом режиме", + "tautulli.BackingUpDatabase": "Резервное копирование базы данных…", + "tautulli.BackingUpConfigurationFailed": "Не удалось создать резервную копию конфигурации", + "tautulli.BackingUpConfigurationDescription": "Резервное копирование конфигурации в фоновом режиме", + "tautulli.BackingUpConfiguration": "Резервное копирование конфигурации…", + "tautulli.Audio": "Аудио", + "tautulli.Activity": "Активность" } \ No newline at end of file diff --git a/assets/localization/sv.json b/assets/localization/sv.json index 79e95e15..78047590 100644 --- a/assets/localization/sv.json +++ b/assets/localization/sv.json @@ -1,5 +1,6 @@ { "lunasea.AnErrorHasOccurred": "Ett fel har inträffat", + "lunasea.Cancel": "Avbryt", "lunasea.ChangeProfiles": "Byta Profil", "lunasea.CheckLogsMessage": "Kolla loggen för mera detaljer", "lunasea.ComingSoon": "Kommer Snart", @@ -103,5 +104,73 @@ "search.Comments": "Kommentarer", "search.Categories": "Kategorier", "search.Category": "Kategori", - "search.AllSubcategories": "Alla Underkategorier" + "search.AllSubcategories": "Alla Underkategorier", + "tautulli.Year": "År", + "tautulli.ViewWebGUI": "Visa Web GUI", + "tautulli.Video": "Video", + "tautulli.Users": "Användare", + "tautulli.User": "Användare", + "tautulli.Transcodes": "Omkodningar", + "tautulli.Transcode": "Omkoda", + "tautulli.Title": "Titel", + "tautulli.Throttled": "Strypt", + "tautulli.TerminateSessionFailed": "Det gick inte att Tvångsavsluta Session", + "tautulli.TerminateSession": "Tvångsavsluta Session", + "tautulli.TerminatedSession": "Tvångsavslutad Session", + "tautulli.TerminationMessage": "Tvångsavslutnings Meddelande", + "tautulli.TerminationConfirmMessage": "Vill du tvångsavsluta denna sessionen?", + "tautulli.TerminationAttachMessage": "Du kan skriva in ett tvångsavslutnings meddelande nedan.", + "tautulli.Terminate": "Tvångsavsluta", + "tautulli.Subtitle": "Undertext", + "tautulli.Stream": "Ström", + "tautulli.SessionsMany": "{} Sessioner", + "tautulli.SessionsOne": "1 Session", + "tautulli.Sessions": "Sessioner", + "tautulli.SessionEnded": "Session Stoppad", + "tautulli.Season": "Säsong {}", + "tautulli.Quality": "Kvalitet", + "tautulli.Product": "Produkt", + "tautulli.Player": "Spelare", + "tautulli.Platform": "Platform", + "tautulli.NoActiveStreams": "Inga Aktiva Strömmar", + "tautulli.None": "Inget", + "tautulli.More": "Mer", + "tautulli.Metadata": "Metadata", + "tautulli.Location": "Plats", + "tautulli.Library": "Bibliotek", + "tautulli.History": "Historik", + "tautulli.ETA": "ETA", + "tautulli.Episode": "Episod {}", + "tautulli.Duration": "Speltid", + "tautulli.DirectStreams": "Direktströmmar", + "tautulli.DirectStream": "Direktström", + "tautulli.DirectPlays": "Direktuppspelningar", + "tautulli.DirectPlay": "Direktuppspelning", + "tautulli.DeletingTemporarySessionsFailed": "Ett fel uppstod vid Radering av Temporära Sessioner", + "tautulli.DeletingTemporarySessionsDescription": "Temporära sessioner raderas", + "tautulli.DeletingTemporarySessions": "Raderar Temporära Sessioner…", + "tautulli.DeletingImageCacheFailed": "Ett fel uppstod vid Radering av Cache", + "tautulli.DeletingImageCacheDescription": "Tautulli bild cache raderas", + "tautulli.DeletingImageCache": "Raderar Bild Cache…", + "tautulli.DeletingCacheFailed": "Ett fel uppstod vid Radering av Cache", + "tautulli.DeletingCacheDescription": "Tautulli cache raderas", + "tautulli.DeletingCache": "Raderar Cache…", + "tautulli.DeleteTemporarySessions": "Radera Temporära Sessioner", + "tautulli.DeleteImageCache": "Radera Bild Cache", + "tautulli.DeleteCache": "Radera Cache", + "tautulli.Copy": "Kopiera", + "tautulli.Container": "Behållare", + "tautulli.Burn": "Bränn", + "tautulli.Bandwidth": "Bandbredd", + "tautulli.BackupDatabase": "Säkerhetskopiera Databas", + "tautulli.BackupConfiguration": "Säkerhetskopiera Konfiguration", + "tautulli.BackingUpDatabaseFailed": "Ett fel uppstod vid Säkerhetskopiering av Databasen", + "tautulli.BackingUpDatabaseDescription": "Säkerhetskopierar din konfiguration i bakgrunden", + "tautulli.BackingUpDatabase": "Säkerhetskopierar Databasen…", + "tautulli.BackingUpConfigurationFailed": "Ett fel uppstod vid Säkerhetskopiering", + "tautulli.BackingUpConfigurationDescription": "Säkerhetskopierar din konfiguration i bakgrunden", + "tautulli.BackingUpConfiguration": "Säkerhetskopiera Konfiguration…", + "tautulli.Audio": "Ljud", + "tautulli.ActivityDetails": "Aktivitetsdetaljer", + "tautulli.Activity": "Aktivitet" } \ No newline at end of file diff --git a/ios/ExportOptions.plist.gpg b/ios/ExportOptions.plist.gpg new file mode 100644 index 00000000..9560f76a Binary files /dev/null and b/ios/ExportOptions.plist.gpg differ diff --git a/ios/GoogleService-Info.plist.gpg b/ios/GoogleService-Info.plist.gpg new file mode 100644 index 00000000..18602dc4 Binary files /dev/null and b/ios/GoogleService-Info.plist.gpg differ diff --git a/ios/Podfile b/ios/Podfile index 6b92b118..211e314f 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -28,9 +28,10 @@ require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelpe flutter_ios_podfile_setup target 'Runner' do + pod 'GoogleUtilities' + pod 'FirebaseFirestore', :git => 'https://github.com/invertase/firestore-ios-sdk-frameworks.git', :tag => '7.3.0' use_frameworks! use_modular_headers! - pod 'FirebaseFirestore', :git => 'https://github.com/invertase/firestore-ios-sdk-frameworks.git', :tag => '7.3.0' flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end @@ -53,6 +54,7 @@ post_install do |installer| end target 'ImageNotification' do + pod 'GoogleUtilities' use_frameworks! pod 'Firebase/Messaging' end diff --git a/ios/Podfile.lock b/ios/Podfile.lock index f86ca006..ab6379d1 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -114,12 +114,12 @@ PODS: - nanopb (~> 2.30906.0) - PromisesObjC (~> 1.2) - FirebaseFirestore (7.3.0) - - FirebaseInstallations (7.7.0): + - FirebaseInstallations (7.8.0): - FirebaseCore (~> 7.0) - GoogleUtilities/Environment (~> 7.0) - GoogleUtilities/UserDefaults (~> 7.0) - PromisesObjC (~> 1.2) - - FirebaseInstanceID (7.7.0): + - FirebaseInstanceID (7.8.0): - FirebaseCore (~> 7.0) - FirebaseInstallations (~> 7.0) - GoogleUtilities/Environment (~> 7.0) @@ -143,24 +143,38 @@ PODS: - nanopb (~> 2.30906.0) - GoogleDataTransport (8.1.0): - nanopb (~> 2.30906.0) - - GoogleUtilities/AppDelegateSwizzler (7.2.2): + - GoogleUtilities (7.3.1): + - GoogleUtilities/AppDelegateSwizzler (= 7.3.1) + - GoogleUtilities/Environment (= 7.3.1) + - GoogleUtilities/ISASwizzler (= 7.3.1) + - GoogleUtilities/Logger (= 7.3.1) + - GoogleUtilities/MethodSwizzler (= 7.3.1) + - GoogleUtilities/Network (= 7.3.1) + - "GoogleUtilities/NSData+zlib (= 7.3.1)" + - GoogleUtilities/Reachability (= 7.3.1) + - GoogleUtilities/SwizzlerTestHelpers (= 7.3.1) + - GoogleUtilities/UserDefaults (= 7.3.1) + - GoogleUtilities/AppDelegateSwizzler (7.3.1): - GoogleUtilities/Environment - GoogleUtilities/Logger - GoogleUtilities/Network - - GoogleUtilities/Environment (7.2.2): + - GoogleUtilities/Environment (7.3.1): - PromisesObjC (~> 1.2) - - GoogleUtilities/Logger (7.2.2): + - GoogleUtilities/ISASwizzler (7.3.1) + - GoogleUtilities/Logger (7.3.1): - GoogleUtilities/Environment - - GoogleUtilities/MethodSwizzler (7.2.2): + - GoogleUtilities/MethodSwizzler (7.3.1): - GoogleUtilities/Logger - - GoogleUtilities/Network (7.2.2): + - GoogleUtilities/Network (7.3.1): - GoogleUtilities/Logger - "GoogleUtilities/NSData+zlib" - GoogleUtilities/Reachability - - "GoogleUtilities/NSData+zlib (7.2.2)" - - GoogleUtilities/Reachability (7.2.2): + - "GoogleUtilities/NSData+zlib (7.3.1)" + - GoogleUtilities/Reachability (7.3.1): - GoogleUtilities/Logger - - GoogleUtilities/UserDefaults (7.2.2): + - GoogleUtilities/SwizzlerTestHelpers (7.3.1): + - GoogleUtilities/MethodSwizzler + - GoogleUtilities/UserDefaults (7.3.1): - GoogleUtilities/Logger - GTMSessionFetcher/Core (1.5.0) - in_app_purchase (0.0.1): @@ -204,6 +218,7 @@ DEPENDENCIES: - firebase_storage (from `.symlinks/plugins/firebase_storage/ios`) - FirebaseFirestore (from `https://github.com/invertase/firestore-ios-sdk-frameworks.git`, tag `7.3.0`) - Flutter (from `Flutter`) + - GoogleUtilities - in_app_purchase (from `.symlinks/plugins/in_app_purchase/ios`) - network_info_plus (from `.symlinks/plugins/network_info_plus/ios`) - package_info (from `.symlinks/plugins/package_info/ios`) @@ -302,14 +317,14 @@ SPEC CHECKSUMS: FirebaseCoreDiagnostics: d50e11039e5984d92c8a512be2395f13df747350 FirebaseCrashlytics: d31325312c92e2cb2f0386d589b9aa44e303d99b FirebaseFirestore: 46e32f479aac127e742b1cf873930f7bb43e7b45 - FirebaseInstallations: 42c86e7b02ff75b7f27f85833bf5dcb5f38a9774 - FirebaseInstanceID: cf940324a20ac14a27ad1e931d15ac9d335526db + FirebaseInstallations: 7f7ed0e7e27fb51f57291e1876e2ddb1524126c1 + FirebaseInstanceID: aaecc93b4528bbcafea12c477e26827719ca1183 FirebaseMessaging: 68d1bcb14880189558a8ae57167abe0b7e417232 FirebaseStorage: 5002b1895bfe74a5ce92ad54f966e6162d0da2e5 Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c GoogleAppMeasurement: 8d3c0aeede16ab7764144b5a4ca8e1d4323841b7 GoogleDataTransport: 116c84c4bdeb76be2a7a46de51244368f9794eab - GoogleUtilities: 31c5b01f978a70c6cff2afc6272b3f1921614b43 + GoogleUtilities: e1d9ed4e544fc32a93e00e721400cbc3f377200d GTMSessionFetcher: b3503b20a988c4e20cc189aa798fd18220133f52 in_app_purchase: 3e2155afa9d03d4fa32d9e62d567885080ce97d6 nanopb: 1bf24dd71191072e120b83dd02d08f3da0d65e53 @@ -325,6 +340,6 @@ SPEC CHECKSUMS: SwiftyGif: 5d4af95df24caf1c570dbbcb32a3b8a0763bc6d7 url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef -PODFILE CHECKSUM: d7f0a8efbe8010ddad9d00678781441d461a1402 +PODFILE CHECKSUM: fca408160c687ba2afb8bca214366f046bdc948c COCOAPODS: 1.10.1 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 23694519..0fad1d61 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -442,7 +442,7 @@ "${BUILT_PRODUCTS_DIR}/FirebaseStorage/FirebaseStorage.framework", "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework", "${BUILT_PRODUCTS_DIR}/GoogleDataTransport/GoogleDataTransport.framework", - "${BUILT_PRODUCTS_DIR}/GoogleUtilities-00567490/GoogleUtilities.framework", + "${BUILT_PRODUCTS_DIR}/GoogleUtilities/GoogleUtilities.framework", "${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework", "${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework", "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework", @@ -824,7 +824,7 @@ ENABLE_BITCODE = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ImageNotification/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.4; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -856,7 +856,7 @@ ENABLE_BITCODE = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ImageNotification/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.4; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -887,7 +887,7 @@ ENABLE_BITCODE = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ImageNotification/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.4; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/lib/core/database/luna_database.dart b/lib/core/database/luna_database.dart index e79adc2f..5be93982 100644 --- a/lib/core/database/luna_database.dart +++ b/lib/core/database/luna_database.dart @@ -9,6 +9,7 @@ class LunaDatabase extends LunaModuleDatabase { Hive.registerAdapter(LunaLogHiveObjectAdapter()); Hive.registerAdapter(LunaLogTypeAdapter()); Hive.registerAdapter(LunaBrowserAdapter()); + Hive.registerAdapter(LunaIndexerIconAdapter()); } @override diff --git a/lib/core/ui.dart b/lib/core/ui.dart index 786cd684..f84f7a75 100644 --- a/lib/core/ui.dart +++ b/lib/core/ui.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; export 'ui/appbar.dart'; +export 'ui/bottom_bar.dart'; export 'ui/bottom_modal_sheet.dart'; -export 'ui/bottom_navigation_bar.dart'; export 'ui/button.dart'; export 'ui/card.dart'; export 'ui/colors.dart'; diff --git a/lib/core/ui/bottom_bar.dart b/lib/core/ui/bottom_bar.dart new file mode 100644 index 00000000..55f0ab33 --- /dev/null +++ b/lib/core/ui/bottom_bar.dart @@ -0,0 +1,3 @@ +export 'bottom_bar/badge.dart'; +export 'bottom_bar/action_bar.dart'; +export 'bottom_bar/navigation_bar.dart'; diff --git a/lib/core/ui/bottom_bar/action_bar.dart b/lib/core/ui/bottom_bar/action_bar.dart new file mode 100644 index 00000000..57a67105 --- /dev/null +++ b/lib/core/ui/bottom_bar/action_bar.dart @@ -0,0 +1,45 @@ + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; + +/// Create a [LunaBottomActionBar] that contains button actions. +/// +/// The children are expected to be [LunaButton]s or children of [LunaButton]. +class LunaBottomActionBar extends StatelessWidget { + final EdgeInsets padding; + final List actions; + final bool useSafeArea; + + LunaBottomActionBar({ + @required this.actions, + this.padding = const EdgeInsets.symmetric(horizontal: 6.0, vertical: 8.0), + this.useSafeArea = true, + Key key, + }) : super(key: key) { + assert(actions != null && actions.length != 0); + } + + @override + Widget build(BuildContext context) { + return Container( + child: SafeArea( + top: useSafeArea, + bottom: useSafeArea, + left: useSafeArea, + right: useSafeArea, + child: Padding( + child: LunaButtonContainer( + children: actions, + padding: EdgeInsets.zero, + ), + padding: padding, + ), + ), + decoration: BoxDecoration( + color: Theme.of(context).primaryColor, + ), + ); + } + +} \ No newline at end of file diff --git a/lib/core/ui/bottom_navigation_bar/badge.dart b/lib/core/ui/bottom_bar/badge.dart similarity index 100% rename from lib/core/ui/bottom_navigation_bar/badge.dart rename to lib/core/ui/bottom_bar/badge.dart diff --git a/lib/core/ui/bottom_bar/navigation_bar.dart b/lib/core/ui/bottom_bar/navigation_bar.dart new file mode 100644 index 00000000..6209d544 --- /dev/null +++ b/lib/core/ui/bottom_bar/navigation_bar.dart @@ -0,0 +1,112 @@ +import 'package:google_nav_bar/google_nav_bar.dart'; +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; + +class LunaBottomNavigationBar extends StatefulWidget { + final PageController pageController; + final List icons; + final List titles; + final List scrollControllers; + final List topActions; + final Function(int) onTabChange; + final List leadingOnTab; + + LunaBottomNavigationBar({ + Key key, + @required this.pageController, + @required this.icons, + @required this.titles, + this.topActions, + this.onTabChange, + this.leadingOnTab, + this.scrollControllers, + }) : super(key: key) { + assert(icons.length == titles.length, 'An unequal amount of titles and icons were passed to LunaNavigationBar.'); + if(leadingOnTab != null) assert(icons.length == leadingOnTab.length, 'An unequal amount of icons and leadingOnTab were passed to LunaNavigationBar.'); + if(scrollControllers != null) assert(icons.length == scrollControllers.length, 'An unequal amount of icons and scrollControllers were passed to LunaNavigationBar.'); + } + + @override + State createState() => _State(); +} + +class _State extends State { + int _index; + + @override + void initState() { + _index = widget.pageController?.initialPage ?? 0; + widget.pageController?.addListener(_pageControllerListener); + super.initState(); + } + + @override + void dispose() { + widget.pageController?.removeListener(_pageControllerListener); + super.dispose(); + } + + void _pageControllerListener() { + if((widget.pageController?.page?.round() ?? _index) == _index) return; + setState(() => _index = widget.pageController.page.round()); + } + + @override + Widget build(BuildContext context) { + return Container( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if(widget.topActions != null && widget.topActions.length != 0) LunaBottomActionBar( + actions: widget.topActions, + padding: EdgeInsets.fromLTRB(6.0, 8.0, 6.0, 0.0), + useSafeArea: false, + ), + SafeArea( + child: Padding( + child: GNav( + gap: 8.0, + iconSize: 24.0, + padding: EdgeInsets.fromLTRB(18.0, 10.0, 12.0, 10.0), + duration: Duration(milliseconds: LunaUI.ANIMATION_SPEED), + tabBackgroundColor: Theme.of(context).canvasColor, + activeColor: LunaColours.accent, + tabs: List.generate(widget.icons.length, (index) => GButton( + icon: widget.icons[index], + text: widget.titles[index], + iconSize: 22.0, + haptic: true, + iconColor: Colors.white, + textStyle: TextStyle( + fontWeight: LunaUI.FONT_WEIGHT_BOLD, + fontSize: LunaUI.FONT_SIZE_NAVIGATION_BAR, + color: LunaColours.accent, + ), + leading: widget.leadingOnTab == null ? null : widget.leadingOnTab[index], + )).toList(), + tabActiveBorder: LunaUI.shouldUseBorder ? Border.all(color: Colors.white12) : null, + tabBorder: LunaUI.shouldUseBorder ? Border.all(color: Colors.transparent) : null, + selectedIndex: _index, + onTabChange: (index) { + _onTabChange(index); + if(widget.onTabChange != null) widget.onTabChange(index); + }, + ), + padding: EdgeInsets.all(12.0), + ), + top: false, + ), + ], + ), + decoration: BoxDecoration(color: Theme.of(context).primaryColor), + ); + } + + void _onTabChange(int index) { + if(index == _index) { + if(widget.scrollControllers != null && widget.scrollControllers[index] != null) widget.scrollControllers[index].lunaAnimateToStart(); + } else { + widget.pageController.lunaJumpToPage(index); + } + } +} diff --git a/lib/core/ui/bottom_navigation_bar.dart b/lib/core/ui/bottom_navigation_bar.dart deleted file mode 100644 index aca69743..00000000 --- a/lib/core/ui/bottom_navigation_bar.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'bottom_navigation_bar/badge.dart'; -export 'bottom_navigation_bar/bottom_navigation_bar.dart'; diff --git a/lib/core/ui/bottom_navigation_bar/bottom_navigation_bar.dart b/lib/core/ui/bottom_navigation_bar/bottom_navigation_bar.dart deleted file mode 100644 index cfc46f04..00000000 --- a/lib/core/ui/bottom_navigation_bar/bottom_navigation_bar.dart +++ /dev/null @@ -1,99 +0,0 @@ -import 'package:google_nav_bar/google_nav_bar.dart'; -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; - -class LunaBottomNavigationBar extends StatefulWidget { - final PageController pageController; - final List icons; - final List titles; - final List scrollControllers; - final Function(int) onTabChange; - final List leadingOnTab; - - LunaBottomNavigationBar({ - Key key, - @required this.pageController, - @required this.icons, - @required this.titles, - this.onTabChange, - this.leadingOnTab, - this.scrollControllers, - }) : super(key: key) { - assert(icons.length == titles.length, 'An unequal amount of titles and icons were passed to LunaNavigationBar.'); - if(leadingOnTab != null) assert(icons.length == leadingOnTab.length, 'An unequal amount of icons and leadingOnTab were passed to LunaNavigationBar.'); - if(scrollControllers != null) assert(icons.length == scrollControllers.length, 'An unequal amount of icons and scrollControllers were passed to LunaNavigationBar.'); - } - - @override - State createState() => _State(); -} - -class _State extends State { - int _index; - - @override - void initState() { - _index = widget.pageController?.initialPage ?? 0; - widget.pageController?.addListener(_pageControllerListener); - super.initState(); - } - - @override - void dispose() { - widget.pageController?.removeListener(_pageControllerListener); - super.dispose(); - } - - void _pageControllerListener() { - if((widget.pageController?.page?.round() ?? _index) == _index) return; - setState(() => _index = widget.pageController.page.round()); - } - - @override - Widget build(BuildContext context) { - return Container( - child: SafeArea( - child: Padding( - child: GNav( - gap: 8.0, - iconSize: 24.0, - padding: EdgeInsets.fromLTRB(18.0, 10.0, 12.0, 10.0), - duration: Duration(milliseconds: LunaUI.ANIMATION_SPEED), - tabBackgroundColor: Theme.of(context).canvasColor, - activeColor: LunaColours.accent, - tabs: List.generate(widget.icons.length, (index) => GButton( - icon: widget.icons[index], - text: widget.titles[index], - iconSize: 22.0, - haptic: true, - iconColor: Colors.white, - textStyle: TextStyle( - fontWeight: LunaUI.FONT_WEIGHT_BOLD, - fontSize: LunaUI.FONT_SIZE_NAVIGATION_BAR, - color: LunaColours.accent, - ), - leading: widget.leadingOnTab == null ? null : widget.leadingOnTab[index], - )).toList(), - tabActiveBorder: LunaUI.shouldUseBorder ? Border.all(color: Colors.white12) : null, - selectedIndex: _index, - onTabChange: (index) { - _onTabChange(index); - if(widget.onTabChange != null) widget.onTabChange(index); - }, - ), - padding: EdgeInsets.all(12.0), - ), - top: false, - ), - decoration: BoxDecoration(color: Theme.of(context).primaryColor), - ); - } - - void _onTabChange(int index) { - if(index == _index) { - if(widget.scrollControllers != null && widget.scrollControllers[index] != null) widget.scrollControllers[index].lunaAnimateToStart(); - } else { - widget.pageController.lunaJumpToPage(index); - } - } -} diff --git a/lib/core/ui/button.dart b/lib/core/ui/button.dart index 453dcb7c..c7290bef 100644 --- a/lib/core/ui/button.dart +++ b/lib/core/ui/button.dart @@ -2,146 +2,223 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:lunasea/core.dart'; -enum _BUTTON_SIZE { - REGULAR, - SLIM, +enum LunaButtonType { + TEXT, + ICON, + LOADER, } -class LunaButton extends StatelessWidget { - final Color textColor; - final Color backgroundColor; - final EdgeInsets margin; - final LunaLoadingState loadingState; - final Function onTap; - final Function onLongPress; - final String text; - final Widget child; - final _BUTTON_SIZE size; - +/// A Luna-styled button. +class LunaButton extends Card { LunaButton._({ Key key, - @required this.onTap, - @required this.onLongPress, - @required this.size, - @required this.text, - @required this.child, - @required this.textColor, - @required this.backgroundColor, - @required this.margin, - @required this.loadingState, - }) : super(key: key); - - /// Create a standard-sized button. - factory LunaButton({ - Key key, - @required Function onTap, - Function onLongPress, - String text = '', - Widget child, - Color textColor = Colors.white, - Color backgroundColor = LunaColours.accent, + @required Widget child, EdgeInsets margin = const EdgeInsets.symmetric(horizontal: 6.0, vertical: 6.0), - LunaLoadingState loadingState = LunaLoadingState.INACTIVE, - }) { - return LunaButton._( - key: key, - onTap: onTap, - onLongPress: onLongPress, - text: text, - child: child, - textColor: textColor, - backgroundColor: backgroundColor, - margin: margin, - loadingState: loadingState, - size: _BUTTON_SIZE.REGULAR, - ); - } - - /// Create a slim-sized button, typically used within table blocks. - factory LunaButton.slim({ - Key key, - @required Function onTap, - Function onLongPress, - String text = '', - Widget child, - Color textColor = Colors.white, Color backgroundColor = LunaColours.accent, - EdgeInsets margin = const EdgeInsets.symmetric(horizontal: 6.0, vertical: 6.0), - LunaLoadingState loadingState = LunaLoadingState.INACTIVE, - }) { - return LunaButton._( - key: key, - onTap: onTap, - onLongPress: onLongPress, - text: text, - child: child, - textColor: textColor, - backgroundColor: backgroundColor, - margin: margin, - loadingState: loadingState, - size: _BUTTON_SIZE.SLIM, - ); - } - - @override - Widget build(BuildContext context) { - return Row( - children: [ - Expanded( - child: Card( - child: InkWell( - child: size == _BUTTON_SIZE.REGULAR ? _regular() : _slim(), - borderRadius: BorderRadius.circular(LunaUI.BORDER_RADIUS), - onTap: () async { - HapticFeedback.lightImpact(); - if(onTap != null && loadingState != LunaLoadingState.ACTIVE) onTap(); - }, - onLongPress: () async { - HapticFeedback.heavyImpact(); - if(onLongPress != null && loadingState != LunaLoadingState.ACTIVE) onLongPress(); - }, - ), - margin: margin, - color: backgroundColor, - elevation: LunaUI.ELEVATION, - shape: LunaUI.shapeBorder, - ), - ), - ], - ); - } - - Widget _regular() { - return ListTile(title: _title()); - } - - Widget _slim() { - return Padding( - child: _title(), - padding: EdgeInsets.symmetric(vertical: 14.0), - ); - } - - Widget _title() { - if(loadingState == LunaLoadingState.ACTIVE) return LunaLoader( - useSafeArea: false, - color: textColor, - size: size == _BUTTON_SIZE.REGULAR ? 20.0 : 17.0, - ); - if(loadingState == LunaLoadingState.ERROR) return Icon( - Icons.error, - color: textColor, - size: size == _BUTTON_SIZE.REGULAR ? 20.0 : 17.0, - ); - if(child != null) return child; - return Text( - text, - style: TextStyle( - color: textColor, - fontWeight: LunaUI.FONT_WEIGHT_BOLD, - fontSize: LunaUI.FONT_SIZE_BUTTON, + double height = 50.0, + Alignment alignment = Alignment.center, + Decoration decoration, + Function onTap, + Function onLongPress, + LunaLoadingState loadingState, + }) : super( + key: key, + child: InkWell( + child: Container( + child: child, + decoration: decoration, + height: height, + alignment: alignment, ), - textAlign: TextAlign.center, + borderRadius: BorderRadius.circular(LunaUI.BORDER_RADIUS), + onTap: () async { + HapticFeedback.lightImpact(); + if(onTap != null && loadingState != LunaLoadingState.ACTIVE) onTap(); + }, + onLongPress: () async { + HapticFeedback.heavyImpact(); + if( onLongPress != null && loadingState != LunaLoadingState.ACTIVE) onLongPress(); + }, + ), + margin: margin, + color: backgroundColor, + shape: LunaUI.shapeBorder, + elevation: LunaUI.ELEVATION, + clipBehavior: Clip.antiAlias, + ) { + assert(child != null); + } + + /// Create a default button. + /// + /// If [LunaLoadingState] is passed in, will build the correct button based on the type. + factory LunaButton({ + @required LunaButtonType type, + Color color = Colors.white, + Color backgroundColor = LunaColours.accent, + String text, + IconData icon, + LunaLoadingState loadingState, + EdgeInsets margin = const EdgeInsets.symmetric(horizontal: 6.0, vertical: 6.0), + double height = 50.0, + Alignment alignment = Alignment.center, + Decoration decoration, + Function onTap, + Function onLongPress, + }) { + assert(type != null); + switch(loadingState) { + case LunaLoadingState.ACTIVE: return LunaButton.loader( + color: color, + backgroundColor: backgroundColor, + margin: margin, + height: height, + alignment: alignment, + decoration: decoration, + onTap: onTap, + onLongPress: onLongPress, + ); + case LunaLoadingState.ERROR: return LunaButton.icon( + icon: Icons.error, + color: color, + backgroundColor: backgroundColor, + margin: margin, + height: height, + alignment: alignment, + decoration: decoration, + onTap: onTap, + onLongPress: onLongPress, + ); + default: break; + } + switch(type) { + case LunaButtonType.TEXT: + assert(text != null); + return LunaButton.text( + text: text, + color: color, + backgroundColor: backgroundColor, + margin: margin, + height: height, + alignment: alignment, + decoration: decoration, + onTap: onTap, + onLongPress: onLongPress, + ); + case LunaButtonType.ICON: + assert(icon != null); + return LunaButton.icon( + icon: icon, + color: color, + backgroundColor: backgroundColor, + margin: margin, + height: height, + alignment: alignment, + decoration: decoration, + onTap: onTap, + onLongPress: onLongPress, + ); + case LunaButtonType.LOADER: return LunaButton.loader( + color: color, + backgroundColor: backgroundColor, + margin: margin, + height: height, + alignment: alignment, + decoration: decoration, + onTap: onTap, + onLongPress: onLongPress, + ); + } + throw Exception("Attempted to create an invalid LunaButton"); + } + + /// Build a button that contains a centered text string. + factory LunaButton.text({ + @required String text, + Color color = Colors.white, + Color backgroundColor = LunaColours.accent, + EdgeInsets margin = const EdgeInsets.symmetric(horizontal: 6.0, vertical: 6.0), + double height = 50.0, + Alignment alignment = Alignment.center, + Decoration decoration, + Function onTap, + Function onLongPress, + }) { + return LunaButton._( + child: Padding( + child: Text( + text, + style: TextStyle( + color: color, + fontWeight: LunaUI.FONT_WEIGHT_BOLD, + fontSize: LunaUI.FONT_SIZE_BUTTON, + ), + textAlign: TextAlign.center, + ), + padding: EdgeInsets.symmetric(horizontal: 8.0), + ), + margin: margin, + height: height, + backgroundColor: backgroundColor, + alignment: alignment, + decoration: decoration, + onTap: onTap, + onLongPress: onLongPress, ); } -} \ No newline at end of file + + /// Build a button that contains a [LunaLoader]. + factory LunaButton.loader({ + EdgeInsets margin = const EdgeInsets.symmetric(horizontal: 6.0, vertical: 6.0), + Color color = Colors.white, + Color backgroundColor = LunaColours.accent, + double height = 50.0, + Alignment alignment = Alignment.center, + Decoration decoration, + Function onTap, + Function onLongPress, + }) { + return LunaButton._( + child: LunaLoader( + useSafeArea: false, + color: color, + size: 16.0, + ), + margin: margin, + height: height, + backgroundColor: backgroundColor, + alignment: alignment, + decoration: decoration, + onTap: onTap, + onLongPress: onLongPress, + ); + } + + /// Build a button that contains a single, centered [Icon]. + factory LunaButton.icon({ + @required IconData icon, + Color color = Colors.white, + Color backgroundColor = LunaColours.accent, + EdgeInsets margin = const EdgeInsets.symmetric(horizontal: 6.0, vertical: 6.0), + double height = 50.0, + Alignment alignment = Alignment.center, + Decoration decoration, + Function onTap, + Function onLongPress, + }) { + return LunaButton._( + child: Icon( + icon, + color: color, + size: 22.0, + ), + margin: margin, + height: height, + backgroundColor: backgroundColor, + alignment: alignment, + decoration: decoration, + onTap: onTap, + onLongPress: onLongPress, + ); + } +} diff --git a/lib/core/ui/deprecated.dart b/lib/core/ui/deprecated.dart index f161a112..f9fbfec5 100644 --- a/lib/core/ui/deprecated.dart +++ b/lib/core/ui/deprecated.dart @@ -1,9 +1,7 @@ -export 'deprecated/button.dart'; export 'deprecated/card.dart'; export 'deprecated/containers.dart'; export 'deprecated/description_block.dart'; export 'deprecated/dialogs.dart'; -export 'deprecated/expandable.dart'; export 'deprecated/floating_action_button.dart'; export 'deprecated/icon.dart'; export 'deprecated/input_bar.dart'; diff --git a/lib/core/ui/deprecated/button.dart b/lib/core/ui/deprecated/button.dart deleted file mode 100644 index f4f488d7..00000000 --- a/lib/core/ui/deprecated/button.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'button/button.dart'; -export 'button/button_slim.dart'; diff --git a/lib/core/ui/deprecated/button/button.dart b/lib/core/ui/deprecated/button/button.dart deleted file mode 100644 index 2b81bcff..00000000 --- a/lib/core/ui/deprecated/button/button.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:lunasea/core.dart'; - -class LSButton extends StatelessWidget { - final String text; - final Function onTap; - final Color backgroundColor; - final Color textColor; - final bool reducedMargin; - final LunaLoadingState loadingState; - - LSButton({ - @required this.text, - @required this.onTap, - this.backgroundColor = const Color(LunaColours.ACCENT_COLOR), - this.textColor = Colors.white, - this.reducedMargin = false, - this.loadingState = LunaLoadingState.INACTIVE, - }); - - @override - Widget build(BuildContext context) => Row( - children: [ - Expanded( - child: Card( - child: InkWell( - child: ListTile( - title: loadingState == LunaLoadingState.ACTIVE - ? LSLoader( - color: Colors.white, - size: 20.0, - ) - : Text( - text, - style: TextStyle( - color: textColor, - fontWeight: LunaUI.FONT_WEIGHT_BOLD, - fontSize: Constants.UI_FONT_SIZE_STICKYHEADER, - ), - textAlign: TextAlign.center, - ), - ), - borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), - onTap: loadingState == LunaLoadingState.ACTIVE ? null : onTap == null ? null : () async { - HapticFeedback.lightImpact(); - onTap(); - }, - ), - color: backgroundColor, - margin: reducedMargin - ? EdgeInsets.symmetric(horizontal: 6.0, vertical: 6.0) - : LunaUI.MARGIN_CARD, - elevation: LunaUI.ELEVATION, - shape: LunaUI.shapeBorder, - ), - ), - ], - ); -} diff --git a/lib/core/ui/deprecated/button/button_slim.dart b/lib/core/ui/deprecated/button/button_slim.dart deleted file mode 100644 index ad2078c7..00000000 --- a/lib/core/ui/deprecated/button/button_slim.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:lunasea/core.dart'; - -class LSButtonSlim extends StatelessWidget { - final String text; - final Function onTap; - final Color backgroundColor; - final Color textColor; - final EdgeInsets margin; - final Widget widget; - - LSButtonSlim({ - @required this.text, - @required this.onTap, - this.widget, - this.backgroundColor = const Color(LunaColours.ACCENT_COLOR), - this.textColor = Colors.white, - this.margin = const EdgeInsets.symmetric(horizontal: 6.0, vertical: 6.0), - }); - - @override - Widget build(BuildContext context) => Row( - children: [ - Expanded( - child: Card( - child: InkWell( - child: Padding( - child: widget != null ? widget :Text( - text, - style: TextStyle( - color: textColor, - fontWeight: LunaUI.FONT_WEIGHT_BOLD, - fontSize: Constants.UI_FONT_SIZE_STICKYHEADER, - ), - textAlign: TextAlign.center, - ), - padding: EdgeInsets.symmetric(vertical: 14.0), - ), - borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), - onTap: onTap == null ? null : () async { - HapticFeedback.lightImpact(); - onTap(); - } - ), - color: backgroundColor, - margin: margin, - elevation: LunaUI.ELEVATION, - shape: LunaUI.shapeBorder, - ), - ), - ], - ); -} \ No newline at end of file diff --git a/lib/core/ui/deprecated/expandable.dart b/lib/core/ui/deprecated/expandable.dart deleted file mode 100644 index d252610a..00000000 --- a/lib/core/ui/deprecated/expandable.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:expandable/expandable.dart'; -import 'package:flutter/material.dart'; - -class LSExpandable extends StatefulWidget { - final ExpandableController controller; - final Widget collapsed; - final Widget expanded; - - LSExpandable({ - @required this.controller, - @required this.collapsed, - @required this.expanded, - Key key, - }): super(key: key); - - @override - State createState() => _State(); -} - -class _State extends State { - @override - Widget build(BuildContext context) => ExpandableNotifier( - controller: widget.controller, - child: Expandable( - collapsed: widget.collapsed, - expanded: widget.expanded, - ), - ); -} \ No newline at end of file diff --git a/lib/core/ui/deprecated/messages/generic.dart b/lib/core/ui/deprecated/messages/generic.dart index 54a57b10..cab89444 100644 --- a/lib/core/ui/deprecated/messages/generic.dart +++ b/lib/core/ui/deprecated/messages/generic.dart @@ -31,9 +31,13 @@ class LSGenericMessage extends StatelessWidget { elevation: LunaUI.ELEVATION, shape: LunaUI.shapeBorder, ), - if(showButton) LSButton( - text: buttonText, - onTap: onTapHandler, + if(showButton) LunaButtonContainer( + children: [ + LunaButton.text( + text: buttonText, + onTap: onTapHandler, + ), + ], ), ], ), diff --git a/lib/core/ui/deprecated/messages/typewriter.dart b/lib/core/ui/deprecated/messages/typewriter.dart index f6235622..bb0c3705 100644 --- a/lib/core/ui/deprecated/messages/typewriter.dart +++ b/lib/core/ui/deprecated/messages/typewriter.dart @@ -59,9 +59,13 @@ class _State extends State with TickerProviderStateMixin { elevation: LunaUI.ELEVATION, shape: LunaUI.shapeBorder, ), - if(widget.showButton) LSButton( - text: widget.buttonText, - onTap: widget.onTapHandler, + if(widget.showButton) LunaButtonContainer( + children: [ + LunaButton.text( + text: widget.buttonText, + onTap: widget.onTapHandler, + ), + ], ), ], ), diff --git a/lib/core/ui/list_view/list_view.dart b/lib/core/ui/list_view/list_view.dart index 071ea52c..ce020d0b 100644 --- a/lib/core/ui/list_view/list_view.dart +++ b/lib/core/ui/list_view/list_view.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; class LunaListView extends StatelessWidget { final List children; + final double itemExtent; final EdgeInsetsGeometry padding; final ScrollPhysics physics; final ScrollController controller; @@ -10,6 +11,7 @@ class LunaListView extends StatelessWidget { Key key, @required this.children, @required this.controller, + this.itemExtent, this.padding, this.physics = const AlwaysScrollableScrollPhysics(), }) : super(key: key); @@ -26,6 +28,7 @@ class LunaListView extends StatelessWidget { child: ListView( controller: controller, children: children, + itemExtent: itemExtent, padding: padding != null ? padding : EdgeInsets.only( top: 8.0, bottom: 8.0+(MediaQuery.of(context).padding.bottom), diff --git a/lib/core/ui/list_view/list_view_builder.dart b/lib/core/ui/list_view/list_view_builder.dart index 16ba347c..f2368c88 100644 --- a/lib/core/ui/list_view/list_view_builder.dart +++ b/lib/core/ui/list_view/list_view_builder.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; class LunaListViewBuilder extends StatelessWidget { final int itemCount; final Widget Function(BuildContext, int) itemBuilder; + final double itemExtent; final EdgeInsetsGeometry padding; final ScrollPhysics physics; final ScrollController controller; @@ -11,6 +12,7 @@ class LunaListViewBuilder extends StatelessWidget { Key key, @required this.itemCount, @required this.itemBuilder, + this.itemExtent, @required this.controller, this.padding, this.physics = const AlwaysScrollableScrollPhysics(), @@ -37,6 +39,7 @@ class LunaListViewBuilder extends StatelessWidget { physics: physics, itemCount: itemCount, itemBuilder: itemBuilder, + itemExtent: itemExtent, ), ), ); diff --git a/lib/core/ui/list_view/list_view_modal.dart b/lib/core/ui/list_view/list_view_modal.dart index b12b4cae..10ecc262 100644 --- a/lib/core/ui/list_view/list_view_modal.dart +++ b/lib/core/ui/list_view/list_view_modal.dart @@ -3,10 +3,12 @@ import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; class LunaListViewModal extends StatelessWidget { final List children; + final double itemExtent; LunaListViewModal({ Key key, @required this.children, + this.itemExtent, }) : super(key: key); @override @@ -16,6 +18,7 @@ class LunaListViewModal extends StatelessWidget { child: ListView( controller: ModalScrollController.of(context), children: children, + itemExtent: itemExtent, padding: EdgeInsets.only( top: 8.0, bottom: 8.0+(MediaQuery.of(context).padding.bottom), diff --git a/lib/core/ui/list_view/paged_list_view.dart b/lib/core/ui/list_view/paged_list_view.dart index ff20d6e2..e09b45a2 100644 --- a/lib/core/ui/list_view/paged_list_view.dart +++ b/lib/core/ui/list_view/paged_list_view.dart @@ -7,6 +7,7 @@ class LunaPagedListView extends StatefulWidget { final ScrollController scrollController; final void Function(int) listener; final Widget Function(BuildContext, T, int) itemBuilder; + final double itemExtent; final EdgeInsetsGeometry padding; final String noItemsFoundMessage; @@ -18,6 +19,7 @@ class LunaPagedListView extends StatefulWidget { @required this.itemBuilder, @required this.noItemsFoundMessage, @required this.scrollController, + this.itemExtent, this.padding, }) : super(key: key) { assert(refreshKey != null); @@ -54,6 +56,7 @@ class _State extends State> { child: PagedListView( pagingController: widget.pagingController, scrollController: widget.scrollController, + itemExtent: widget.itemExtent, builderDelegate: PagedChildBuilderDelegate( itemBuilder: widget.itemBuilder, firstPageErrorIndicatorBuilder: (context) => LunaMessage.error(onTap: () => Future.sync(() => widget.pagingController.refresh())), diff --git a/lib/core/ui/message/message.dart b/lib/core/ui/message/message.dart index 053d2813..c253fca0 100644 --- a/lib/core/ui/message/message.dart +++ b/lib/core/ui/message/message.dart @@ -120,7 +120,11 @@ class LunaMessage extends StatelessWidget { ], ), ), - if(buttonText != null) LSButton(text: buttonText, onTap: onTap), + if(buttonText != null) LunaButtonContainer( + children: [ + LunaButton.text(text: buttonText, onTap: onTap), + ], + ), ], ), ); diff --git a/lib/core/ui/table/table_content.dart b/lib/core/ui/table/table_content.dart index 5b4ee959..1ca3f7fe 100644 --- a/lib/core/ui/table/table_content.dart +++ b/lib/core/ui/table/table_content.dart @@ -26,10 +26,7 @@ class LunaTableContent extends StatelessWidget { this.titleColor = Colors.white70, this.bodyColor = Colors.white, this.padding = const EdgeInsets.symmetric(vertical: 4.0), - }) : super(key: key) { - assert(title != null); - assert(body != null); - } + }) : super(key: key); @override Widget build(BuildContext context) { @@ -49,7 +46,7 @@ class LunaTableContent extends StatelessWidget { Widget _title() { return Expanded( child: Text( - title.toUpperCase(), + title?.toUpperCase() ?? LunaUI.TEXT_EMDASH, textAlign: titleAlign, style: TextStyle( color: titleColor, @@ -64,7 +61,7 @@ class LunaTableContent extends StatelessWidget { return Expanded( child: InkWell( child: Text( - body, + body ?? LunaUI.TEXT_EMDASH, textAlign: bodyAlign, style: TextStyle( color: bodyColor, diff --git a/lib/core/utilities/changelog.dart b/lib/core/utilities/changelog.dart index 66b1ca7f..8c90b9a3 100644 --- a/lib/core/utilities/changelog.dart +++ b/lib/core/utilities/changelog.dart @@ -62,7 +62,7 @@ class LunaChangelog { ), LunaButtonContainer( children: [ - LunaButton(text: 'Full Changelog', onTap: () async => Constants.URL_CHANGELOG.lunaOpenGenericLink()), + LunaButton.text(text: 'Full Changelog', onTap: () async => Constants.URL_CHANGELOG.lunaOpenGenericLink()), ], ), ], diff --git a/lib/core/utilities/encryption.dart b/lib/core/utilities/encryption.dart index 41652cc5..c1880950 100644 --- a/lib/core/utilities/encryption.dart +++ b/lib/core/utilities/encryption.dart @@ -35,7 +35,6 @@ class LunaEncryption { final _encrypter = Encrypter(AES(key)); return _encrypter.decrypt64(data, iv: iv); } catch (error) { - /// Do not log as error (to Sentry), since a decrpytion error is very likely a user problem LunaLogger().warning('LunaEncryption', 'decrypt', 'Failed to decrypted "$data" with using key "$decryptionKey"'); } return ENCRYPTION_FAILURE; diff --git a/lib/core/widgets/expandable_list_tile.dart b/lib/core/widgets/expandable_list_tile.dart index 42d6ae14..5596eb83 100644 --- a/lib/core/widgets/expandable_list_tile.dart +++ b/lib/core/widgets/expandable_list_tile.dart @@ -6,12 +6,15 @@ class LunaExpandableListTile extends StatefulWidget { final String title; final TextSpan collapsedSubtitle1; final TextSpan collapsedSubtitle2; - final LunaIconButton collapsedTrailing; + final Widget collapsedTrailing; + final Widget collapsedLeading; + final Color backgroundColor; final Function onLongPress; final List expandedHighlightedNodes; final List expandedTableContent; final List expandedTableButtons; final Widget expandedCustomWidget; + final bool initialExpanded; /// Create a [LunaExpandableListTile] which is a list tile that expands into a table-style card. /// @@ -23,11 +26,14 @@ class LunaExpandableListTile extends StatefulWidget { @required this.collapsedSubtitle1, this.collapsedSubtitle2, this.collapsedTrailing, + this.collapsedLeading, this.onLongPress, this.expandedHighlightedNodes, this.expandedTableContent, this.expandedTableButtons, this.expandedCustomWidget, + this.backgroundColor, + this.initialExpanded = false, }) : super(key: key) { assert(title != null); assert(collapsedSubtitle1 != null); @@ -41,7 +47,13 @@ class LunaExpandableListTile extends StatefulWidget { } class _State extends State { - final ExpandableController controller = ExpandableController(); + ExpandableController controller; + + @override + void initState() { + super.initState(); + controller = ExpandableController(initialExpanded: widget.initialExpanded); + } @override void dispose() { @@ -84,6 +96,8 @@ class _State extends State { onLongPress: widget.onLongPress, contentPadding: widget.collapsedSubtitle2 != null, trailing: widget.collapsedTrailing, + leading: widget.collapsedLeading, + color: widget.backgroundColor, ); } @@ -148,6 +162,7 @@ class _State extends State { onTap: controller.toggle, onLongPress: widget.onLongPress, ), + color: widget.backgroundColor, ); } } diff --git a/lib/core/widgets/five_line_card_with_poster.dart b/lib/core/widgets/five_line_card_with_poster.dart index e8ae75b1..7a47c63b 100644 --- a/lib/core/widgets/five_line_card_with_poster.dart +++ b/lib/core/widgets/five_line_card_with_poster.dart @@ -24,9 +24,8 @@ class LunaFiveLineCardWithPoster extends StatelessWidget { final Color titleColor; final Map posterHeaders; final LunaIconButton trailing; - final double _height = 105.0; - final double _width = 70.0; - final double _padding = 8.0; + static final double itemExtent = 105.0; + static final double _padding = 8.0; LunaFiveLineCardWithPoster({ Key key, @@ -81,8 +80,8 @@ class LunaFiveLineCardWithPoster extends StatelessWidget { return LunaNetworkImage( url: posterUrl, placeholderAsset: posterPlaceholder, - height: _height, - width: _width, + height: itemExtent, + width: itemExtent/1.5, headers: posterHeaders, ); } @@ -107,7 +106,7 @@ class LunaFiveLineCardWithPoster extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.max, ), - height: (_height-(_padding*2)), + height: (itemExtent-(_padding*2)), ), padding: EdgeInsets.all(_padding), ), @@ -144,7 +143,7 @@ class LunaFiveLineCardWithPoster extends StatelessWidget { child: trailing, padding: EdgeInsets.only(right: 12.0), ), - height: _height, + height: itemExtent, ); } } diff --git a/lib/core/widgets/four_line_card_with_poster.dart b/lib/core/widgets/four_line_card_with_poster.dart index b4afcc44..16924c56 100644 --- a/lib/core/widgets/four_line_card_with_poster.dart +++ b/lib/core/widgets/four_line_card_with_poster.dart @@ -21,9 +21,8 @@ class LunaFourLineCardWithPoster extends StatelessWidget { final Color titleColor; final Map posterHeaders; final LunaIconButton trailing; - final double _height = 90.0; - final double _width = 60.0; - final double _padding = 8.0; + static final double itemExtent = 90.0; + static final double _padding = 8.0; LunaFourLineCardWithPoster({ Key key, @@ -68,6 +67,7 @@ class LunaFourLineCardWithPoster extends StatelessWidget { uri: backgroundUrl, headers: posterHeaders, ), + height: itemExtent, ); } @@ -75,8 +75,8 @@ class LunaFourLineCardWithPoster extends StatelessWidget { return LunaNetworkImage( url: posterUrl, placeholderAsset: posterPlaceholder, - height: _height, - width: _width, + height: itemExtent, + width: itemExtent/1.5, headers: posterHeaders, ); } @@ -99,7 +99,7 @@ class LunaFourLineCardWithPoster extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.max, ), - height: (_height-(_padding*2)), + height: (itemExtent-(_padding*2)), ), padding: EdgeInsets.all(_padding), ), @@ -136,7 +136,7 @@ class LunaFourLineCardWithPoster extends StatelessWidget { child: trailing, padding: EdgeInsets.only(right: 12.0), ), - height: _height, + height: itemExtent, ); } } diff --git a/lib/core/widgets/two_line_card_with_poster.dart b/lib/core/widgets/two_line_card_with_poster.dart index e36feedf..84581d54 100644 --- a/lib/core/widgets/two_line_card_with_poster.dart +++ b/lib/core/widgets/two_line_card_with_poster.dart @@ -16,9 +16,8 @@ class LunaTwoLineCardWithPoster extends StatelessWidget { final Color titleColor; final Map posterHeaders; final LunaIconButton trailing; - final double _height = 67.0; - final double _width = 67.0; - final double _padding = 8.0; + static final double itemExtent = 67.0; + static final double _padding = 8.0; LunaTwoLineCardWithPoster({ Key key, @@ -65,8 +64,8 @@ class LunaTwoLineCardWithPoster extends StatelessWidget { return LunaNetworkImage( url: posterUrl, placeholderAsset: posterPlaceholder, - height: _height, - width: _width, + height: itemExtent, + width: itemExtent, headers: posterHeaders, ); } @@ -87,7 +86,7 @@ class LunaTwoLineCardWithPoster extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.max, ), - height: (_height-(_padding*2)), + height: (itemExtent-(_padding*2)), ), padding: EdgeInsets.all(_padding), ), @@ -124,7 +123,7 @@ class LunaTwoLineCardWithPoster extends StatelessWidget { child: trailing, padding: EdgeInsets.only(right: 12.0), ), - height: _height, + height: itemExtent, ); } } diff --git a/lib/main.dart b/lib/main.dart index 01839dc8..3eb15d06 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,7 +8,6 @@ import 'package:lunasea/core.dart'; /// /// Runs app in guarded zone to attempt to capture fatal (crashing) errors Future main() async { - WidgetsFlutterBinding.ensureInitialized(); await _init(); runZonedGuarded( () => runApp( @@ -35,10 +34,8 @@ Future main() async { /// - Firebase /// - IAPs /// - Database -/// -/// Does not call [WidgetsFlutterBinding.ensureInitialized()] as that is called during Sentry's initialization. -/// If Sentry is removed, you should call that method to prevent black-screen flashing on launch. Future _init() async { + WidgetsFlutterBinding.ensureInitialized(); //Set system UI overlay style (navbar, statusbar) SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( systemNavigationBarColor: Colors.black, diff --git a/lib/modules/lidarr/routes/add_details.dart b/lib/modules/lidarr/routes/add_details.dart index bcfffef6..982fa30e 100644 --- a/lib/modules/lidarr/routes/add_details.dart +++ b/lib/modules/lidarr/routes/add_details.dart @@ -218,22 +218,16 @@ class _State extends State { ); }, ), - LSContainerRow( - children: [ - Expanded( - child: LSButton( + LunaButtonContainer( + children: [ + LunaButton.text( text: 'Add', onTap: () async => _addArtist(false), - reducedMargin: true, ), - ), - Expanded( - child: LSButton( + LunaButton.text( text: 'Add + Search', backgroundColor: LunaColours.orange, onTap: () async => _addArtist(true), - reducedMargin: true, - ), ), ], ), diff --git a/lib/modules/lidarr/routes/edit_artist.dart b/lib/modules/lidarr/routes/edit_artist.dart index 035c833c..2de5a60d 100644 --- a/lib/modules/lidarr/routes/edit_artist.dart +++ b/lib/modules/lidarr/routes/edit_artist.dart @@ -144,9 +144,13 @@ class _State extends State { trailing: LSIconButton(icon: Icons.arrow_forward_ios_rounded), onTap: () => _changeMetadata(), ), - LSButton( - text: 'Update Artist', - onTap: () async => _save().catchError((_) {}), + LunaButtonContainer( + children: [ + LunaButton.text( + text: 'Update Artist', + onTap: () async => _save().catchError((_) {}), + ), + ], ), ], ); diff --git a/lib/modules/lidarr/routes/search_results.dart b/lib/modules/lidarr/routes/search_results.dart index af016e00..e112ef69 100644 --- a/lib/modules/lidarr/routes/search_results.dart +++ b/lib/modules/lidarr/routes/search_results.dart @@ -110,7 +110,7 @@ class _State extends State { ? [LSGenericMessage(text: 'No Results Found')] : List.generate( filtered.length, - (index) => LidarrSearchResultTile(data: filtered[index]), + (index) => LidarrReleasesTile(release: filtered[index]), ); return LSListViewStickyHeader( controller: _scrollController, diff --git a/lib/modules/lidarr/widgets/search_result_tile.dart b/lib/modules/lidarr/widgets/search_result_tile.dart index 6b900c82..bef07485 100644 --- a/lib/modules/lidarr/widgets/search_result_tile.dart +++ b/lib/modules/lidarr/widgets/search_result_tile.dart @@ -1,185 +1,156 @@ -import 'package:expandable/expandable.dart'; import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/lidarr.dart'; import 'package:lunasea/modules/dashboard.dart'; -class LidarrSearchResultTile extends StatelessWidget { - final LidarrReleaseData data; - final ExpandableController _controller = ExpandableController(); +class LidarrReleasesTile extends StatefulWidget { + final LidarrReleaseData release; - LidarrSearchResultTile({ + LidarrReleasesTile({ Key key, - @required this.data, + @required this.release, }) : super(key: key); @override - Widget build(BuildContext context) => LSExpandable( - controller: _controller, - collapsed: _collapsed(context), - expanded: _expanded(context), - ); + State createState() => _State(); +} + +class _State extends State { + LunaLoadingState _downloadState = LunaLoadingState.INACTIVE; + + @override + Widget build(BuildContext context) { + return LunaExpandableListTile( + title: widget.release.title, + collapsedSubtitle1: _subtitle1(), + collapsedSubtitle2: _subtitle2(), + collapsedTrailing: _trailing(), + expandedHighlightedNodes: _highlightedNodes(), + expandedTableContent: _tableContent(), + expandedTableButtons: _tableButtons(), + ); + } + + TextSpan _subtitle1() { + return TextSpan( + children: [ + TextSpan( + style: TextStyle( + color: lunaProtocolColor, + fontWeight: LunaUI.FONT_WEIGHT_BOLD, + ), + text: widget.release.protocol.lunaCapitalizeFirstLetters(), + ), + if(widget.release.isTorrent) TextSpan( + text: ' (${widget.release.seeders}/${widget.release.leechers})', + style: TextStyle( + color: lunaProtocolColor, + fontWeight: LunaUI.FONT_WEIGHT_BOLD, + ), + ), + TextSpan(text: LunaUI.TEXT_BULLET.lunaPad()), + TextSpan(text: widget.release.indexer ?? LunaUI.TEXT_EMDASH), + TextSpan(text: LunaUI.TEXT_BULLET.lunaPad()), + TextSpan(text: widget.release.ageHours?.lunaHoursToAge() ?? LunaUI.TEXT_EMDASH), + ] + ); + } + + TextSpan _subtitle2() { + return TextSpan( + children: [ + TextSpan(text: widget.release.quality ?? LunaUI.TEXT_EMDASH), + TextSpan(text: LunaUI.TEXT_BULLET.lunaPad()), + TextSpan(text: widget.release.size?.lunaBytesToString() ?? LunaUI.TEXT_EMDASH), + ], + ); + } + + Widget _trailing() { + return LunaIconButton( + icon: widget.release.approved + ? Icons.file_download + : Icons.report_rounded, + color: widget.release.approved + ? Colors.white + : LunaColours.red, + onPressed: () async => widget.release.approved ? _startDownload() : _showWarnings(), + onLongPress: _startDownload, + loadingState: _downloadState, + ); + } + + List _highlightedNodes() { + return [ + LunaHighlightedNode( + text: widget.release.protocol.lunaCapitalizeFirstLetters(), + backgroundColor: lunaProtocolColor, + ), + ]; + } + + List _tableContent() { + return [ + LunaTableContent(title: 'source', body: widget.release.protocol.lunaCapitalizeFirstLetters()), + LunaTableContent(title: 'age', body: widget.release.ageHours?.lunaHoursToAge() ?? LunaUI.TEXT_EMDASH), + LunaTableContent(title: 'indexer', body: widget.release.indexer ?? LunaUI.TEXT_EMDASH), + LunaTableContent(title: 'size', body: widget.release.size?.lunaBytesToString() ?? LunaUI.TEXT_EMDASH), + LunaTableContent(title: 'quality', body: widget.release.quality ?? LunaUI.TEXT_EMDASH), + if(widget.release.protocol == 'torrent' && widget.release.seeders != null) LunaTableContent(title: 'seeders', body: '${widget.release.seeders}'), + if(widget.release.protocol == 'torrent' && widget.release.leechers != null) LunaTableContent(title: 'leechers', body: '${widget.release.leechers}'), + ]; + } Color get lunaProtocolColor { - if(!data.isTorrent) return LunaColours.accent; - int seeders = data.seeders ?? 0; + if(!widget.release.isTorrent) return LunaColours.accent; + int seeders = widget.release.seeders ?? 0; if(seeders > 10) return LunaColours.blue; if(seeders > 0) return LunaColours.orange; return LunaColours.red; } - Widget _expanded(BuildContext context) => LSCard( - child: InkWell( - child: Row( - children: [ - Expanded( - child: Padding( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LSTitle(text: data.title, softWrap: true, maxLines: 12), - Padding( - child: Wrap( - direction: Axis.horizontal, - runSpacing: 10.0, - children: [ - LSTextHighlighted( - text: data.protocol.lunaCapitalizeFirstLetters(), - bgColor: lunaProtocolColor, - ), - ], - ), - padding: EdgeInsets.only(top: 8.0, bottom: 2.0), - ), - Padding( - child: RichText( - text: TextSpan( - style: TextStyle( - color: Colors.white70, - fontSize: Constants.UI_FONT_SIZE_SUBTITLE, - ), - children: [ - if(data.isTorrent) TextSpan( - text: '${data.seeders} Seeders\t•\t${data.leechers} Leechers\n', - style: TextStyle( - color: lunaProtocolColor, - fontWeight: LunaUI.FONT_WEIGHT_BOLD, - ), - ), - TextSpan(text: '${data.quality ?? 'Unknown'}\t•\t'), - TextSpan(text: '${data.size.lunaBytesToString() ?? 'Unknown'}\t•\t'), - TextSpan(text: '${data.indexer}\n'), - TextSpan(text: '${data.ageHours.lunaHoursToAge() ?? 'Unknown'}'), - ], - ), - ), - padding: EdgeInsets.only(top: 6.0, bottom: 10.0), - ), - Padding( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Expanded( - child: LSButtonSlim( - text: 'Download', - onTap: () => _startDownload(context), - margin: data.approved - ? EdgeInsets.zero - : EdgeInsets.only(right: 6.0), - ), - ), - if(!data.approved) Expanded( - child: LSButtonSlim( - text: 'Rejected', - backgroundColor: LunaColours.red, - onTap: () => _showWarnings(context), - margin: EdgeInsets.only(left: 6.0), - ), - ), - ], - ), - padding: EdgeInsets.only(bottom: 2.0), - ), - ], - ), - padding: EdgeInsets.symmetric(horizontal: 12.0, vertical: 10.0), - ), - ), - ], + List _tableButtons() { + return [ + LunaButton( + type: LunaButtonType.TEXT, + text: 'Download', + onTap: _startDownload, + loadingState: _downloadState, ), - borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), - onTap: () => _controller.toggle(), - ), - ); - - Widget _collapsed(BuildContext context) => LSCardTile( - title: LSTitle(text: data.title), - subtitle: RichText( - text: TextSpan( - style: TextStyle( - color: Colors.white70, - fontSize: Constants.UI_FONT_SIZE_SUBTITLE, - ), - children: [ - TextSpan( - style: TextStyle( - color: lunaProtocolColor, - fontWeight: LunaUI.FONT_WEIGHT_BOLD, - ), - text: data.protocol.lunaCapitalizeFirstLetters(), - ), - if(data.isTorrent) TextSpan( - text: ' (${data.seeders}/${data.leechers})', - style: TextStyle( - color: lunaProtocolColor, - fontWeight: LunaUI.FONT_WEIGHT_BOLD, - ), - ), - TextSpan(text: '\t•\t${data.indexer}\t•\t'), - TextSpan(text: '${data.ageHours.lunaHoursToAge() ?? 'Unknown'}\n'), - TextSpan(text: '${data.quality ?? 'Unknown'}\t•\t'), - TextSpan(text: '${data.size.lunaBytesToString() ?? 'Unknown'}'), - ], + if(!widget.release.approved) LunaButton.text( + text: 'Rejected', + backgroundColor: LunaColours.red, + onTap: _showWarnings, ), - ), - trailing: LSIconButton( - icon: data.approved - ? Icons.file_download - : Icons.report_rounded, - color: data.approved - ? Colors.white - : LunaColours.red, - onPressed: () async => data.approved - ? _startDownload(context) - : _showWarnings(context), - ), - padContent: true, - onTap: () => _controller.toggle(), - ); - - Future _startDownload(BuildContext context) async { - LidarrAPI _api = LidarrAPI.from(Database.currentProfileObject); - await _api.downloadRelease(data.guid, data.indexerId) - .then((_) => LSSnackBar( - context: context, - title: 'Downloading...', - message: data.title, - type: SNACKBAR_TYPE.success, - showButton: true, - buttonText: 'Back', - buttonOnPressed: () => Navigator.of(context).popUntil((Route route) { - return !route.willHandlePopInternally - && route is ModalRoute - && (route.settings.name == Lidarr.ROUTE_NAME || route.settings.name == Dashboard.ROUTE_NAME); - }), - )) - .catchError((_) => LSSnackBar( - context: context, - title: 'Failed to Start Downloading', - message: LunaLogger.checkLogsMessage, - type: SNACKBAR_TYPE.failure, - )); + ]; } - Future _showWarnings(BuildContext context) async => await LunaDialogs().showRejections(context, data.rejections?.cast() ?? []); + Future _startDownload() async { + setState(() => _downloadState = LunaLoadingState.ACTIVE); + LidarrAPI _api = LidarrAPI.from(Database.currentProfileObject); + await _api.downloadRelease(widget.release.guid, widget.release.indexerId) + .then((_) { + showLunaSuccessSnackBar( + title: 'Downloading...', + message: widget.release.title, + showButton: true, + buttonText: 'Back', + buttonOnPressed: () => Navigator.of(context).popUntil((Route route) { + return !route.willHandlePopInternally + && route is ModalRoute + && (route.settings.name == Lidarr.ROUTE_NAME || route.settings.name == Dashboard.ROUTE_NAME); + }), + ); + }) + .catchError((error, stack) { + showLunaErrorSnackBar( + context: context, + title: 'Failed to Start Downloading', + error: error, + ); + }); + setState(() => _downloadState = LunaLoadingState.INACTIVE); + } + + Future _showWarnings() async => await LunaDialogs().showRejections(context, (widget.release.rejections ?? []).cast()); } diff --git a/lib/modules/nzbget/widgets/history_tile.dart b/lib/modules/nzbget/widgets/history_tile.dart index 4bff10cd..5e1d89d3 100644 --- a/lib/modules/nzbget/widgets/history_tile.dart +++ b/lib/modules/nzbget/widgets/history_tile.dart @@ -1,11 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:expandable/expandable.dart'; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/nzbget.dart'; -class NZBGetHistoryTile extends StatelessWidget { +class NZBGetHistoryTile extends StatefulWidget { final NZBGetHistoryData data; - final ExpandableController _controller = ExpandableController(); final Function() refresh; NZBGetHistoryTile({ @@ -14,151 +12,89 @@ class NZBGetHistoryTile extends StatelessWidget { }); @override - Widget build(BuildContext context) => LSExpandable( - controller: _controller, - collapsed: _collapsed(context), - expanded: _expanded(context), - ); + State createState() => _State(); +} - Widget _expanded(BuildContext context) => LSCard( - child: InkWell( - child: Row( - children: [ - Expanded( - child: Padding( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LSTitle( - text: data.name, - softWrap: true, - maxLines: 12, - ), - Padding( - child: Wrap( - direction: Axis.horizontal, - runSpacing: 10.0, - children: [ - LSTextHighlighted( - text: data.statusString, - bgColor: data.statusColor, - ), - LSTextHighlighted( - text: data.healthString, - bgColor: LunaColours.blueGrey, - ) - ], - ), - padding: EdgeInsets.only(top: 8.0, bottom: 2.0), - ), - Padding( - child: RichText( - text: TextSpan( - style: TextStyle( - color: Colors.white70, - fontSize: Constants.UI_FONT_SIZE_SUBTITLE, - ), - children: [ - TextSpan(text: data.completeTime), - TextSpan(text: '\t•\t'), - TextSpan(text: data.sizeReadable), - TextSpan(text: '\t•\t'), - TextSpan( - text: data.category.isEmpty - ? "No Category" - : data.category - ), - TextSpan(text: '\n'), - TextSpan(text: 'Average Speed of ${data.downloadSpeed}'), - TextSpan(text: '\n\n'), - TextSpan( - text: data.storageLocation, - style: TextStyle( - fontStyle: FontStyle.italic, - ), - ), - ], - ), - ), - padding: EdgeInsets.only(top: 6.0, bottom: 10.0), - ), - Padding( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Expanded( - child: LSButtonSlim( - text: 'Delete', - backgroundColor: LunaColours.red, - onTap: () async => _deleteButton(context), - margin: EdgeInsets.zero, - ), - ), - ], - ), - padding: EdgeInsets.only(bottom: 2.0), - ), - ], - ), - padding: EdgeInsets.symmetric(horizontal: 12.0, vertical: 10.0), - ), - ), - ], +class _State extends State { + @override + Widget build(BuildContext context) { + return LunaExpandableListTile( + title: widget.data.name, + collapsedSubtitle1: _subtitle1(), + collapsedSubtitle2: _subtitle2(), + expandedHighlightedNodes: _expandedHighlightedNodes(), + expandedTableContent: _expandedTableContent(), + expandedTableButtons: _expandedTableButtons(), + onLongPress: () async => _handlePopup(), + ); + } + + TextSpan _subtitle1() { + return TextSpan( + children: [ + TextSpan(text: widget.data.completeTime), + TextSpan(text: LunaUI.TEXT_BULLET.lunaPad()), + TextSpan(text: widget.data.sizeReadable), + TextSpan(text: LunaUI.TEXT_BULLET.lunaPad()), + TextSpan(text: (widget.data.category ?? '').isEmpty ? 'No Category' : widget.data.category), + ], + ); + } + + TextSpan _subtitle2() { + return TextSpan( + text: widget.data.statusString, + style: TextStyle( + color: widget.data.statusColor, + fontWeight: LunaUI.FONT_WEIGHT_BOLD, ), - borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), - onTap: () => _controller.toggle(), - onLongPress: () async => _handlePopup(context), - ), - ); + ); + } - Widget _collapsed(BuildContext context) => LSCardTile( - title: LSTitle(text: data.name), - subtitle: RichText( - text: TextSpan( - style: TextStyle( - color: Colors.white70, - fontSize: Constants.UI_FONT_SIZE_SUBTITLE, - ), - children: [ - TextSpan(text: data.completeTime), - TextSpan(text: '\t•\t'), - TextSpan(text: data.sizeReadable), - TextSpan(text: '\t•\t'), - TextSpan( - text: data.category.isEmpty - ? "No Category" - : data.category - ), - TextSpan(text: '\n'), - TextSpan( - text: data.statusString, - style: TextStyle( - color: data.statusColor, - fontWeight: LunaUI.FONT_WEIGHT_BOLD, - ), - ), - ], + List _expandedHighlightedNodes() { + return [ + LunaHighlightedNode( + text: widget.data.statusString, + backgroundColor: widget.data.statusColor, ), - maxLines: 2, - overflow: TextOverflow.fade, - softWrap: false, - ), - padContent: true, - onTap: () => _controller.toggle(), - onLongPress: () async => _handlePopup(context), - ); + LunaHighlightedNode( + text: widget.data.healthString, + backgroundColor: LunaColours.blueGrey, + ) + ]; + } - Future _handlePopup(BuildContext context) async { - List values = await NZBGetDialogs.historySettings(context, data.name); + List _expandedTableContent() { + return [ + LunaTableContent(title: 'age', body: widget.data.completeTime), + LunaTableContent(title: 'size', body: widget.data.sizeReadable), + LunaTableContent(title: 'category', body: (widget.data.category ?? '').isEmpty ? 'No Category' : widget.data.category), + LunaTableContent(title: 'speed', body: widget.data.downloadSpeed), + LunaTableContent(title: 'path', body: widget.data.storageLocation), + ]; + } + + List _expandedTableButtons() { + return [ + LunaButton.text( + text: 'Delete', + backgroundColor: LunaColours.red, + onTap: () async => _deleteButton(), + ), + ]; + } + + Future _handlePopup() async { + List values = await NZBGetDialogs.historySettings(context, widget.data.name); if(values[0]) switch(values[1]) { case 'retry': { - await NZBGetAPI.from(Database.currentProfileObject).retryHistoryEntry(data.id) + await NZBGetAPI.from(Database.currentProfileObject).retryHistoryEntry(widget.data.id) .then((_) { - refresh(); + widget.refresh(); LSSnackBar( context: context, title: 'Retrying Job...', - message: data.name, + message: widget.data.name, ); }) .catchError((_) => LSSnackBar( @@ -169,8 +105,8 @@ class NZBGetHistoryTile extends StatelessWidget { )); break; } - case 'hide': await NZBGetAPI.from(Database.currentProfileObject).deleteHistoryEntry(data.id, hide: true) - .then((_) => _handleDelete(context, 'History Hidden')) + case 'hide': await NZBGetAPI.from(Database.currentProfileObject).deleteHistoryEntry(widget.data.id, hide: true) + .then((_) => _handleDelete('History Hidden')) .catchError((_) => LSSnackBar( context: context, title: 'Failed to Hide History', @@ -178,8 +114,8 @@ class NZBGetHistoryTile extends StatelessWidget { type: SNACKBAR_TYPE.failure, )); break; - case 'delete': await NZBGetAPI.from(Database.currentProfileObject).deleteHistoryEntry(data.id, hide: true) - .then((_) => _handleDelete(context, 'History Deleted')) + case 'delete': await NZBGetAPI.from(Database.currentProfileObject).deleteHistoryEntry(widget.data.id, hide: true) + .then((_) => _handleDelete('History Deleted')) .catchError((_) => LSSnackBar( context: context, title: 'Failed to Delete History', @@ -189,13 +125,13 @@ class NZBGetHistoryTile extends StatelessWidget { } } - Future _deleteButton(BuildContext context) async { + Future _deleteButton() async { List values = await NZBGetDialogs.deleteHistory(context); if(values[0]) await NZBGetAPI.from(Database.currentProfileObject).deleteHistoryEntry( - data.id, + widget.data.id, hide: values[1], ) - .then((_) => _handleDelete(context, values[1] ? 'History Hidden' : 'History Deleted')) + .then((_) => _handleDelete(values[1] ? 'History Hidden' : 'History Deleted')) .catchError((_) => LSSnackBar( context: context, title: 'Failed to Delete History', @@ -204,13 +140,13 @@ class NZBGetHistoryTile extends StatelessWidget { )); } - void _handleDelete(BuildContext context, String title) { + void _handleDelete(String title) { LSSnackBar( context: context, title: title, - message: data.name, + message: widget.data.name, type: SNACKBAR_TYPE.success, ); - refresh(); + widget.refresh(); } } diff --git a/lib/modules/radarr/core/api_helper.dart b/lib/modules/radarr/core/api_helper.dart index 2e950202..7357bc36 100644 --- a/lib/modules/radarr/core/api_helper.dart +++ b/lib/modules/radarr/core/api_helper.dart @@ -265,6 +265,32 @@ class RadarrAPIHelper { return false; } + /// Execute a quick import. + Future quickImport({ + @required BuildContext context, + @required String path, + bool showSnackbar = true, + }) async { + assert(path != null && path.isNotEmpty); + if(context.read().enabled) { + return await context.read().api.command.downloadedMoviesScan(path: path) + .then((_) async { + if(showSnackbar) showLunaSuccessSnackBar( + context: context, + title: 'Running Quick Import${LunaUI.TEXT_ELLIPSIS}', + message: path, + ); + return true; + }) + .catchError((error, stack) { + LunaLogger().error('Failed to execute downloaded movies scan: $path', error, stack); + showLunaErrorSnackBar(title: 'Failed to Quick Import', error: error); + return false; + }); + } + return false; + } + /// Trigger an automatic search of a movie. Future automaticSearch({ @required BuildContext context, diff --git a/lib/modules/radarr/core/dialogs.dart b/lib/modules/radarr/core/dialogs.dart index 12fe6fca..ce42e64f 100644 --- a/lib/modules/radarr/core/dialogs.dart +++ b/lib/modules/radarr/core/dialogs.dart @@ -301,39 +301,36 @@ class RadarrDialogs { text: 'Close', onPressed: () => Navigator.of(context, rootNavigator: true).pop(), ), - ], title: LSDialog.title(text: 'Tags'), content: Selector>>( selector: (_, state) => state.tags, builder: (context, future, _) => FutureBuilder( future: future, - builder: (context, AsyncSnapshot> snapshot) => LSDialog.content( - children: (snapshot.data?.length ?? 0) == 0 - ? [ LSDialog.textContent(text: 'No Tags Found') ] - : List.generate( - snapshot.data.length, - (index) => CheckboxListTile( - title: Text( - snapshot.data[index].label, - style: TextStyle( - fontSize: LSDialog.BODY_SIZE, - color: Colors.white, - ), + builder: (context, AsyncSnapshot> snapshot) { + if((snapshot.data?.length ?? 0) == 0) return LSDialog.content( + children: [ + LSDialog.textContent(text: 'No Tags Found'), + ], + ); + return LSDialog.content( + children: List.generate( + snapshot.data.length, + (index) => LSDialog.checkbox( + title: snapshot.data[index].label, + value: context.watch().tags.where((tag) => tag.id == snapshot.data[index].id).length != 0, + onChanged: (selected) { + List _tags = context.read().tags; + selected ? _tags.add(snapshot.data[index]) : _tags.removeWhere((tag) => tag.id == snapshot.data[index].id); + context.read().tags = _tags; + }, ), - value: context.watch().tags.where((tag) => tag.id == snapshot.data[index].id).length != 0, - onChanged: (selected) { - List _tags = context.read().tags; - selected ? _tags.add(snapshot.data[index]) : _tags.removeWhere((tag) => tag.id == snapshot.data[index].id); - context.read().tags = _tags; - }, - contentPadding: LSDialog.tileContentPadding(), ), - ), - ), + ); + }, ), ), - contentPadding: LSDialog.textDialogContentPadding(), + contentPadding: LSDialog.listDialogContentPadding(), shape: LunaUI.shapeBorder, ), ), @@ -359,32 +356,30 @@ class RadarrDialogs { selector: (_, state) => state.tags, builder: (context, future, _) => FutureBuilder( future: future, - builder: (context, AsyncSnapshot> snapshot) => LSDialog.content( - children: (snapshot.data?.length ?? 0) == 0 - ? [ LSDialog.textContent(text: 'No Tags Found') ] - : List.generate( - snapshot.data.length, - (index) => CheckboxListTile( - title: Text( - snapshot.data[index].label, - style: TextStyle( - fontSize: LSDialog.BODY_SIZE, - color: Colors.white, - ), + builder: (context, AsyncSnapshot> snapshot) { + if((snapshot.data?.length ?? 0) == 0) return LSDialog.content( + children: [ + LSDialog.textContent(text: 'No Tags Found'), + ], + ); + return LSDialog.content( + children: List.generate( + snapshot.data.length, + (index) => LSDialog.checkbox( + title: snapshot.data[index].label, + value: context.watch().tags.where((tag) => tag.id == snapshot.data[index].id).length != 0, + onChanged: (selected) { + List _tags = context.read().tags; + selected ? _tags.add(snapshot.data[index]) : _tags.removeWhere((tag) => tag.id == snapshot.data[index].id); + context.read().tags = _tags; + }, ), - value: context.watch().tags.where((tag) => tag.id == snapshot.data[index].id).length != 0, - onChanged: (selected) { - List _tags = context.read().tags; - selected ? _tags.add(snapshot.data[index]) : _tags.removeWhere((tag) => tag.id == snapshot.data[index].id); - context.read().tags = _tags; - }, - contentPadding: LSDialog.tileContentPadding(), ), - ), - ), + ); + }, ), ), - contentPadding: LSDialog.textDialogContentPadding(), + contentPadding: LSDialog.listDialogContentPadding(), shape: LunaUI.shapeBorder, ), ), @@ -442,7 +437,6 @@ class RadarrDialogs { ), ], content: [ - LSDialog.textContent(text: 'Are you sure you want to remove the movie from Radarr?\n'), RadarrDatabaseValue.REMOVE_MOVIE_IMPORT_LIST.listen( builder: (context, value, _) => LSDialog.checkbox( title: 'Add to Exclusion List', @@ -458,7 +452,7 @@ class RadarrDialogs { ), ), ], - contentPadding: LSDialog.textDialogContentPadding(), + contentPadding: LSDialog.listDialogContentPadding(), ); return _flag; } diff --git a/lib/modules/radarr/core/router.dart b/lib/modules/radarr/core/router.dart index 3c94ed93..da22c73b 100644 --- a/lib/modules/radarr/core/router.dart +++ b/lib/modules/radarr/core/router.dart @@ -10,6 +10,8 @@ class RadarrRouter extends LunaModuleRouter { RadarrAddMovieDetailsRouter().defineRoute(router); RadarrHomeRouter().defineRoute(router); RadarrHistoryRouter().defineRoute(router); + RadarrManualImportRouter().defineRoute(router); + RadarrManualImportDetailsRouter().defineRoute(router); RadarrMoviesDetailsRouter().defineRoute(router); RadarrMoviesEditRouter().defineRoute(router); RadarrReleasesRouter().defineRoute(router); diff --git a/lib/modules/radarr/core/webhooks.dart b/lib/modules/radarr/core/webhooks.dart index f0a3fb19..e486f0e4 100644 --- a/lib/modules/radarr/core/webhooks.dart +++ b/lib/modules/radarr/core/webhooks.dart @@ -44,35 +44,17 @@ extension _EventTypeExtension on _EventType { } } - Future _downloadEvent(Map data) async { - int movieId = int.tryParse(data['id']); + Future _downloadEvent(Map data) async => _goToMovieDetails(int.tryParse(data['id'])); + Future _grabEvent(Map data) async => _goToMovieDetails(int.tryParse(data['id'])); + Future _healthEvent(Map data) async => RadarrSystemStatusRouter().navigateTo(LunaState.navigatorKey.currentContext); + Future _renameEvent(Map data) async => _goToMovieDetails(int.tryParse(data['id'])); + Future _testEvent(Map data) async => LunaModule.RADARR.launch(); + + Future _goToMovieDetails(int movieId) async { if(movieId != null) return RadarrMoviesDetailsRouter().navigateTo( LunaState.navigatorKey.currentContext, movieId: movieId, ); - } - - Future _grabEvent(Map data) async { - int movieId = int.tryParse(data['id']); - if(movieId != null) return RadarrMoviesDetailsRouter().navigateTo( - LunaState.navigatorKey.currentContext, - movieId: movieId, - ); - } - - Future _healthEvent(Map data) async { - return RadarrSystemStatusRouter().navigateTo(LunaState.navigatorKey.currentContext); - } - - Future _renameEvent(Map data) async { - int movieId = int.tryParse(data['id']); - if(movieId != null) return RadarrMoviesDetailsRouter().navigateTo( - LunaState.navigatorKey.currentContext, - movieId: movieId, - ); - } - - Future _testEvent(Map data) async { return LunaModule.RADARR.launch(); } } diff --git a/lib/modules/radarr/routes.dart b/lib/modules/radarr/routes.dart index 43a70d8d..e9b72178 100644 --- a/lib/modules/radarr/routes.dart +++ b/lib/modules/radarr/routes.dart @@ -3,6 +3,8 @@ export 'routes/add_movie_details.dart'; export 'routes/catalogue.dart'; export 'routes/edit_movie.dart'; export 'routes/history.dart'; +export 'routes/manual_import.dart'; +export 'routes/manual_import_details.dart'; export 'routes/missing.dart'; export 'routes/more.dart'; export 'routes/movie_details.dart'; diff --git a/lib/modules/radarr/routes/add_movie_details/route.dart b/lib/modules/radarr/routes/add_movie_details/route.dart index 91d1f578..949c4adf 100644 --- a/lib/modules/radarr/routes/add_movie_details/route.dart +++ b/lib/modules/radarr/routes/add_movie_details/route.dart @@ -56,10 +56,17 @@ class _State extends State<_RadarrMoviesAddDetailsRoute> with LunaLoadCallbackMi title: 'radarr.AddMovie'.tr(), message: 'radarr.MovieNotFound'.tr(), ); - return Scaffold( - key: _scaffoldKey, - appBar: _appBar(), - body: _body(), + return ChangeNotifierProvider( + create: (_) => RadarrAddMovieDetailsState( + movie: arguments.movie, + isDiscovery: arguments.isDiscovery, + ), + builder: (context, _) => Scaffold( + key: _scaffoldKey, + appBar: _appBar(), + body: _body(), + bottomNavigationBar: RadarrAddMovieDetailsActionBar(), + ), ); } @@ -80,45 +87,42 @@ class _State extends State<_RadarrMoviesAddDetailsRoute> with LunaLoadCallbackMi builder: (context, AsyncSnapshot> snapshot) { if(snapshot.hasError) return LunaMessage.error(onTap: _refreshKey.currentState.show); if(snapshot.hasData) { - return _content(snapshot.data[0], snapshot.data[1], snapshot.data[2]); + return _content( + context, + rootFolders: snapshot.data[0], + qualityProfiles: snapshot.data[1], + tags: snapshot.data[2], + ); } return LunaLoader(); }, ); } - Widget _content(List rootFolders, List qualityProfiles, List tags) { - _RadarrAddMovieDetailsArguments arguments = ModalRoute.of(context).settings.arguments; - return ChangeNotifierProvider( - create: (_) => RadarrAddMovieDetailsState( - movie: arguments.movie, - isDiscovery: arguments.isDiscovery, - rootFolders: rootFolders, - qualityProfiles: qualityProfiles, - tags: tags, - ), - builder: (context, _) => LunaListView( - controller: scrollController, - children: [ - RadarrAddMovieSearchResultTile( - movie: context.read().movie, - onTapShowOverview: true, - exists: false, - isExcluded: false, - ), - RadarrAddMovieDetailsMonitoredTile(), - RadarrAddMovieDetailsRootFolderTile(), - RadarrAddMovieDetailsMinimumAvailabilityTile(), - RadarrAddMovieDetailsQualityProfileTile(), - RadarrAddMovieDetailsTagsTile(), - LunaButtonContainer( - children: [ - RadarrAddMovieDetailsAddButton(searchOnAdd: false), - RadarrAddMovieDetailsAddButton(searchOnAdd: true), - ], - ), - ], - ), + Widget _content(BuildContext context, { + List rootFolders, + List qualityProfiles, + List tags, + }) { + context.read().initializeAvailability(); + context.read().initializeQualityProfile(qualityProfiles); + context.read().initializeRootFolder(rootFolders); + context.read().initializeTags(tags); + return LunaListView( + controller: scrollController, + children: [ + RadarrAddMovieSearchResultTile( + movie: context.read().movie, + onTapShowOverview: true, + exists: false, + isExcluded: false, + ), + RadarrAddMovieDetailsMonitoredTile(), + RadarrAddMovieDetailsRootFolderTile(), + RadarrAddMovieDetailsMinimumAvailabilityTile(), + RadarrAddMovieDetailsQualityProfileTile(), + RadarrAddMovieDetailsTagsTile(), + ], ); } } diff --git a/lib/modules/radarr/routes/add_movie_details/state.dart b/lib/modules/radarr/routes/add_movie_details/state.dart index 548f9bce..b3f121fd 100644 --- a/lib/modules/radarr/routes/add_movie_details/state.dart +++ b/lib/modules/radarr/routes/add_movie_details/state.dart @@ -5,34 +5,11 @@ import 'package:lunasea/modules/radarr.dart'; class RadarrAddMovieDetailsState extends ChangeNotifier { final bool isDiscovery; final RadarrMovie movie; - + RadarrAddMovieDetailsState({ @required this.movie, @required this.isDiscovery, - @required List rootFolders, - @required List qualityProfiles, - @required List tags, - }) { - assert(movie != null); - assert(isDiscovery != null); - assert(rootFolders != null); - assert(qualityProfiles != null); - assert(tags != null); - _tags = (tags ?? []).where((tag) => ((RadarrDatabaseValue.ADD_MOVIE_DEFAULT_TAGS.data as List) ?? []).contains(tag.id)).toList(); - _monitored = RadarrDatabaseValue.ADD_MOVIE_DEFAULT_MONITORED_STATE.data ?? true; - _rootFolder = (rootFolders ?? []).firstWhere( - (element) => element.id == RadarrDatabaseValue.ADD_MOVIE_DEFAULT_ROOT_FOLDER_ID.data, - orElse: () => (rootFolders?.length ?? 0) != 0 ? rootFolders[0] : RadarrRootFolder(id: -1, freeSpace: 0, path: Constants.TEXT_EMDASH), - ); - _qualityProfile = (qualityProfiles ?? []).firstWhere( - (element) => element.id == RadarrDatabaseValue.ADD_MOVIE_DEFAULT_QUALITY_PROFILE_ID.data, - orElse: () => (qualityProfiles?.length ?? 0) != 0 ? qualityProfiles[0] : RadarrQualityProfile(id: -1, name: Constants.TEXT_EMDASH), - ); - _availability = RadarrAvailability.values.firstWhere( - (element) => element.value == RadarrDatabaseValue.ADD_MOVIE_DEFAULT_MINIMUM_AVAILABILITY_ID.data, - orElse: () => RadarrAvailability.ANNOUNCED, - ); - } + }); bool _monitored = true; bool get monitored => _monitored; @@ -51,6 +28,12 @@ class RadarrAddMovieDetailsState extends ChangeNotifier { RadarrDatabaseValue.ADD_MOVIE_DEFAULT_MINIMUM_AVAILABILITY_ID.put(_availability.value); notifyListeners(); } + void initializeAvailability() { + _availability = RadarrAvailability.values.firstWhere( + (element) => element.value == RadarrDatabaseValue.ADD_MOVIE_DEFAULT_MINIMUM_AVAILABILITY_ID.data, + orElse: () => RadarrAvailability.ANNOUNCED, + ); + } RadarrRootFolder _rootFolder; RadarrRootFolder get rootFolder => _rootFolder; @@ -60,6 +43,12 @@ class RadarrAddMovieDetailsState extends ChangeNotifier { RadarrDatabaseValue.ADD_MOVIE_DEFAULT_ROOT_FOLDER_ID.put(_rootFolder.id); notifyListeners(); } + void initializeRootFolder(List rootFolders) { + _rootFolder = (rootFolders ?? []).firstWhere( + (element) => element.id == RadarrDatabaseValue.ADD_MOVIE_DEFAULT_ROOT_FOLDER_ID.data, + orElse: () => (rootFolders?.length ?? 0) != 0 ? rootFolders[0] : RadarrRootFolder(id: -1, freeSpace: 0, path: Constants.TEXT_EMDASH), + ); + } RadarrQualityProfile _qualityProfile; RadarrQualityProfile get qualityProfile => _qualityProfile; @@ -69,6 +58,12 @@ class RadarrAddMovieDetailsState extends ChangeNotifier { RadarrDatabaseValue.ADD_MOVIE_DEFAULT_QUALITY_PROFILE_ID.put(_qualityProfile.id); notifyListeners(); } + void initializeQualityProfile(List qualityProfiles) { + _qualityProfile = (qualityProfiles ?? []).firstWhere( + (element) => element.id == RadarrDatabaseValue.ADD_MOVIE_DEFAULT_QUALITY_PROFILE_ID.data, + orElse: () => (qualityProfiles?.length ?? 0) != 0 ? qualityProfiles[0] : RadarrQualityProfile(id: -1, name: Constants.TEXT_EMDASH), + ); + } List _tags = []; List get tags => _tags; @@ -78,6 +73,9 @@ class RadarrAddMovieDetailsState extends ChangeNotifier { RadarrDatabaseValue.ADD_MOVIE_DEFAULT_TAGS.put((tags ?? []).map((tag) => tag.id).toList()); notifyListeners(); } + void initializeTags(List tags) { + _tags = (tags ?? []).where((tag) => ((RadarrDatabaseValue.ADD_MOVIE_DEFAULT_TAGS.data as List) ?? []).contains(tag.id)).toList(); + } LunaLoadingState _state = LunaLoadingState.INACTIVE; LunaLoadingState get state => _state; diff --git a/lib/modules/radarr/routes/add_movie_details/widgets.dart b/lib/modules/radarr/routes/add_movie_details/widgets.dart index 6c6e631b..cd9f234e 100644 --- a/lib/modules/radarr/routes/add_movie_details/widgets.dart +++ b/lib/modules/radarr/routes/add_movie_details/widgets.dart @@ -1,3 +1,4 @@ +export 'widgets/bottom_action_bar.dart'; export 'widgets/button_add_movie.dart'; export 'widgets/tile_minimum_availability.dart'; export 'widgets/tile_monitored.dart'; diff --git a/lib/modules/radarr/routes/add_movie_details/widgets/bottom_action_bar.dart b/lib/modules/radarr/routes/add_movie_details/widgets/bottom_action_bar.dart new file mode 100644 index 00000000..63a77d9c --- /dev/null +++ b/lib/modules/radarr/routes/add_movie_details/widgets/bottom_action_bar.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/radarr.dart'; + +class RadarrAddMovieDetailsActionBar extends StatelessWidget { + @override + Widget build(BuildContext context) { + return LunaBottomActionBar( + actions: [ + RadarrAddMovieDetailsAddButton(searchOnAdd: false), + RadarrAddMovieDetailsAddButton(searchOnAdd: true), + ], + ); + } +} diff --git a/lib/modules/radarr/routes/add_movie_details/widgets/button_add_movie.dart b/lib/modules/radarr/routes/add_movie_details/widgets/button_add_movie.dart index bfc3c6db..2c96cf57 100644 --- a/lib/modules/radarr/routes/add_movie_details/widgets/button_add_movie.dart +++ b/lib/modules/radarr/routes/add_movie_details/widgets/button_add_movie.dart @@ -16,6 +16,7 @@ class RadarrAddMovieDetailsAddButton extends StatelessWidget { @override Widget build(BuildContext context) { return LunaButton( + type: LunaButtonType.TEXT, text: searchOnAdd ? 'radarr.AddMovieAndSearch'.tr() : 'radarr.AddMovie'.tr(), backgroundColor: searchOnAdd ? LunaColours.orange : LunaColours.accent, onTap: () async => _onTap(context), diff --git a/lib/modules/radarr/routes/catalogue/route.dart b/lib/modules/radarr/routes/catalogue/route.dart index 28632b9f..cbb8df86 100644 --- a/lib/modules/radarr/routes/catalogue/route.dart +++ b/lib/modules/radarr/routes/catalogue/route.dart @@ -86,7 +86,7 @@ class _State extends State with AutomaticKeepAliveClientMi LunaMessage.inList(text: 'radarr.NoMoviesFound'.tr()), LunaButtonContainer( children: [ - LunaButton( + LunaButton.text( text: query.length > 20 ? 'radarr.SearchFor'.tr(args: ['"${query.substring(0, min(20, query.length))}${LunaUI.TEXT_ELLIPSIS}"']) : 'radarr.SearchFor'.tr(args: ['"$query"']), @@ -99,13 +99,10 @@ class _State extends State with AutomaticKeepAliveClientMi return LunaListViewBuilder( controller: RadarrNavigationBar.scrollControllers[0], itemCount: _filtered.length, - itemBuilder: (context, index) { - if(_filtered[index].id == null) return Container(height: 0.0); - return RadarrCatalogueTile( - movie: _filtered[index], - profile: qualityProfiles.firstWhere((element) => element.id == _filtered[index].qualityProfileId, orElse: null), - ); - }, + itemBuilder: (context, index) => RadarrCatalogueTile( + movie: _filtered[index], + profile: qualityProfiles.firstWhere((element) => element.id == _filtered[index].qualityProfileId, orElse: null), + ), ); } ); @@ -119,8 +116,8 @@ class _State extends State with AutomaticKeepAliveClientMi bool ascending = context.read().moviesSortAscending; // Filter List filtered = movies.where((movie) { - if(query != null && query.isNotEmpty) return movie.title.toLowerCase().contains(query.toLowerCase()); - return movie != null; + if(query != null && query.isNotEmpty && movie.id != null) return movie.title.toLowerCase().contains(query.toLowerCase()); + return (movie != null && movie.id != null); }).toList(); filtered = filter.filter(filtered); // Sort diff --git a/lib/modules/radarr/routes/edit_movie/route.dart b/lib/modules/radarr/routes/edit_movie/route.dart index 370bee6a..2ac4aaf9 100644 --- a/lib/modules/radarr/routes/edit_movie/route.dart +++ b/lib/modules/radarr/routes/edit_movie/route.dart @@ -46,10 +46,17 @@ class _State extends State<_RadarrMoviesEditRoute> with LunaLoadCallbackMixin, L title: 'radarr.EditMovie'.tr(), message: 'radarr.MovieNotFound'.tr(), ); - return Scaffold( - key: _scaffoldKey, - appBar: _appBar(), - body: _body(), + return ChangeNotifierProvider( + create: (_) => RadarrMoviesEditState(), + builder: (context, _) { + LunaLoadingState state = context.select((state) => state.state); + return Scaffold( + key: _scaffoldKey, + appBar: _appBar(), + body: state == LunaLoadingState.ERROR ? _bodyError() : _body(context), + bottomNavigationBar: state == LunaLoadingState.ERROR ? null : RadarrEditMovieActionBar(), + ); + } ); } @@ -60,12 +67,19 @@ class _State extends State<_RadarrMoviesEditRoute> with LunaLoadCallbackMixin, L ); } - Widget _body() { + Widget _bodyError() { + return LunaMessage.goBack( + context: context, + text: 'lunasea.AnErrorHasOccurred'.tr(), + ); + } + + Widget _body(BuildContext context) { return FutureBuilder( future: Future.wait([ - context.watch().movies, - context.watch().qualityProfiles, - context.watch().tags, + context.select>>((state) => state.movies), + context.select>>((state) => state.qualityProfiles), + context.select>>((state) => state.tags), ]), builder: (context, AsyncSnapshot> snapshot) { if(snapshot.hasError) return LunaMessage.error(onTap: loadCallback); @@ -75,33 +89,37 @@ class _State extends State<_RadarrMoviesEditRoute> with LunaLoadCallbackMixin, L orElse: () => null, ); if(movie == null) return LunaLoader(); - return _list(movie, snapshot.data[1], snapshot.data[2]); + return _list( + context, + movie: movie, + qualityProfiles: snapshot.data[1], + tags: snapshot.data[2], + ); } return LunaLoader(); }, ); } - Widget _list(RadarrMovie movie, List qualityProfiles, List tags) { - return ChangeNotifierProvider( - create: (_) => RadarrMoviesEditState(movie: movie, qualityProfiles: qualityProfiles, tags: tags), - builder: (context, _) { - if(context.watch().state == LunaLoadingState.ERROR) return LunaMessage.goBack( - context: context, - text: 'lunasea.AnErrorHasOccurred'.tr(), - ); - return LunaListView( - controller: scrollController, - children: [ - RadarrMoviesEditMonitoredTile(), - RadarrMoviesEditMinimumAvailabilityTile(), - RadarrMoviesEditQualityProfileTile(profiles: qualityProfiles), - RadarrMoviesEditPathTile(), - RadarrMoviesEditTagsTile(), - RadarrMoviesEditUpdateMovieButton(movie: movie), - ], - ); - } + Widget _list(BuildContext context, { + RadarrMovie movie, + List qualityProfiles, + List tags, + }) { + if(context.read().movie == null) { + context.read().movie = movie; + context.read().initializeQualityProfile(qualityProfiles); + context.read().initializeTags(tags); + } + return LunaListView( + controller: scrollController, + children: [ + RadarrMoviesEditMonitoredTile(), + RadarrMoviesEditMinimumAvailabilityTile(), + RadarrMoviesEditQualityProfileTile(profiles: qualityProfiles), + RadarrMoviesEditPathTile(), + RadarrMoviesEditTagsTile(), + ], ); } } diff --git a/lib/modules/radarr/routes/edit_movie/state.dart b/lib/modules/radarr/routes/edit_movie/state.dart index d153739f..b65dbefb 100644 --- a/lib/modules/radarr/routes/edit_movie/state.dart +++ b/lib/modules/radarr/routes/edit_movie/state.dart @@ -3,19 +3,13 @@ import 'package:lunasea/core.dart'; import 'package:lunasea/modules/radarr.dart'; class RadarrMoviesEditState extends ChangeNotifier { - RadarrMoviesEditState({ - @required RadarrMovie movie, - @required List qualityProfiles, - @required List tags, - }) { - _monitored = movie.monitored ?? true; - _path = movie.path ?? ''; - _availability = movie.minimumAvailability ?? RadarrAvailability.ANNOUNCED; - _qualityProfile = qualityProfiles.firstWhere( - (profile) => profile.id == movie.qualityProfileId, - orElse: () => qualityProfiles.length == 0 ? null : qualityProfiles[0], - ); - _tags = (tags ?? []).where((tag) => (movie.tags ?? []).contains(tag.id)).toList(); + RadarrMovie _movie; + RadarrMovie get movie => _movie; + set movie(RadarrMovie movie) { + _movie = movie; + initializeMonitored(); + initializeAvailability(); + initializePath(); } LunaLoadingState _state = LunaLoadingState.INACTIVE; @@ -33,6 +27,9 @@ class RadarrMoviesEditState extends ChangeNotifier { _monitored = monitored; notifyListeners(); } + void initializeMonitored() { + _monitored = movie.monitored ?? false; + } String _path = ''; String get path => _path; @@ -41,6 +38,9 @@ class RadarrMoviesEditState extends ChangeNotifier { _path = path; notifyListeners(); } + void initializePath() { + _path = movie.path ?? ''; + } RadarrQualityProfile _qualityProfile; RadarrQualityProfile get qualityProfile => _qualityProfile; @@ -49,6 +49,12 @@ class RadarrMoviesEditState extends ChangeNotifier { _qualityProfile = qualityProfile; notifyListeners(); } + void initializeQualityProfile(List qualityProfiles) { + _qualityProfile = qualityProfiles.firstWhere( + (profile) => profile.id == movie.qualityProfileId, + orElse: () => qualityProfiles.length == 0 ? null : qualityProfiles[0], + ); + } RadarrAvailability _availability; RadarrAvailability get availability => _availability; @@ -57,6 +63,9 @@ class RadarrMoviesEditState extends ChangeNotifier { _availability = availability; notifyListeners(); } + void initializeAvailability() { + _availability = movie.minimumAvailability ?? RadarrAvailability.ANNOUNCED; + } List _tags; List get tags => _tags; @@ -65,4 +74,7 @@ class RadarrMoviesEditState extends ChangeNotifier { _tags = tags; notifyListeners(); } + void initializeTags(List tags) { + _tags = (tags ?? []).where((tag) => (movie.tags ?? []).contains(tag.id)).toList(); + } } diff --git a/lib/modules/radarr/routes/edit_movie/widgets.dart b/lib/modules/radarr/routes/edit_movie/widgets.dart index 646b7fd5..9d325798 100644 --- a/lib/modules/radarr/routes/edit_movie/widgets.dart +++ b/lib/modules/radarr/routes/edit_movie/widgets.dart @@ -1,3 +1,4 @@ +export 'widgets/bottom_action_bar.dart'; export 'widgets/button_update_movie.dart'; export 'widgets/tile_monitored.dart'; export 'widgets/tile_path.dart'; diff --git a/lib/modules/radarr/routes/edit_movie/widgets/bottom_action_bar.dart b/lib/modules/radarr/routes/edit_movie/widgets/bottom_action_bar.dart new file mode 100644 index 00000000..8aba13b8 --- /dev/null +++ b/lib/modules/radarr/routes/edit_movie/widgets/bottom_action_bar.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/radarr.dart'; + +class RadarrEditMovieActionBar extends StatelessWidget { + @override + Widget build(BuildContext context) { + return LunaBottomActionBar( + actions: [ + RadarrEditMovieUpdateMovieButton(), + ], + ); + } +} diff --git a/lib/modules/radarr/routes/edit_movie/widgets/button_update_movie.dart b/lib/modules/radarr/routes/edit_movie/widgets/button_update_movie.dart index 6aa5fd97..f224fb60 100644 --- a/lib/modules/radarr/routes/edit_movie/widgets/button_update_movie.dart +++ b/lib/modules/radarr/routes/edit_movie/widgets/button_update_movie.dart @@ -2,29 +2,21 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/radarr.dart'; -class RadarrMoviesEditUpdateMovieButton extends StatelessWidget { - final RadarrMovie movie; - - RadarrMoviesEditUpdateMovieButton({ - Key key, - @required this.movie, - }) : super(key: key); - +class RadarrEditMovieUpdateMovieButton extends StatelessWidget { @override Widget build(BuildContext context) { - return LunaButtonContainer( - children: [ - LunaButton( - text: 'radarr.UpdateMovie'.tr(), - loadingState: context.watch().state, - onTap: () async { - context.read().state = LunaLoadingState.ACTIVE; - RadarrMovie updatedMovie = movie.updateEdits(context.read()); - bool result = await RadarrAPIHelper().updateMovie(context: context, movie: updatedMovie); - if(result) Navigator.of(context).lunaSafetyPop(); - }, - ), - ], + return LunaButton( + type: LunaButtonType.TEXT, + text: 'radarr.UpdateMovie'.tr(), + loadingState: context.watch().state, + onTap: () async { + context.read().state = LunaLoadingState.ACTIVE; + if(context.read().movie != null) { + RadarrMovie movie = context.read().movie.updateEdits(context.read()); + bool result = await RadarrAPIHelper().updateMovie(context: context, movie: movie); + if(result) Navigator.of(context).lunaSafetyPop(); + } + }, ); } } diff --git a/lib/modules/radarr/routes/manual_import.dart b/lib/modules/radarr/routes/manual_import.dart new file mode 100644 index 00000000..5f77dd17 --- /dev/null +++ b/lib/modules/radarr/routes/manual_import.dart @@ -0,0 +1,3 @@ +export 'manual_import/route.dart'; +export 'manual_import/state.dart'; +export 'manual_import/widgets.dart'; diff --git a/lib/modules/radarr/routes/manual_import/route.dart b/lib/modules/radarr/routes/manual_import/route.dart new file mode 100644 index 00000000..d422f4bd --- /dev/null +++ b/lib/modules/radarr/routes/manual_import/route.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/radarr.dart'; + +class RadarrManualImportRouter extends RadarrPageRouter { + RadarrManualImportRouter() : super('/radarr/manualimport'); + + @override + void defineRoute(FluroRouter router) => super.noParameterRouteDefinition(router, _RadarrManualImportRoute()); +} + + +class _RadarrManualImportRoute extends StatefulWidget { + @override + State<_RadarrManualImportRoute> createState() => _State(); +} + +class _State extends State<_RadarrManualImportRoute> with LunaScrollControllerMixin { + final GlobalKey _scaffoldKey = GlobalKey(); + + @override + Widget build(BuildContext context) { + return ChangeNotifierProvider( + create: (context) => RadarrManualImportState(context), + builder: (context, _) => Scaffold( + key: _scaffoldKey, + appBar: _appBar(), + body: _body(context), + bottomNavigationBar: RadarrManualImportBottomActionBar(), + ), + ); + } + + Widget _appBar() { + return LunaAppBar( + title: 'radarr.ManualImport'.tr(), + scrollControllers: [scrollController], + bottom: RadarrManualImportPathBar(), + ); + } + + Widget _body(BuildContext context) { + return FutureBuilder( + future: context.select>((state) => state.directories), + builder: (context, AsyncSnapshot snapshot) { + if(snapshot.hasError) { + if(snapshot.connectionState != ConnectionState.waiting) LunaLogger().error( + 'Unable to fetch Radarr filesystem', + snapshot.error, + snapshot.stackTrace, + ); + return LunaMessage.error(onTap: () => context.read().fetchDirectories( + context, + context.read().currentPath, + )); + } + if(snapshot.hasData) return _list(context, snapshot.data); + return LunaLoader(); + }, + ); + } + + Widget _list(BuildContext context, RadarrFileSystem fileSystem) { + if((fileSystem?.directories?.length ?? 0) == 0 && (fileSystem.parent == null || fileSystem.parent.isEmpty)) return LunaMessage( + text: 'radarr.NoSubdirectoriesFound'.tr(), + ); + return Selector( + selector: (_, state) => state.currentPath, + builder: (context, path, _) { + List directories = _filterDirectories(path, fileSystem); + return LunaListView( + key: ObjectKey(fileSystem.directories), + controller: scrollController, + children: [ + RadarrManualImportParentDirectoryTile(fileSystem: fileSystem), + ...List.generate( + directories.length, + (index) => RadarrManualImportDirectoryTile(directory: directories[index]), + ), + ] + ); + }, + ); + } + + List _filterDirectories(String path, RadarrFileSystem fileSystem) { + if(path == null || path.isEmpty) return fileSystem.directories ?? []; + if(fileSystem?.directories == null || fileSystem.directories.length == 0) return []; + return fileSystem.directories.where((element) => element.path.contains(path)).toList(); + } +} diff --git a/lib/modules/radarr/routes/manual_import/state.dart b/lib/modules/radarr/routes/manual_import/state.dart new file mode 100644 index 00000000..fc30426e --- /dev/null +++ b/lib/modules/radarr/routes/manual_import/state.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/radarr.dart'; + +class RadarrManualImportState extends ChangeNotifier { + RadarrManualImportState(BuildContext context) { + fetchDirectories(context, null); + } + + String _currentPath; + String get currentPath => _currentPath; + set currentPath(String path) { + _currentPath = path; + updateTextControllerText(); + notifyListeners(); + } + + TextEditingController currentPathTextController = TextEditingController(); + void updateTextControllerText() { + currentPathTextController.text = _currentPath; + currentPathTextController.selection = TextSelection.fromPosition(TextPosition(offset: _currentPath.length)); + } + + Future _directories; + Future get directories => _directories; + void fetchDirectories(BuildContext context, String path) { + if(context.read().enabled) { + _directories = context.read().api.fileSystem.get( + path: path, + includeFiles: false, + allowFoldersWithoutTrailingSlashes: false, + ).whenComplete(() => currentPath = path ?? ''); + } + notifyListeners(); + } +} diff --git a/lib/modules/radarr/routes/manual_import/widgets.dart b/lib/modules/radarr/routes/manual_import/widgets.dart new file mode 100644 index 00000000..5f4f07df --- /dev/null +++ b/lib/modules/radarr/routes/manual_import/widgets.dart @@ -0,0 +1,4 @@ +export 'widgets/appbar_path_bar.dart'; +export 'widgets/bottom_action_bar.dart'; +export 'widgets/directory_tile.dart'; +export 'widgets/parent_directory_tile.dart'; diff --git a/lib/modules/radarr/routes/manual_import/widgets/appbar_path_bar.dart b/lib/modules/radarr/routes/manual_import/widgets/appbar_path_bar.dart new file mode 100644 index 00000000..06b54a21 --- /dev/null +++ b/lib/modules/radarr/routes/manual_import/widgets/appbar_path_bar.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/radarr.dart'; + +class RadarrManualImportPathBar extends StatefulWidget implements PreferredSizeWidget { + @override + Size get preferredSize => Size.fromHeight(62.0); + + @override + State createState() => _State(); +} + +class _State extends State { + @override + Widget build(BuildContext context) { + return Padding( + child: Row( + children: [ + Expanded( + child: LunaTextInputBar( + labelIcon: Icons.sd_storage_rounded, + labelText: 'radarr.FileBrowser'.tr(), + controller: context.watch().currentPathTextController, + autofocus: false, + onChanged: (value) { + context.read().currentPath = value; + if(value.endsWith('/') || value.isEmpty) { + context.read().fetchDirectories(context, value); + } + }, + margin: EdgeInsets.fromLTRB(12.0, 0.0, 12.0, 14.0), + ), + ), + ], + ), + padding: EdgeInsets.only(top: 1.0, bottom: 1.0), + ); + } +} diff --git a/lib/modules/radarr/routes/manual_import/widgets/bottom_action_bar.dart b/lib/modules/radarr/routes/manual_import/widgets/bottom_action_bar.dart new file mode 100644 index 00000000..a8d6266b --- /dev/null +++ b/lib/modules/radarr/routes/manual_import/widgets/bottom_action_bar.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/radarr.dart'; + +class RadarrManualImportBottomActionBar extends StatelessWidget { + @override + Widget build(BuildContext context) { + return LunaBottomActionBar( + actions: [ + LunaButton.text( + text: 'radarr.Quick'.tr(), + onTap: () async { + String path = context.read().currentPath; + RadarrAPIHelper().quickImport(context: context, path: path); + }, + ), + LunaButton.text( + text: 'radarr.Interactive'.tr(), + backgroundColor: LunaColours.orange, + onTap: () async { + String path = context.read().currentPath; + RadarrManualImportDetailsRouter().navigateTo( + context, + path: (path ?? '').isNotEmpty ? path : '/', + ); + }, + ), + ], + ); + } +} diff --git a/lib/modules/radarr/routes/manual_import/widgets/directory_tile.dart b/lib/modules/radarr/routes/manual_import/widgets/directory_tile.dart new file mode 100644 index 00000000..f3839320 --- /dev/null +++ b/lib/modules/radarr/routes/manual_import/widgets/directory_tile.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/radarr.dart'; + +class RadarrManualImportDirectoryTile extends StatefulWidget { + final RadarrFileSystemDirectory directory; + + RadarrManualImportDirectoryTile({ + Key key, + @required this.directory, + }) : super(key: key); + + @override + State createState() => _State(); +} + +class _State extends State { + LunaLoadingState _loadingState = LunaLoadingState.INACTIVE; + + @override + Widget build(BuildContext context) { + if( + widget.directory == null || + widget.directory.path == null || + widget.directory.path.isEmpty + ) return SizedBox(height: 0.0); + return LunaListTile( + context: context, + title: LunaText.title(text: widget.directory?.name ?? LunaUI.TEXT_EMDASH), + subtitle: LunaText.subtitle( + text: widget.directory.path, + ), + trailing: LunaIconButton( + icon: Icons.arrow_forward_ios_rounded, + loadingState: _loadingState, + ), + onTap: () async { + if(_loadingState == LunaLoadingState.INACTIVE) { + if(mounted) setState(() => _loadingState = LunaLoadingState.ACTIVE); + context.read().fetchDirectories( + context, + widget.directory.path, + ); + } + } + ); + } +} diff --git a/lib/modules/radarr/routes/manual_import/widgets/parent_directory_tile.dart b/lib/modules/radarr/routes/manual_import/widgets/parent_directory_tile.dart new file mode 100644 index 00000000..bdd892ed --- /dev/null +++ b/lib/modules/radarr/routes/manual_import/widgets/parent_directory_tile.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/radarr.dart'; + +class RadarrManualImportParentDirectoryTile extends StatefulWidget { + final RadarrFileSystem fileSystem; + + RadarrManualImportParentDirectoryTile({ + Key key, + @required this.fileSystem, + }) : super(key: key); + + @override + State createState() => _State(); +} + +class _State extends State { + LunaLoadingState _loadingState = LunaLoadingState.INACTIVE; + + @override + Widget build(BuildContext context) { + if( + widget.fileSystem == null || + widget.fileSystem.parent == null || + widget.fileSystem.parent.isEmpty + ) return SizedBox(height: 0.0); + return LunaListTile( + context: context, + title: LunaText.title(text: LunaUI.TEXT_ELLIPSIS), + subtitle: LunaText.subtitle(text: 'radarr.ParentDirectory'.tr()), + trailing: LunaIconButton( + icon: Icons.arrow_upward_rounded, + loadingState: _loadingState, + ), + onTap: () async { + if(_loadingState == LunaLoadingState.INACTIVE) { + if(mounted) setState(() => _loadingState = LunaLoadingState.ACTIVE); + context.read().fetchDirectories( + context, + widget.fileSystem.parent, + ); + } + }, + ); + } +} diff --git a/lib/modules/radarr/routes/manual_import_details.dart b/lib/modules/radarr/routes/manual_import_details.dart new file mode 100644 index 00000000..25b5bcc9 --- /dev/null +++ b/lib/modules/radarr/routes/manual_import_details.dart @@ -0,0 +1,3 @@ +export 'manual_import_details/route.dart'; +export 'manual_import_details/state.dart'; +export 'manual_import_details/widgets.dart'; diff --git a/lib/modules/radarr/routes/manual_import_details/route.dart b/lib/modules/radarr/routes/manual_import_details/route.dart new file mode 100644 index 00000000..24947fc1 --- /dev/null +++ b/lib/modules/radarr/routes/manual_import_details/route.dart @@ -0,0 +1,98 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/radarr.dart'; + +class _RadarrManualImportDetailsArguments { + final String path; + + _RadarrManualImportDetailsArguments({ + @required this.path, + }) { + assert(path != null); + } +} + +class RadarrManualImportDetailsRouter extends RadarrPageRouter { + RadarrManualImportDetailsRouter() : super('/radarr/manualimport/details'); + + @override + Future navigateTo(BuildContext context, { + @required String path, + }) => LunaRouter.router.navigateTo( + context, + route(), + routeSettings: RouteSettings(arguments: _RadarrManualImportDetailsArguments(path: path)), + ); + + @override + void defineRoute(FluroRouter router) => super.noParameterRouteDefinition(router, _RadarrMoviesAddDetailsRoute()); +} + +class _RadarrMoviesAddDetailsRoute extends StatefulWidget { + @override + State createState() => _State(); +} + +class _State extends State<_RadarrMoviesAddDetailsRoute> with LunaScrollControllerMixin { + @override + Widget build(BuildContext context) { + _RadarrManualImportDetailsArguments arguments = ModalRoute.of(context).settings.arguments; + if(arguments == null || arguments.path == null || arguments.path.isEmpty) return LunaInvalidRoute( + title: 'radarr.ManualImport'.tr(), + message: 'radarr.DirectoryNotFound'.tr(), + ); + return ChangeNotifierProvider( + create: (BuildContext context) => RadarrManualImportDetailsState(context, path: arguments.path), + builder: (context, _) { + return Scaffold( + appBar: _appBar(), + body: _body(context), + ); + }, + ); + } + + Widget _appBar() { + return LunaAppBar( + title: 'radarr.ManualImport'.tr(), + scrollControllers: [scrollController], + ); + } + + Widget _body(BuildContext context) { + return FutureBuilder( + future: context.select>>((state) => state.manualImport), + builder: (context, AsyncSnapshot> snapshot) { + if(snapshot.hasError) { + if(snapshot.connectionState != ConnectionState.waiting) LunaLogger().error( + 'Unable to fetch Radarr manual import: ${context.read().path}', + snapshot.error, + snapshot.stackTrace, + ); + return LunaMessage.error( + onTap: () => context.read().fetchManualImport(context), + ); + } + if(snapshot.connectionState == ConnectionState.done && snapshot.hasData) { + return _list(context, manualImport: snapshot.data); + } + return LunaLoader(); + }, + ); + } + + Widget _list(BuildContext context, { + @required List manualImport, + }) { + if((manualImport?.length ?? 0) == 0) return LunaMessage( + text: 'radarr.NoFilesFound'.tr(), + buttonText: 'lunasea.Refresh'.tr(), + onTap: () => context.read().fetchManualImport(context), + ); + return LunaListViewBuilder( + controller: scrollController, + itemCount: manualImport.length, + itemBuilder: (context, index) => RadarrManualImportDetailsImportTile(manualImport: manualImport[index]), + ); + } +} diff --git a/lib/modules/radarr/routes/manual_import_details/state.dart b/lib/modules/radarr/routes/manual_import_details/state.dart new file mode 100644 index 00000000..7c4e4ce5 --- /dev/null +++ b/lib/modules/radarr/routes/manual_import_details/state.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/radarr.dart'; + +class RadarrManualImportDetailsState extends ChangeNotifier { + final String path; + + RadarrManualImportDetailsState(BuildContext context, { + @required this.path, + }) { + fetchManualImport(context); + } + + Future> _manualImport; + Future> get manualImport => _manualImport; + Future fetchManualImport(BuildContext context) async { + if(context.read().enabled) { + _manualImport = context.read().api.manualImport.get( + folder: path, + filterExistingFiles: true, + ); + } + notifyListeners(); + } +} diff --git a/lib/modules/radarr/routes/manual_import_details/widgets.dart b/lib/modules/radarr/routes/manual_import_details/widgets.dart new file mode 100644 index 00000000..af6e5d95 --- /dev/null +++ b/lib/modules/radarr/routes/manual_import_details/widgets.dart @@ -0,0 +1 @@ +export 'widgets/import_tile.dart'; diff --git a/lib/modules/radarr/routes/manual_import_details/widgets/import_tile.dart b/lib/modules/radarr/routes/manual_import_details/widgets/import_tile.dart new file mode 100644 index 00000000..65a84c8b --- /dev/null +++ b/lib/modules/radarr/routes/manual_import_details/widgets/import_tile.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/radarr.dart'; + +class RadarrManualImportDetailsImportTile extends StatelessWidget { + final RadarrManualImport manualImport; + + RadarrManualImportDetailsImportTile({ + Key key, + @required this.manualImport, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return LunaListTile( + context: context, + title: LunaText.title(text: manualImport.relativePath), + ); + } +} diff --git a/lib/modules/radarr/routes/more/route.dart b/lib/modules/radarr/routes/more/route.dart index a8478784..a3b7d3a1 100644 --- a/lib/modules/radarr/routes/more/route.dart +++ b/lib/modules/radarr/routes/more/route.dart @@ -33,25 +33,32 @@ class _State extends State with AutomaticKeepAliveClientMixin { trailing: LunaIconButton(icon: LunaIcons.history, color: LunaColours.list(0)), onTap: () async => RadarrHistoryRouter().navigateTo(context), ), + LunaListTile( + context: context, + title: LunaText.title(text: 'radarr.ManualImport'.tr()), + subtitle: LunaText.subtitle(text: 'radarr.ManualImportDescription'.tr()), + trailing: LunaIconButton(icon: Icons.person_rounded, color: LunaColours.list(1)), + onTap: () async => RadarrManualImportRouter().navigateTo(context), + ), LunaListTile( context: context, title: LunaText.title(text: 'radarr.Queue'.tr()), subtitle: LunaText.subtitle(text: 'radarr.QueueDescription'.tr()), - trailing: LunaIconButton(icon: Icons.queue, color: LunaColours.list(1)), + trailing: LunaIconButton(icon: Icons.queue, color: LunaColours.list(2)), onTap: () async => RadarrQueueRouter().navigateTo(context), ), LunaListTile( context: context, title: LunaText.title(text: 'radarr.SystemStatus'.tr()), subtitle: LunaText.subtitle(text: 'radarr.SystemStatusDescription'.tr()), - trailing: LunaIconButton(icon: LunaIcons.monitoring, color: LunaColours.list(2)), + trailing: LunaIconButton(icon: LunaIcons.monitoring, color: LunaColours.list(3)), onTap: () async => RadarrSystemStatusRouter().navigateTo(context), ), LunaListTile( context: context, title: LunaText.title(text: 'radarr.Tags'.tr()), subtitle: LunaText.subtitle(text: 'radarr.TagsDescription'.tr()), - trailing: LunaIconButton(icon: Icons.style, color: LunaColours.list(3)), + trailing: LunaIconButton(icon: Icons.style, color: LunaColours.list(4)), onTap: () async => RadarrTagsRouter().navigateTo(context), ), ], diff --git a/lib/modules/radarr/routes/movie_details/route.dart b/lib/modules/radarr/routes/movie_details/route.dart index aedda255..c6f8467a 100644 --- a/lib/modules/radarr/routes/movie_details/route.dart +++ b/lib/modules/radarr/routes/movie_details/route.dart @@ -35,16 +35,15 @@ class _State extends State<_RadarrMoviesDetailsRoute> with LunaLoadCallbackMixin final GlobalKey _scaffoldKey = GlobalKey(); RadarrMovie movie; PageController _pageController; - bool _loaded = false; @override Future loadCallback() async { if(widget.movieId > 0) { - _findMovie(await context.read().movies); + RadarrMovie result = _findMovie(await context.read().movies); + setState(() => movie = result); context.read().fetchQualityProfiles(); context.read().fetchTags(); await context.read().resetSingleMovie(widget.movieId); - _findMovie(await context.read().movies); } } @@ -54,15 +53,11 @@ class _State extends State<_RadarrMoviesDetailsRoute> with LunaLoadCallbackMixin _pageController = PageController(initialPage: RadarrDatabaseValue.NAVIGATION_INDEX_MOVIE_DETAILS.data); } - void _findMovie(List movies) { - RadarrMovie _movie = movies.firstWhere( + RadarrMovie _findMovie(List movies) { + return movies.firstWhere( (movie) => movie.id == widget.movieId, orElse: () => null, ); - if(mounted) setState(() { - movie = _movie; - _loaded = true; - }); } List _findTags(List tagIds, List tags) { @@ -105,18 +100,17 @@ class _State extends State<_RadarrMoviesDetailsRoute> with LunaLoadCallbackMixin Widget _bottomNavigationBar() { if(movie == null) return null; - return RadarrMovieDetailsNavigationBar(pageController: _pageController); + return RadarrMovieDetailsNavigationBar( + pageController: _pageController, + movie: movie, + ); } Widget _body() { return Consumer( builder: (context, state, _) => FutureBuilder( - future: Future.wait([state.qualityProfiles, state.tags]), + future: Future.wait([state.qualityProfiles, state.tags, state.movies]), builder: (context, AsyncSnapshot> snapshot) { - if(_loaded && movie == null) return LunaMessage.goBack( - text: 'Movie Not Found', - context: context, - ); if(snapshot.hasError) { if(snapshot.connectionState != ConnectionState.waiting) LunaLogger().error( 'Unable to pull Radarr movie details', @@ -126,7 +120,11 @@ class _State extends State<_RadarrMoviesDetailsRoute> with LunaLoadCallbackMixin return LunaMessage.error(onTap: loadCallback); } if(snapshot.hasData) { - if(movie == null) return LunaLoader(); + movie = _findMovie(snapshot.data[2]); + if(movie == null) return LunaMessage.goBack( + text: 'Movie Not Found', + context: context, + ); RadarrQualityProfile qualityProfile = _findQualityProfile(movie.qualityProfileId, snapshot.data[0]); List tags = _findTags(movie.tags, snapshot.data[1]); return _pages(qualityProfile, tags); diff --git a/lib/modules/radarr/routes/movie_details/widgets.dart b/lib/modules/radarr/routes/movie_details/widgets.dart index c7d2e46b..46a0dd1c 100644 --- a/lib/modules/radarr/routes/movie_details/widgets.dart +++ b/lib/modules/radarr/routes/movie_details/widgets.dart @@ -3,7 +3,6 @@ export 'widgets/cast_crew_tile.dart'; export 'widgets/files_extra_file_block.dart'; export 'widgets/files_file_block.dart'; export 'widgets/navigation_bar.dart'; -export 'widgets/overview_download_buttons.dart'; export 'widgets/overview_movie_description_tile.dart'; export 'widgets/overview_movie_information_block.dart'; export 'widgets/overview_movie_links_section.dart'; diff --git a/lib/modules/radarr/routes/movie_details/widgets/files_file_block.dart b/lib/modules/radarr/routes/movie_details/widgets/files_file_block.dart index 1b07dd27..5fb79586 100644 --- a/lib/modules/radarr/routes/movie_details/widgets/files_file_block.dart +++ b/lib/modules/radarr/routes/movie_details/widgets/files_file_block.dart @@ -37,11 +37,12 @@ class _State extends State { LunaTableContent(title: 'added on', body: widget.movieFile?.lunaDateAdded), ], buttons: [ - if(widget.movieFile?.mediaInfo != null) LunaButton.slim( + if(widget.movieFile?.mediaInfo != null) LunaButton.text( text: 'Media Info', onTap: () async => _viewMediaInfo(), ), - LunaButton.slim( + LunaButton( + type: LunaButtonType.TEXT, text: 'Delete', onTap: () async => _deleteFile(), backgroundColor: LunaColours.red, diff --git a/lib/modules/radarr/routes/movie_details/widgets/navigation_bar.dart b/lib/modules/radarr/routes/movie_details/widgets/navigation_bar.dart index 9b89a575..e30d9d3b 100644 --- a/lib/modules/radarr/routes/movie_details/widgets/navigation_bar.dart +++ b/lib/modules/radarr/routes/movie_details/widgets/navigation_bar.dart @@ -1,24 +1,60 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/radarr.dart'; -class RadarrMovieDetailsNavigationBar extends StatelessWidget { +class RadarrMovieDetailsNavigationBar extends StatefulWidget { static const List icons = [Icons.subject_rounded, Icons.insert_drive_file_outlined, Icons.history, Icons.person]; static const List titles = ['Overview', 'Files', 'History', 'Cast & Crew']; static List scrollControllers = List.generate(icons.length, (_) => ScrollController()); final PageController pageController; + final RadarrMovie movie; RadarrMovieDetailsNavigationBar({ Key key, @required this.pageController, + @required this.movie, }): super(key: key); + @override + State createState() => _State(); +} + +class _State extends State { + LunaLoadingState _automaticLoadingState = LunaLoadingState.INACTIVE; + + @override Widget build(BuildContext context) { return LunaBottomNavigationBar( - pageController: pageController, - scrollControllers: scrollControllers, - icons: icons, - titles: titles, + pageController: widget.pageController, + scrollControllers: RadarrMovieDetailsNavigationBar.scrollControllers, + icons: RadarrMovieDetailsNavigationBar.icons, + titles: RadarrMovieDetailsNavigationBar.titles, + topActions: [ + LunaButton( + type: LunaButtonType.TEXT, + text: 'Automatic', + onTap: _automatic, + loadingState: _automaticLoadingState, + ), + LunaButton.text( + text: 'Interactive', + backgroundColor: LunaColours.orange, + onTap: _manual, + ), + ], ); } + + Future _automatic() async { + setState(() => _automaticLoadingState = LunaLoadingState.ACTIVE); + RadarrAPIHelper().automaticSearch(context: context, movieId: widget.movie.id, title: widget.movie.title) + .then((value) { + if(mounted) setState(() { + _automaticLoadingState = value ? LunaLoadingState.INACTIVE : LunaLoadingState.ERROR; + }); + }); + } + + Future _manual() async => RadarrReleasesRouter().navigateTo(context, movieId: widget.movie.id ?? -1); } diff --git a/lib/modules/radarr/routes/movie_details/widgets/overview_download_buttons.dart b/lib/modules/radarr/routes/movie_details/widgets/overview_download_buttons.dart deleted file mode 100644 index 5055c7e4..00000000 --- a/lib/modules/radarr/routes/movie_details/widgets/overview_download_buttons.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/radarr.dart'; - -class RadarrMovieDetailsOverviewDownloadButtons extends StatefulWidget { - final RadarrMovie movie; - - RadarrMovieDetailsOverviewDownloadButtons({ - Key key, - @required this.movie, - }) : super(key: key); - - @override - State createState() => _State(); -} - -class _State extends State { - LunaLoadingState _automaticLoadingState = LunaLoadingState.INACTIVE; - - @override - Widget build(BuildContext context) { - return LunaButtonContainer( - children: [ - LunaButton( - text: 'Automatic', - onTap: () async => _automatic(context), - loadingState: _automaticLoadingState, - ), - LunaButton( - text: 'Interactive', - backgroundColor: LunaColours.orange, - onTap: () async => _manual(context), - ), - ], - ); - } - - Future _automatic(BuildContext context) async { - setState(() => _automaticLoadingState = LunaLoadingState.ACTIVE); - RadarrAPIHelper().automaticSearch(context: context, movieId: widget.movie.id, title: widget.movie.title) - .then((value) { - if(mounted) setState(() { - _automaticLoadingState = value ? LunaLoadingState.INACTIVE : LunaLoadingState.ERROR; - }); - }); - } - - Future _manual(BuildContext context) async => RadarrReleasesRouter().navigateTo(context, movieId: widget.movie.id ?? -1); -} diff --git a/lib/modules/radarr/routes/movie_details/widgets/overview_movie_information_block.dart b/lib/modules/radarr/routes/movie_details/widgets/overview_movie_information_block.dart index 1e93eac6..94621451 100644 --- a/lib/modules/radarr/routes/movie_details/widgets/overview_movie_information_block.dart +++ b/lib/modules/radarr/routes/movie_details/widgets/overview_movie_information_block.dart @@ -18,11 +18,12 @@ class RadarrMovieDetailsOverviewInformationBlock extends StatelessWidget { Widget build(BuildContext context) { return LunaTableCard( content: [ + LunaTableContent(title: 'monitor', body: (movie?.monitored ?? false) ? 'Yes' : 'No'), LunaTableContent(title: 'path', body: movie?.path), LunaTableContent(title: 'quality', body: qualityProfile?.name), LunaTableContent(title: 'availability', body: movie?.lunaMinimumAvailability), - LunaTableContent(title: 'status', body: movie?.status?.readable ?? Constants.TEXT_EMDASH), - LunaTableContent(title: 'tags', body: movie?.lunaTags(tags) ?? Constants.TEXT_EMDASH), + LunaTableContent(title: 'status', body: movie?.status?.readable), + LunaTableContent(title: 'tags', body: movie?.lunaTags(tags)), LunaTableContent(title: '', body: ''), LunaTableContent(title: 'in cinemas', body: movie?.lunaInCinemasOn), LunaTableContent(title: 'digital', body: movie?.lunaDigitalReleaseDate), @@ -32,7 +33,7 @@ class RadarrMovieDetailsOverviewInformationBlock extends StatelessWidget { LunaTableContent(title: 'year', body: movie?.lunaYear), LunaTableContent(title: 'studio', body: movie?.lunaStudio), LunaTableContent(title: 'runtime', body: movie?.lunaRuntime), - LunaTableContent(title: 'rating', body: movie?.certification ?? Constants.TEXT_EMDASH), + LunaTableContent(title: 'rating', body: movie?.certification), LunaTableContent(title: 'genres', body: movie?.lunaGenres), LunaTableContent(title: 'alternate titles', body: movie?.lunaAlternateTitles), ], diff --git a/lib/modules/radarr/routes/movie_details/widgets/page_overview.dart b/lib/modules/radarr/routes/movie_details/widgets/page_overview.dart index 98486ddb..19c2de26 100644 --- a/lib/modules/radarr/routes/movie_details/widgets/page_overview.dart +++ b/lib/modules/radarr/routes/movie_details/widgets/page_overview.dart @@ -36,7 +36,6 @@ class _State extends State with AutomaticKeepAli children: [ RadarrMovieDetailsOverviewDescriptionTile(movie: widget.movie), RadarrMovieDetailsOverviewLinksSection(movie: widget.movie), - RadarrMovieDetailsOverviewDownloadButtons(movie: widget.movie), RadarrMovieDetailsOverviewInformationBlock( movie: widget.movie, qualityProfile: widget.qualityProfile, diff --git a/lib/modules/radarr/routes/releases/widgets/release_tile.dart b/lib/modules/radarr/routes/releases/widgets/release_tile.dart index 3e384d54..a4c513f9 100644 --- a/lib/modules/radarr/routes/releases/widgets/release_tile.dart +++ b/lib/modules/radarr/routes/releases/widgets/release_tile.dart @@ -18,15 +18,17 @@ class _State extends State { LunaLoadingState _downloadState = LunaLoadingState.INACTIVE; @override - Widget build(BuildContext context) => LunaExpandableListTile( - title: widget.release.title, - collapsedSubtitle1: _subtitle1(), - collapsedSubtitle2: _subtitle2(), - collapsedTrailing: _trailing(), - expandedHighlightedNodes: _highlightedNodes(), - expandedTableContent: _tableContent(), - expandedTableButtons: _tableButtons(), - ); + Widget build(BuildContext context) { + return LunaExpandableListTile( + title: widget.release.title, + collapsedSubtitle1: _subtitle1(), + collapsedSubtitle2: _subtitle2(), + collapsedTrailing: _trailing(), + expandedHighlightedNodes: _highlightedNodes(), + expandedTableContent: _tableContent(), + expandedTableButtons: _tableButtons(), + ); + } Widget _trailing() { return LunaIconButton( @@ -92,12 +94,13 @@ class _State extends State { List _tableButtons() { return [ - LunaButton.slim( + LunaButton( + type: LunaButtonType.TEXT, text: 'Download', onTap: _startDownload, loadingState: _downloadState, ), - if(widget.release.rejected) LunaButton.slim( + if(widget.release.rejected) LunaButton.text( text: 'Rejected', backgroundColor: LunaColours.red, onTap: _showWarnings, diff --git a/lib/modules/sabnzbd/widgets/history_tile.dart b/lib/modules/sabnzbd/widgets/history_tile.dart index c8b896f2..6d5f4c9b 100644 --- a/lib/modules/sabnzbd/widgets/history_tile.dart +++ b/lib/modules/sabnzbd/widgets/history_tile.dart @@ -1,11 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:expandable/expandable.dart'; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/sabnzbd.dart'; -class SABnzbdHistoryTile extends StatelessWidget { +class SABnzbdHistoryTile extends StatefulWidget { final SABnzbdHistoryData data; - final ExpandableController _controller = ExpandableController(); final Function() refresh; SABnzbdHistoryTile({ @@ -14,159 +12,104 @@ class SABnzbdHistoryTile extends StatelessWidget { }); @override - Widget build(BuildContext context) => LSExpandable( - controller: _controller, - collapsed: _collapsed(context), - expanded: _expanded(context), - ); + State createState() => _State(); +} - Widget _expanded(BuildContext context) => LSCard( - child: InkWell( - child: Row( - children: [ - Expanded( - child: Padding( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LSTitle( - text: data.name, - softWrap: true, - maxLines: 12, - ), - Padding( - child: Wrap( - direction: Axis.horizontal, - runSpacing: 10.0, - children: [ - LSTextHighlighted( - text: data.status, - bgColor: data.statusColor, - ), - ], - ), - padding: EdgeInsets.only(top: 8.0, bottom: 2.0), - ), - Padding( - child: RichText( - text: TextSpan( - style: TextStyle( - color: Colors.white70, - fontSize: Constants.UI_FONT_SIZE_SUBTITLE, - ), - children: [ - TextSpan(text: data.completeTimeString), - TextSpan(text: '\t•\t'), - TextSpan(text: data.sizeReadable), - TextSpan(text: '\t•\t'), - TextSpan(text: data.category), - TextSpan(text: '\n\n'), - TextSpan( - text: data.storageLocation, - style: TextStyle( - fontStyle: FontStyle.italic, - ), - ), - ], - ), - ), - padding: EdgeInsets.only(top: 6.0, bottom: 10.0), - ), - Padding( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Expanded( - child: LSButtonSlim( - text: 'Stages', - onTap: () async => _enterStages(context), - margin: EdgeInsets.only(right: 6.0), - ), - ), - Expanded( - child: LSButtonSlim( - text: 'Delete', - backgroundColor: LunaColours.red, - onTap: () async => _delete(context), - margin: EdgeInsets.only(left: 6.0), - ), - ), - ], - ), - padding: EdgeInsets.only(bottom: 2.0), - ), - ], - ), - padding: EdgeInsets.symmetric(horizontal: 12.0, vertical: 10.0), - ), - ), - ], +class _State extends State { + + @override + Widget build(BuildContext context) { + return LunaExpandableListTile( + title: widget.data.name, + collapsedSubtitle1: _subtitle1(), + collapsedSubtitle2: _subtitle2(), + expandedTableContent: _expandedTableContent(), + expandedHighlightedNodes: _expandedHighlightedNodes(), + expandedTableButtons: _expandedButtons(), + onLongPress: () async => _handlePopup(), + ); + } + + TextSpan _subtitle1() { + return TextSpan( + children: [ + TextSpan(text: widget.data.completeTimeString), + TextSpan(text: LunaUI.TEXT_BULLET.lunaPad()), + TextSpan(text: widget.data.sizeReadable), + TextSpan(text: LunaUI.TEXT_BULLET.lunaPad()), + TextSpan(text: widget.data.category), + ] + ); + } + + TextSpan _subtitle2() { + return TextSpan( + text: widget.data.statusString, + style: TextStyle( + color: widget.data.statusColor, + fontWeight: LunaUI.FONT_WEIGHT_BOLD, ), - borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), - onTap: () => _controller.toggle(), - onLongPress: () async => _handlePopup(context), - ), - ); + ); + } - Widget _collapsed(BuildContext context) => LSCardTile( - title: LSTitle(text: data.name), - subtitle: RichText( - text: TextSpan( - style: TextStyle( - color: Colors.white70, - fontSize: Constants.UI_FONT_SIZE_SUBTITLE, - ), - children: [ - TextSpan(text: data.completeTimeString), - TextSpan(text: '\t•\t'), - TextSpan(text: data.sizeReadable), - TextSpan(text: '\t•\t'), - TextSpan(text: data.category), - TextSpan(text: '\n'), - TextSpan( - text: data.statusString, - style: TextStyle( - color: data.statusColor, - fontWeight: LunaUI.FONT_WEIGHT_BOLD, - ), - ) - ], + List _expandedTableContent() { + return [ + LunaTableContent(title: 'age', body: widget.data.completeTimeString), + LunaTableContent(title: 'size', body: widget.data.sizeReadable), + LunaTableContent(title: 'category', body: widget.data.category), + LunaTableContent(title: 'path', body: widget.data.storageLocation), + ]; + } + + List _expandedHighlightedNodes() { + return [ + LunaHighlightedNode( + text: widget.data.status, + backgroundColor: widget.data.statusColor, ), - maxLines: 2, - overflow: TextOverflow.fade, - softWrap: false, - ), - padContent: true, - onTap: () => _controller.toggle(), - onLongPress: () async => _handlePopup(context), - ); + ]; + } - Future _enterStages(BuildContext context) async { + List _expandedButtons() { + return [ + LunaButton.text( + text: 'Stages', + onTap: () async => _enterStages(), + ), + LunaButton.text( + text: 'Delete', + backgroundColor: LunaColours.red, + onTap: () async => _delete(), + ), + ]; + } + + Future _enterStages() async { final dynamic result = await Navigator.of(context).pushNamed( SABnzbdHistoryStages.ROUTE_NAME, - arguments: SABnzbdHistoryStagesArguments(data: data), + arguments: SABnzbdHistoryStagesArguments(data: widget.data), ); if(result != null) switch(result[0]) { - case 'delete': _handleRefresh(context, 'History Deleted'); break; + case 'delete': _handleRefresh('History Deleted'); break; default: LunaLogger().warning('SABnzbdHistoryTile', '_enterDetails', 'Unknown Case: ${result[0]}'); } } - Future _handlePopup(BuildContext context) async { - List values = await SABnzbdDialogs.historySettings(context, data.name, data.failed); + Future _handlePopup() async { + List values = await SABnzbdDialogs.historySettings(context, widget.data.name, widget.data.failed); if(values[0]) switch(values[1]) { - case 'retry': _retry(context); break; - case 'password': _password(context); break; - case 'delete': _delete(context); break; + case 'retry': _retry(); break; + case 'password': _password(); break; + case 'delete': _delete(); break; default: LunaLogger().warning('SABnzbdHistoryTile', '_handlePopup', 'Unknown Case: ${values[1]}'); } } - Future _delete(BuildContext context) async { + Future _delete() async { List values = await SABnzbdDialogs.deleteHistory(context); if(values[0]) { - SABnzbdAPI.from(Database.currentProfileObject).deleteHistory(data.nzoId) - .then((_) => _handleRefresh(context, 'History Deleted')) + SABnzbdAPI.from(Database.currentProfileObject).deleteHistory(widget.data.nzoId) + .then((_) => _handleRefresh('History Deleted')) .catchError((_) => LSSnackBar( context: context, title: 'Failed to Delete History', @@ -176,10 +119,10 @@ class SABnzbdHistoryTile extends StatelessWidget { } } - Future _password(BuildContext context) async { + Future _password() async { List values = await SABnzbdDialogs.setPassword(context); - if(values[0]) SABnzbdAPI.from(Database.currentProfileObject).retryFailedJobPassword(data.nzoId, values[1]) - .then((_) => _handleRefresh(context, 'Password Set / Retrying...')) + if(values[0]) SABnzbdAPI.from(Database.currentProfileObject).retryFailedJobPassword(widget.data.nzoId, values[1]) + .then((_) => _handleRefresh('Password Set / Retrying...')) .catchError((_) => LSSnackBar( context: context, title: 'Failed to Set Password / Retry Job', @@ -188,9 +131,9 @@ class SABnzbdHistoryTile extends StatelessWidget { )); } - Future _retry(BuildContext context) async { - SABnzbdAPI.from(Database.currentProfileObject).retryFailedJob(data.nzoId) - .then((_) => _handleRefresh(context, 'Retrying Job')) + Future _retry() async { + SABnzbdAPI.from(Database.currentProfileObject).retryFailedJob(widget.data.nzoId) + .then((_) => _handleRefresh('Retrying Job')) .catchError((_) => LSSnackBar( context: context, title: 'Failed to Retry Job', @@ -199,13 +142,13 @@ class SABnzbdHistoryTile extends StatelessWidget { )); } - void _handleRefresh(BuildContext context, String title) { + void _handleRefresh(String title) { LSSnackBar( context: context, title: title, - message: data.name, + message: widget.data.name, type: SNACKBAR_TYPE.success, ); - refresh(); + widget.refresh(); } } diff --git a/lib/modules/search/routes/results/widgets/result_tile.dart b/lib/modules/search/routes/results/widgets/result_tile.dart index b8642604..6a987620 100644 --- a/lib/modules/search/routes/results/widgets/result_tile.dart +++ b/lib/modules/search/routes/results/widgets/result_tile.dart @@ -49,7 +49,7 @@ class SearchResultTile extends StatelessWidget { List _tableButtons(BuildContext context) { return [ - LunaButton.slim( + LunaButton.text( text: 'search.Download'.tr(), onTap: () async => _sendToClient(context), ), diff --git a/lib/modules/settings/core/pages/headers.dart b/lib/modules/settings/core/pages/headers.dart index 1cc2982f..c2b6c90c 100644 --- a/lib/modules/settings/core/pages/headers.dart +++ b/lib/modules/settings/core/pages/headers.dart @@ -79,7 +79,7 @@ class _State extends State with LunaScrollControllerMixin { Widget _addHeader() { return LunaButtonContainer( children: [ - LunaButton( + LunaButton.text( text: 'Add Header', onTap: () async { await HeaderUtility().addHeader(context, headers: _headers()); diff --git a/lib/modules/settings/routes/account/pages/password_reset.dart b/lib/modules/settings/routes/account/pages/password_reset.dart index 24d470ed..169408ee 100644 --- a/lib/modules/settings/routes/account/pages/password_reset.dart +++ b/lib/modules/settings/routes/account/pages/password_reset.dart @@ -58,10 +58,15 @@ class _State extends State<_SettingsAccountPasswordResetRoute> with LunaScrollCo ), ), ), - LSButton( - text: 'Reset Password', - onTap: _resetPassword, + LunaButtonContainer( + children: [ + LunaButton.text( + text: 'Reset Password', + onTap: _resetPassword, + ), + ], ), + ], ); } diff --git a/lib/modules/settings/routes/account/pages/signed_in.dart b/lib/modules/settings/routes/account/pages/signed_in.dart index 443444f1..47a8552b 100644 --- a/lib/modules/settings/routes/account/pages/signed_in.dart +++ b/lib/modules/settings/routes/account/pages/signed_in.dart @@ -31,7 +31,7 @@ class _State extends State { Widget _signOutButton() { return LunaButtonContainer( children: [ - LunaButton( + LunaButton.text( text: 'Sign Out', backgroundColor: LunaColours.red, onTap: () async { diff --git a/lib/modules/settings/routes/account/pages/signed_out.dart b/lib/modules/settings/routes/account/pages/signed_out.dart index cf4910a9..e5aece30 100644 --- a/lib/modules/settings/routes/account/pages/signed_out.dart +++ b/lib/modules/settings/routes/account/pages/signed_out.dart @@ -66,12 +66,14 @@ class _State extends State { LunaButtonContainer( children: [ LunaButton( + type: LunaButtonType.TEXT, text: 'Register', backgroundColor: LunaColours.blueGrey, onTap: _register, loadingState: _state, ), LunaButton( + type: LunaButtonType.TEXT, text: 'Sign In', onTap: _signIn, loadingState: _state, diff --git a/lib/modules/settings/routes/configuration_lidarr/pages/connection_details.dart b/lib/modules/settings/routes/configuration_lidarr/pages/connection_details.dart index ae4be0ef..c5566a39 100644 --- a/lib/modules/settings/routes/configuration_lidarr/pages/connection_details.dart +++ b/lib/modules/settings/routes/configuration_lidarr/pages/connection_details.dart @@ -89,7 +89,7 @@ class _State extends State<_SettingsConfigurationLidarrRoute> with LunaScrollCon Widget _testConnection() { return LunaButtonContainer( children: [ - LunaButton( + LunaButton.text( text: 'Test Connection', onTap: () async { ProfileHiveObject _profile = Database.currentProfileObject; diff --git a/lib/modules/settings/routes/configuration_nzbget/pages/connection_details.dart b/lib/modules/settings/routes/configuration_nzbget/pages/connection_details.dart index 05c05a2f..745222c9 100644 --- a/lib/modules/settings/routes/configuration_nzbget/pages/connection_details.dart +++ b/lib/modules/settings/routes/configuration_nzbget/pages/connection_details.dart @@ -117,7 +117,7 @@ class _State extends State<_SettingsConfigurationNZBGetRoute> with LunaScrollCon Widget _testConnection() { return LunaButtonContainer( children: [ - LunaButton( + LunaButton.text( text: 'Test Connection', onTap: () async { ProfileHiveObject _profile = Database.currentProfileObject; diff --git a/lib/modules/settings/routes/configuration_radarr/pages/connection_details.dart b/lib/modules/settings/routes/configuration_radarr/pages/connection_details.dart index 03a8e190..b092979c 100644 --- a/lib/modules/settings/routes/configuration_radarr/pages/connection_details.dart +++ b/lib/modules/settings/routes/configuration_radarr/pages/connection_details.dart @@ -89,7 +89,7 @@ class _State extends State<_SettingsConfigurationRadarrRoute> with LunaScrollCon Widget _testConnection() { return LunaButtonContainer( children: [ - LunaButton( + LunaButton.text( text: 'Test Connection', onTap: () async { ProfileHiveObject _profile = Database.currentProfileObject; diff --git a/lib/modules/settings/routes/configuration_sabnzbd/pages/connection_details.dart b/lib/modules/settings/routes/configuration_sabnzbd/pages/connection_details.dart index c22a0903..84e9f762 100644 --- a/lib/modules/settings/routes/configuration_sabnzbd/pages/connection_details.dart +++ b/lib/modules/settings/routes/configuration_sabnzbd/pages/connection_details.dart @@ -89,7 +89,7 @@ class _State extends State<_SettingsConfigurationSABnzbdRoute> with LunaScrollCo Widget _testConnection() { return LunaButtonContainer( children: [ - LunaButton( + LunaButton.text( text: 'Test Connection', onTap: () async { ProfileHiveObject _profile = Database.currentProfileObject; diff --git a/lib/modules/settings/routes/configuration_search/pages/add_indexer.dart b/lib/modules/settings/routes/configuration_search/pages/add_indexer.dart index c2abb3b2..f42f9e6c 100644 --- a/lib/modules/settings/routes/configuration_search/pages/add_indexer.dart +++ b/lib/modules/settings/routes/configuration_search/pages/add_indexer.dart @@ -99,7 +99,7 @@ class _State extends State<_SettingsConfigurationSearchAddRoute> with LunaScroll Widget _addIndexer() { return LunaButtonContainer( children: [ - LunaButton( + LunaButton.text( text: 'Add Indexer', onTap: () async { if(_indexer.displayName.isEmpty || _indexer.host.isEmpty || _indexer.apiKey.isEmpty) { diff --git a/lib/modules/settings/routes/configuration_search/pages/add_indexer_headers.dart b/lib/modules/settings/routes/configuration_search/pages/add_indexer_headers.dart index eea22e86..2a96d582 100644 --- a/lib/modules/settings/routes/configuration_search/pages/add_indexer_headers.dart +++ b/lib/modules/settings/routes/configuration_search/pages/add_indexer_headers.dart @@ -91,7 +91,7 @@ class _State extends State<_SettingsConfigurationSearchAddHeadersRoute> with Lun Widget _addHeader() { return LunaButtonContainer( children: [ - LunaButton( + LunaButton.text( text: 'Add Header', onTap: () async { await HeaderUtility().addHeader(context, headers: _arguments.indexer.headers); diff --git a/lib/modules/settings/routes/configuration_search/pages/edit_indexer.dart b/lib/modules/settings/routes/configuration_search/pages/edit_indexer.dart index b6673027..3700da6f 100644 --- a/lib/modules/settings/routes/configuration_search/pages/edit_indexer.dart +++ b/lib/modules/settings/routes/configuration_search/pages/edit_indexer.dart @@ -128,7 +128,7 @@ class _State extends State<_SettingsConfigurationSearchEditRoute> with LunaScrol Widget _deleteIndexer() { return LunaButtonContainer( children: [ - LunaButton( + LunaButton.text( text: 'Delete Indexer', backgroundColor: LunaColours.red, onTap: () async { diff --git a/lib/modules/settings/routes/configuration_search/pages/edit_indexer_headers.dart b/lib/modules/settings/routes/configuration_search/pages/edit_indexer_headers.dart index abaeb557..6d965319 100644 --- a/lib/modules/settings/routes/configuration_search/pages/edit_indexer_headers.dart +++ b/lib/modules/settings/routes/configuration_search/pages/edit_indexer_headers.dart @@ -92,7 +92,7 @@ class _State extends State<_SettingsConfigurationSearchEditHeadersRoute> with Lu Widget _addHeader() { return LunaButtonContainer( children: [ - LunaButton( + LunaButton.text( text: 'Add Header', onTap: () async => HeaderUtility().addHeader(context, headers: _indexer.headers, indexer: _indexer), ), diff --git a/lib/modules/settings/routes/configuration_search/route.dart b/lib/modules/settings/routes/configuration_search/route.dart index d4ec51af..9f65cd85 100644 --- a/lib/modules/settings/routes/configuration_search/route.dart +++ b/lib/modules/settings/routes/configuration_search/route.dart @@ -57,9 +57,13 @@ class _State extends State<_SettingsConfigurationSearchRoute> with LunaScrollCon List get _indexerSection => [ if(Database.indexersBox.isEmpty) LSGenericMessage(text: 'No Indexers Added'), ..._indexers, - LSButton( - text: 'Add New Indexer', - onTap: () async => SettingsConfigurationSearchAddRouter().navigateTo(context), + LunaButtonContainer( + children: [ + LunaButton.text( + text: 'Add New Indexer', + onTap: () async => SettingsConfigurationSearchAddRouter().navigateTo(context), + ), + ], ), ]; diff --git a/lib/modules/settings/routes/configuration_sonarr/pages/connection_details.dart b/lib/modules/settings/routes/configuration_sonarr/pages/connection_details.dart index 12dd6efb..7ae86188 100644 --- a/lib/modules/settings/routes/configuration_sonarr/pages/connection_details.dart +++ b/lib/modules/settings/routes/configuration_sonarr/pages/connection_details.dart @@ -106,7 +106,7 @@ class _State extends State<_SettingsConfigurationSonarrRoute> with LunaScrollCon Widget _testConnection() { return LunaButtonContainer( children: [ - LunaButton( + LunaButton.text( text: 'Test Connection', onTap: () async { ProfileHiveObject _profile = Database.currentProfileObject; diff --git a/lib/modules/settings/routes/configuration_tautulli/pages/connection_details.dart b/lib/modules/settings/routes/configuration_tautulli/pages/connection_details.dart index e370415c..d2689716 100644 --- a/lib/modules/settings/routes/configuration_tautulli/pages/connection_details.dart +++ b/lib/modules/settings/routes/configuration_tautulli/pages/connection_details.dart @@ -89,7 +89,7 @@ class _State extends State<_SettingsConfigurationTautulliRoute> with LunaScrollC Widget _testConnection() { return LunaButtonContainer( children: [ - LunaButton( + LunaButton.text( text: 'Test Connection', onTap: () async { ProfileHiveObject _profile = Database.currentProfileObject; diff --git a/lib/modules/sonarr/core/dialogs.dart b/lib/modules/sonarr/core/dialogs.dart index 3bb58eee..e8550150 100644 --- a/lib/modules/sonarr/core/dialogs.dart +++ b/lib/modules/sonarr/core/dialogs.dart @@ -3,8 +3,6 @@ import 'package:lunasea/core.dart'; import 'package:lunasea/modules/sonarr.dart'; class SonarrDialogs { - SonarrDialogs._(); - static Future> globalSettings(BuildContext context) async { bool _flag = false; SonarrGlobalSettingsType _value; @@ -167,32 +165,30 @@ class SonarrDialogs { selector: (_, state) => state.tags, builder: (context, future, _) => FutureBuilder( future: future, - builder: (context, AsyncSnapshot> snapshot) => LSDialog.content( - children: (snapshot.data?.length ?? 0) == 0 - ? [ LSDialog.textContent(text: 'No Tags Found') ] - : List.generate( - snapshot.data.length, - (index) => CheckboxListTile( - title: Text( - snapshot.data[index].label, - style: TextStyle( - fontSize: LSDialog.BODY_SIZE, - color: Colors.white, - ), + builder: (context, AsyncSnapshot> snapshot) { + if((snapshot.data?.length ?? 0) == 0) return LSDialog.content( + children: [ + LSDialog.textContent(text: 'No Tags Found'), + ], + ); + return LSDialog.content( + children: List.generate( + snapshot.data.length, + (index) => LSDialog.checkbox( + title: snapshot.data[index].label, + value: context.watch().tags.where((tag) => tag.id == snapshot.data[index].id).length != 0, + onChanged: (selected) { + List _tags = context.read().tags; + selected ? _tags.add(snapshot.data[index]) : _tags.removeWhere((tag) => tag.id == snapshot.data[index].id); + context.read().tags = _tags; + }, ), - value: context.watch().tags.where((tag) => tag.id == snapshot.data[index].id).length != 0, - onChanged: (selected) { - List _tags = context.read().tags; - selected ? _tags.add(snapshot.data[index]) : _tags.removeWhere((tag) => tag.id == snapshot.data[index].id); - context.read().tags = _tags; - }, - contentPadding: LSDialog.tileContentPadding(), ), - ), - ), + ); + }, ), ), - contentPadding: LSDialog.textDialogContentPadding(), + contentPadding: LSDialog.listDialogContentPadding(), shape: LunaUI.shapeBorder, ), ), @@ -218,32 +214,30 @@ class SonarrDialogs { selector: (_, state) => state.tags, builder: (context, future, _) => FutureBuilder( future: future, - builder: (context, AsyncSnapshot> snapshot) => LSDialog.content( - children: (snapshot.data?.length ?? 0) == 0 - ? [ LSDialog.textContent(text: 'No Tags Found') ] - : List.generate( - snapshot.data.length, - (index) => CheckboxListTile( - title: Text( - snapshot.data[index].label, - style: TextStyle( - fontSize: LSDialog.BODY_SIZE, - color: Colors.white, - ), + builder: (context, AsyncSnapshot> snapshot) { + if((snapshot.data?.length ?? 0) == 0) return LSDialog.content( + children: [ + LSDialog.textContent(text: 'No Tags Found'), + ], + ); + return LSDialog.content( + children: List.generate( + snapshot.data.length, + (index) => LSDialog.checkbox( + title: snapshot.data[index].label, + value: context.watch().tags.where((tag) => tag.id == snapshot.data[index].id).length != 0, + onChanged: (selected) { + List _tags = context.read().tags; + selected ? _tags.add(snapshot.data[index]) : _tags.removeWhere((tag) => tag.id == snapshot.data[index].id); + context.read().tags = _tags; + }, ), - value: context.watch().tags.where((tag) => tag.id == snapshot.data[index].id).length != 0, - onChanged: (selected) { - List _tags = context.read().tags; - selected ? _tags.add(snapshot.data[index]) : _tags.removeWhere((tag) => tag.id == snapshot.data[index].id); - context.read().tags = _tags; - }, - contentPadding: LSDialog.tileContentPadding(), ), - ), - ), + ); + }, ), ), - contentPadding: LSDialog.textDialogContentPadding(), + contentPadding: LSDialog.listDialogContentPadding(), shape: LunaUI.shapeBorder, ), ), @@ -530,30 +524,21 @@ class SonarrDialogs { ), ], content: [ - LSDialog.textContent(text: 'Are you sure you want to remove the series from Sonarr?\n'), Selector( selector: (_, state) => state.removeSeriesDeleteFiles, - builder: (context, value, text) => CheckboxListTile( - title: text, + builder: (context, value, _) => LSDialog.checkbox( + title: 'Delete Files', value: value, onChanged: (selected) => Provider.of(context, listen: false).removeSeriesDeleteFiles = selected, - contentPadding: LSDialog.tileContentPadding(), - ), - child: Text( - 'Delete Files', - style: TextStyle( - fontSize: LSDialog.BODY_SIZE, - color: Colors.white, - ), ), ), ], - contentPadding: LSDialog.textDialogContentPadding(), + contentPadding: LSDialog.listDialogContentPadding(), ); return [_flag]; } - static Future> confirmDeleteQueue(BuildContext context) async { + Future confirmDeleteQueue(BuildContext context) async { bool _flag = false; void _setValues(bool flag) { @@ -572,27 +557,18 @@ class SonarrDialogs { ), ], content: [ - LSDialog.textContent(text: 'Are you sure you want to remove this from the queue?\n'), Selector( selector: (_, state) => state.removeQueueBlacklist, - builder: (context, value, text) => CheckboxListTile( - title: text, + builder: (context, value, _) => LSDialog.checkbox( + title: 'Blacklist Release', value: value, onChanged: (selected) => Provider.of(context, listen: false).removeQueueBlacklist = selected, - contentPadding: LSDialog.tileContentPadding(), - ), - child: Text( - 'Blacklist Release', - style: TextStyle( - fontSize: LSDialog.BODY_SIZE, - color: Colors.white, - ), ), ), ], - contentPadding: LSDialog.textDialogContentPadding(), + contentPadding: LSDialog.listDialogContentPadding(), ); - return [_flag]; + return _flag; } static Future> confirmDeleteEpisodeFile(BuildContext context) async { diff --git a/lib/modules/sonarr/core/webhooks.dart b/lib/modules/sonarr/core/webhooks.dart index 793b2007..a6040363 100644 --- a/lib/modules/sonarr/core/webhooks.dart +++ b/lib/modules/sonarr/core/webhooks.dart @@ -50,53 +50,28 @@ extension _EventTypeExtension on _EventType { } } - Future _downloadEvent(Map data) async { - int seriesId = int.tryParse(data['seriesId']); - int seasonNumber = int.tryParse(data['seasonNumber']); - if(seriesId != null && seasonNumber != null) SonarrSeasonDetailsRouter().navigateTo( + Future _downloadEvent(Map data) async => _goToSeasonDetails(int.tryParse(data['seriesId']), int.tryParse(data['seasonNumber'])); + Future _episodeFileDeleteEvent(Map data) async => _goToSeasonDetails(int.tryParse(data['seriesId']), int.tryParse(data['seasonNumber'])); + Future _grabEvent(Map data) async => _goToSeasonDetails(int.tryParse(data['seriesId']), int.tryParse(data['seasonNumber'])); + Future _healthEvent(Map data) async => LunaModule.SONARR.launch(); + Future _renameEvent(Map data) async => _goToSeriesDetails(int.tryParse(data['seriesId'])); + Future _seriesDeleteEvent(Map data) async => LunaModule.SONARR.launch(); + Future _testEvent(Map data) async => LunaModule.SONARR.launch(); + + Future _goToSeriesDetails(int seriesId) async { + if(seriesId != null) return SonarrSeriesDetailsRouter().navigateTo( LunaState.navigatorKey.currentContext, seriesId: seriesId, - seasonNumber: seasonNumber, ); - } - - Future _episodeFileDeleteEvent(Map data) async { - int seriesId = int.tryParse(data['seriesId']); - int seasonNumber = int.tryParse(data['seasonNumber']); - if(seriesId != null && seasonNumber != null) SonarrSeasonDetailsRouter().navigateTo( - LunaState.navigatorKey.currentContext, - seriesId: seriesId, - seasonNumber: seasonNumber, - ); - } - - Future _grabEvent(Map data) async { - int seriesId = int.tryParse(data['seriesId']); - int seasonNumber = int.tryParse(data['seasonNumber']); - if(seriesId != null && seasonNumber != null) SonarrSeasonDetailsRouter().navigateTo( - LunaState.navigatorKey.currentContext, - seriesId: seriesId, - seasonNumber: seasonNumber, - ); - } - - Future _healthEvent(Map data) async { return LunaModule.SONARR.launch(); } - Future _renameEvent(Map data) async { - int seriesId = int.tryParse(data['seriesId']); - if(seriesId != null) SonarrSeriesDetailsRouter().navigateTo( + Future _goToSeasonDetails(int seriesId, int seasonNumber) async { + if(seriesId != null && seasonNumber != null) return SonarrSeasonDetailsRouter().navigateTo( LunaState.navigatorKey.currentContext, seriesId: seriesId, + seasonNumber: seasonNumber, ); - } - - Future _seriesDeleteEvent(Map data) async { - return LunaModule.SONARR.launch(); - } - - Future _testEvent(Map data) async { return LunaModule.SONARR.launch(); } } diff --git a/lib/modules/sonarr/routes/queue/route.dart b/lib/modules/sonarr/routes/queue/route.dart index ffe2447b..39867f82 100644 --- a/lib/modules/sonarr/routes/queue/route.dart +++ b/lib/modules/sonarr/routes/queue/route.dart @@ -60,11 +60,10 @@ class _State extends State<_SonarrQueueRoute> { ), ); - Widget get _emptyQueue => LSGenericMessage( + Widget get _emptyQueue => LunaMessage( text: 'Empty Queue', buttonText: 'Refresh', - showButton: true, - onTapHandler: () async => _refreshKey.currentState.show(), + onTap: _refreshKey.currentState.show, ); Widget _queue(List queue) => LSListView( diff --git a/lib/modules/sonarr/routes/queue/widgets/queue_tile.dart b/lib/modules/sonarr/routes/queue/widgets/queue_tile.dart index ee826a11..43ec4380 100644 --- a/lib/modules/sonarr/routes/queue/widgets/queue_tile.dart +++ b/lib/modules/sonarr/routes/queue/widgets/queue_tile.dart @@ -20,94 +20,104 @@ class _State extends State { final ExpandableController _controller = ExpandableController(); @override - Widget build(BuildContext context) => LSExpandable( - controller: _controller, - collapsed: _collapsed(context), - expanded: _expanded(context), - ); - - Widget _collapsed(BuildContext context) => LSCardTile( - title: LSTitle(text: widget.record.title), - subtitle: RichText( - text: TextSpan( - style: TextStyle( - fontSize: Constants.UI_FONT_SIZE_SUBTITLE, - color: Colors.white70, - ), - children: [ - TextSpan(text: widget?.record?.series?.title ?? 'Unknown Series'), - TextSpan(text: '\nSeason ${widget.record.episode.seasonNumber} ${Constants.TEXT_EMDASH} Episode ${widget.record.episode.episodeNumber}\n'), - TextSpan( - text: '${widget.record?.quality?.quality?.name ?? 'Unknown'} ${Constants.TEXT_EMDASH} ${widget.record?.lunaPercentageComplete ?? Constants.TEXT_EMDASH}%', - style: TextStyle( - fontWeight: LunaUI.FONT_WEIGHT_BOLD, - color: LunaColours.accent, - ), - ), - ], + Widget build(BuildContext context) { + return ExpandableNotifier( + controller: _controller, + child: Expandable( + collapsed: _collapsed(context), + expanded: _expanded(context), ), - maxLines: 3, - overflow: TextOverflow.fade, - softWrap: false, - ), - trailing: Padding( - child: LSIconButton(icon: widget.record.lunaStatusIcon, color: widget.record.lunaStatusColor), - padding: EdgeInsets.only(bottom: 14.0), - ), - padContent: true, - onTap: () => _controller.toggle(), - ); + ); + } - Widget _expanded(BuildContext context) => LSCard( - child: InkWell( - child: Row( - children: [ - Expanded( - child: Padding( + Widget _collapsed(BuildContext context) { + return LunaListTile( + context: context, + title: LunaText.title(text: widget.record.title), + subtitle: RichText( + text: TextSpan( + style: TextStyle( + fontSize: LunaUI.FONT_SIZE_SUBTITLE, + color: Colors.white70, + ), + children: [ + TextSpan(text: widget?.record?.series?.title ?? 'Unknown Series'), + TextSpan(text: '\nSeason ${widget.record.episode.seasonNumber} ${Constants.TEXT_EMDASH} Episode ${widget.record.episode.episodeNumber}\n'), + TextSpan( + text: '${widget.record?.quality?.quality?.name ?? 'Unknown'} ${Constants.TEXT_EMDASH} ${widget.record?.lunaPercentageComplete ?? Constants.TEXT_EMDASH}%', + style: TextStyle( + fontWeight: LunaUI.FONT_WEIGHT_BOLD, + color: LunaColours.accent, + ), + ), + ], + ), + maxLines: 3, + overflow: TextOverflow.fade, + softWrap: false, + ), + trailing: Padding( + child: LunaIconButton(icon: widget.record.lunaStatusIcon, color: widget.record.lunaStatusColor), + padding: EdgeInsets.only(bottom: 12.0), + ), + contentPadding: true, + onTap: () => _controller.toggle(), + ); + } + + Widget _expanded(BuildContext context) { + return LunaCard( + context: context, + child: InkWell( + child: Row( + children: [ + Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LSTitle(text: widget.record.title, softWrap: true, maxLines: 12), + Padding( + child: LunaText.title( + text: widget.record.title, + softWrap: true, + maxLines: 12, + ), + padding: EdgeInsets.only(bottom: 8.0, left: 12.0, right: 12.0, top: 10.0), + ), Padding( child: Wrap( direction: Axis.horizontal, - runSpacing: 10.0, + spacing: 6.0, + runSpacing: 6.0, children: [ - LSTextHighlighted( + LunaHighlightedNode( text: widget.record?.protocol?.lunaCapitalizeFirstLetters() ?? Constants.TEXT_EMDASH, - bgColor: widget.record.protocol == 'torrent' - ? LunaColours.purple - : LunaColours.blue, + backgroundColor: widget.record.protocol == 'torrent' ? LunaColours.purple : LunaColours.blue, ), - LSTextHighlighted( + LunaHighlightedNode( text: widget.record?.quality?.quality?.name ?? 'Unknown', - bgColor: LunaColours.accent, + backgroundColor: LunaColours.accent, ), - LSTextHighlighted( + LunaHighlightedNode( text: '${widget.record?.lunaPercentageComplete ?? Constants.TEXT_EMDASH}%', - bgColor: LunaColours.blueGrey, + backgroundColor: LunaColours.blueGrey, ), - LSTextHighlighted( + LunaHighlightedNode( text: widget.record.lunaStatusText, - bgColor: LunaColours.blueGrey, + backgroundColor: LunaColours.blueGrey, ), ], ), - padding: EdgeInsets.only(top: 8.0, bottom: 2.0), + padding: EdgeInsets.only(bottom: 8.0, left: 12.0, right: 12.0, top: 0.0), ), Padding( child: RichText( text: TextSpan( - style: TextStyle( - color: Colors.white70, - fontSize: Constants.UI_FONT_SIZE_SUBTITLE, - ), children: [ TextSpan( text: '${widget.record.series.title}\n', style: TextStyle( fontWeight: LunaUI.FONT_WEIGHT_BOLD, - fontSize: Constants.UI_FONT_SIZE_STICKYHEADER, + fontSize: LunaUI.FONT_SIZE_BUTTON, color: Colors.white, ), ), @@ -118,72 +128,73 @@ class _State extends State { style: TextStyle( color: LunaColours.accent, fontWeight: LunaUI.FONT_WEIGHT_BOLD, - fontSize: Constants.UI_FONT_SIZE_STICKYHEADER, + fontSize: LunaUI.FONT_SIZE_BUTTON, ), ), TextSpan( + style: TextStyle( + color: Colors.white70, + fontSize: LunaUI.FONT_SIZE_SUBTITLE, + ), text: widget.record.episode.airDateUtc == null ? 'Unknown Date' : DateFormat.yMMMMd().format(widget.record.episode.airDateUtc.toLocal()), ), - TextSpan(text: '\n\n'), - TextSpan( - text: widget.record.episode.overview == null || widget.record.episode.overview.isEmpty - ? 'No overview is available.' - : widget.record.episode.overview, - style: TextStyle( - fontStyle: FontStyle.italic, - ), - ) ], ), ), - padding: EdgeInsets.only(top: 6.0, bottom: 10.0), + padding: EdgeInsets.fromLTRB(12.0, 0.0, 12.0, 4.0), ), Padding( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if(widget.record?.statusMessages != null && (widget.record?.statusMessages?.isNotEmpty ?? false)) Expanded( - child: LSButtonSlim( - text: 'Messages', - backgroundColor: LunaColours.accent, - onTap: () async => LunaDialogs().textPreview(context, 'Warnings', widget.record.statusMessages.fold('', (warnings, status) { - warnings += status.messages.fold('', (message, element) => message += '\n${Constants.TEXT_BULLET} $element'); - return warnings.trim(); - })), - margin: EdgeInsets.only(right: 6.0), - ), + child: RichText( + text: TextSpan( + style: TextStyle( + color: Colors.white70, + fontSize: LunaUI.FONT_SIZE_SUBTITLE, + fontStyle: FontStyle.italic, ), - Expanded( - child: LSButtonSlim( - text: 'Delete', - backgroundColor: LunaColours.red, - onTap: () async => _deleteQueueRecord(context), - margin: widget.record?.statusMessages != null && (widget.record?.statusMessages?.isNotEmpty ?? false) - ? EdgeInsets.only(left: 6.0) - : EdgeInsets.zero, - ), - ), - ], + text: widget.record.episode.overview != null && widget.record.episode.overview.isNotEmpty + ? widget.record.episode.overview + : 'No summary is available.', + ), ), - padding: EdgeInsets.only(bottom: 2.0), + padding: EdgeInsets.fromLTRB(12.0, 2.0, 12.0, 4.0), + ), + LunaButtonContainer( + padding: EdgeInsets.fromLTRB(6.0, 0.0, 6.0, 6.0), + children: [ + if(widget.record.statusMessages != null && widget.record.statusMessages.isNotEmpty) LunaButton.text( + text: 'Messages', + backgroundColor: LunaColours.accent, + onTap: () async { + String messages = widget.record.statusMessages.fold('', (warnings, status) { + warnings += status.messages.fold('', (message, element) => message += '\n${Constants.TEXT_BULLET} $element'); + return warnings.trim(); + }); + LunaDialogs().textPreview(context, 'Messages', messages); + }, + ), + LunaButton.text( + text: 'Delete', + backgroundColor: LunaColours.red, + onTap: () async => _deleteQueueRecord(), + ), + ], ), ], ), - padding: EdgeInsets.symmetric(horizontal: 12.0, vertical: 10.0), - ), - ) - ], + ) + ], + ), + borderRadius: BorderRadius.circular(LunaUI.BORDER_RADIUS), + onTap: () => _controller.toggle(), ), - borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), - onTap: () => _controller.toggle(), - ), - ); + ); + } - Future _deleteQueueRecord(BuildContext context) async { - List _values = await SonarrDialogs.confirmDeleteQueue(context); - if(_values[0]) context.read().api.queue.deleteQueue( + Future _deleteQueueRecord() async { + bool _result = await SonarrDialogs().confirmDeleteQueue(context); + if(_result) context.read().api.queue.deleteQueue( id: widget.record.id, blacklist: context.read().removeQueueBlacklist, ).then((value) { diff --git a/lib/modules/sonarr/routes/releases/route.dart b/lib/modules/sonarr/routes/releases/route.dart index 33d45e9d..ba50dfa9 100644 --- a/lib/modules/sonarr/routes/releases/route.dart +++ b/lib/modules/sonarr/routes/releases/route.dart @@ -124,7 +124,7 @@ class _State extends State<_SonarrReleasesRoute> { if(snapshot.connectionState == ConnectionState.done && snapshot.hasData) return snapshot.data.length == 0 ? _noReleases() : _releases(snapshot.data); - return LSLoader(); + return LunaLoader(); }, ), ); diff --git a/lib/modules/sonarr/routes/releases/widgets/tile_release.dart b/lib/modules/sonarr/routes/releases/widgets/tile_release.dart index 60d09db4..01454af0 100644 --- a/lib/modules/sonarr/routes/releases/widgets/tile_release.dart +++ b/lib/modules/sonarr/routes/releases/widgets/tile_release.dart @@ -1,6 +1,4 @@ -import 'package:expandable/expandable.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/sonarr.dart'; @@ -19,161 +17,104 @@ class SonarrReleasesReleaseTile extends StatefulWidget { } class _State extends State { - final ExpandableController _controller = ExpandableController(); LunaLoadingState _loadingState = LunaLoadingState.INACTIVE; @override - Widget build(BuildContext context) => LSExpandable( - controller: _controller, - collapsed: _collapsed(context), - expanded: _expanded(context), - ); + Widget build(BuildContext context) { + return LunaExpandableListTile( + title: widget.release.title, + collapsedSubtitle1: _subtitle1(), + collapsedSubtitle2: _subtitle2(), + collapsedTrailing: _trailing(), + expandedTableButtons: _tableButtons(), + expandedHighlightedNodes: _highlightedNodes(), + expandedTableContent: _tableContent(), + ); + } - Widget _collapsed(BuildContext context) => LSCardTile( - title: LSTitle(text: widget.release.title), - subtitle: RichText( - text: TextSpan( - style: TextStyle( - color: Colors.white70, - fontSize: Constants.UI_FONT_SIZE_SUBTITLE, - ), - children: [ - TextSpan( - style: TextStyle( - color: widget.release.lunaProtocolColor, - fontWeight: LunaUI.FONT_WEIGHT_BOLD, - ), - text: widget.release.protocol.lunaCapitalizeFirstLetters(), + TextSpan _subtitle1() { + return TextSpan( + children: [ + TextSpan( + style: TextStyle( + color: widget.release.lunaProtocolColor, + fontWeight: LunaUI.FONT_WEIGHT_BOLD, ), - if(widget.release.protocol == 'torrent') TextSpan( - text: ' (${widget.release.seeders}/${widget.release.leechers})', - style: TextStyle( - color: widget.release.lunaProtocolColor, - fontWeight: LunaUI.FONT_WEIGHT_BOLD, - ), - ), - TextSpan(text: '\t•\t${widget.release.indexer}\t•\t'), - TextSpan(text: '${widget.release?.ageHours?.lunaHoursToAge() ?? 'Unknown'}\n'), - TextSpan(text: '${widget.release?.quality?.quality?.name ?? 'Unknown'}\t•\t'), - TextSpan(text: '${widget.release?.size?.lunaBytesToString() ?? 'Unknown'}'), - ] - ), - ), - trailing: _loadingState == LunaLoadingState.ACTIVE - ? IconButton( - icon: SpinKitThreeBounce( - color: Colors.white, - size: 12.0, + text: widget.release.protocol.lunaCapitalizeFirstLetters(), ), - onPressed: null, - ) - : LSIconButton( - icon: widget.release.approved - ? Icons.file_download - : Icons.report_rounded, - color: widget.release.approved - ? Colors.white - : LunaColours.red, - onPressed: _loadingState == LunaLoadingState.ACTIVE ? null : () async => widget.release.approved ? _startDownload(context) : _showWarnings(context), + if(widget.release.protocol == 'torrent') TextSpan( + text: ' (${widget.release.seeders}/${widget.release.leechers})', + style: TextStyle( + color: widget.release.lunaProtocolColor, + fontWeight: LunaUI.FONT_WEIGHT_BOLD, + ), + ), + TextSpan(text: LunaUI.TEXT_BULLET.lunaPad()), + TextSpan(text: widget.release.indexer ?? LunaUI.TEXT_EMDASH), + TextSpan(text: LunaUI.TEXT_BULLET.lunaPad()), + TextSpan(text: widget.release?.ageHours?.lunaHoursToAge() ?? LunaUI.TEXT_EMDASH), + ] + ); + } + + TextSpan _subtitle2() { + return TextSpan( + children: [ + TextSpan(text: widget.release?.quality?.quality?.name ?? LunaUI.TEXT_EMDASH), + TextSpan(text: LunaUI.TEXT_BULLET.lunaPad()), + TextSpan(text: widget.release?.size?.lunaBytesToString() ?? LunaUI.TEXT_EMDASH), + ] + ); + } + + Widget _trailing() { + return LunaIconButton( + icon: widget.release.approved ? Icons.file_download : Icons.report_rounded, + color: widget.release.approved ? Colors.white : LunaColours.red, + onPressed: () async => widget.release.rejected ? _showWarnings() : _startDownload(), + onLongPress: _startDownload, + loadingState: _loadingState, + ); + } + + List _tableButtons() { + return [ + LunaButton( + type: LunaButtonType.TEXT, + text: 'Download', + loadingState: _loadingState, + onTap: () async => _startDownload(), ), - padContent: true, - onTap: () => _controller.toggle(), - ); - - Widget _expanded(BuildContext context) => LSCard( - child: InkWell( - child: Row( - children: [ - Expanded( - child: Padding( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LSTitle(text: widget.release.title, softWrap: true, maxLines: 12), - Padding( - child: Wrap( - direction: Axis.horizontal, - runSpacing: 10.0, - children: [ - LSTextHighlighted( - text: widget.release.protocol.lunaCapitalizeFirstLetters(), - bgColor: widget.release.lunaProtocolColor, - ), - LSTextHighlighted( - text: widget.release?.indexer ?? 'Unknown', - bgColor: LunaColours.blueGrey, - ), - ], - ), - padding: EdgeInsets.only(top: 8.0, bottom: 2.0), - ), - Padding( - child: Column( - children: [ - _tableContent('age', widget.release?.ageHours?.lunaHoursToAge() ?? 'Unknown'), - _tableContent('quality', widget.release?.quality?.quality?.name ?? 'Unknown'), - _tableContent('size', widget.release?.size?.lunaBytesToString() ?? 'Unknown'), - if(widget.release.protocol == 'torrent') _tableContent( - 'statistics', - [ - '${widget.release?.seeders?.toString() ?? 'Unknown'} Seeder${(widget.release?.seeders ?? 0) != 1 ? 's' : ''}', - '${widget.release?.leechers?.toString() ?? 'Unknown'} Leecher${(widget.release?.leechers ?? 0) != 1 ? 's' : ''}', - ].join(' ${Constants.TEXT_BULLET} '), - ), - ], - ), - padding: EdgeInsets.only(top: 6.0, bottom: 10.0), - ), - Padding( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Expanded( - child: LSButtonSlim( - text: 'Download', - widget: _loadingState == LunaLoadingState.ACTIVE - ? LSLoader( - color: Colors.white, - size: 17.0, - ) - : null, - onTap: _loadingState == LunaLoadingState.ACTIVE ? null : () => _startDownload(context), - margin: widget.release.approved - ? EdgeInsets.zero - : EdgeInsets.only(right: 6.0), - ), - ), - if(!widget.release.approved) Expanded( - child: LSButtonSlim( - text: 'Rejected', - backgroundColor: LunaColours.red, - onTap: () => _showWarnings(context), - margin: EdgeInsets.only(left: 6.0), - ), - ), - ], - ), - padding: EdgeInsets.only(bottom: 2.0), - ), - ], - ), - padding: EdgeInsets.symmetric(horizontal: 12.0, vertical: 10.0), - ), - ) - ], + if(!widget.release.approved) LunaButton.text( + text: 'Rejected', + backgroundColor: LunaColours.red, + onTap: () => _showWarnings(), ), - borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), - onTap: () => _controller.toggle(), - ), - ); + ]; + } - Widget _tableContent(String title, String body) => LSTableContent( - title: title, - body: body, - padding: EdgeInsets.symmetric(horizontal: 0.0, vertical: 2.0), - ); + List _tableContent() { + return [ + LunaTableContent(title: 'source', body: widget.release.protocol.lunaCapitalizeFirstLetters()), + LunaTableContent(title: 'age', body: widget.release.ageHours?.lunaHoursToAge() ?? LunaUI.TEXT_EMDASH), + LunaTableContent(title: 'indexer', body: widget.release.indexer ?? LunaUI.TEXT_EMDASH), + LunaTableContent(title: 'size', body: widget.release.size?.lunaBytesToString() ?? LunaUI.TEXT_EMDASH), + LunaTableContent(title: 'quality', body: widget.release.quality?.quality?.name ?? LunaUI.TEXT_EMDASH), + if(widget.release.protocol == 'torrent' && widget.release.seeders != null) LunaTableContent(title: 'seeders', body: '${widget.release.seeders}'), + if(widget.release.protocol == 'torrent' && widget.release.leechers != null) LunaTableContent(title: 'leechers', body: '${widget.release.leechers}'), + ]; + } - Future _startDownload(BuildContext context) async { + List _highlightedNodes() { + return [ + LunaHighlightedNode( + text: widget.release.protocol.lunaCapitalizeFirstLetters(), + backgroundColor: widget.release.lunaProtocolColor, + ), + ]; + } + + Future _startDownload() async { if(mounted) setState(() => _loadingState = LunaLoadingState.ACTIVE); if(context.read().api != null) await context.read().api.release.addRelease( guid: widget.release.guid, @@ -197,5 +138,5 @@ class _State extends State { if(mounted) setState(() => _loadingState = LunaLoadingState.INACTIVE); } - Future _showWarnings(BuildContext context) async => await LunaDialogs().showRejections(context, widget.release.rejections); + Future _showWarnings() async => await LunaDialogs().showRejections(context, widget.release.rejections); } diff --git a/lib/modules/sonarr/routes/season_details/widgets/tile_episode.dart b/lib/modules/sonarr/routes/season_details/widgets/tile_episode.dart index 03762a43..adf792bc 100644 --- a/lib/modules/sonarr/routes/season_details/widgets/tile_episode.dart +++ b/lib/modules/sonarr/routes/season_details/widgets/tile_episode.dart @@ -17,270 +17,282 @@ class SonarrSeasonDetailsEpisodeTile extends StatefulWidget { } class _State extends State { - final ExpandableController _expandableController = ExpandableController(); + final ExpandableController _controller = ExpandableController(); @override - Widget build(BuildContext context) => LSExpandable( - controller: _expandableController, - collapsed: _collapsed, - expanded: _expanded, - ); + Widget build(BuildContext context) { + return FutureBuilder( + future: context.select>>((state) => state.queue), + builder: (context, snapshot) { + SonarrQueueRecord queue; + if(snapshot.hasData) queue = snapshot.data.firstWhere( + (record) => (record?.episode?.id ?? -1) == (widget?.episode?.id ?? -99), + orElse: () => null, + ); + return ExpandableNotifier( + controller: _controller, + child: Expandable( + collapsed: _collapsed(queue), + expanded: _expanded(queue), + ), + ); + }, + ); + } - Widget get _expanded => LSCard( - child: InkWell( - child: Row( - children: [ - Expanded( - child: Padding( + Widget _collapsed(SonarrQueueRecord queue) { + return LunaListTile( + context: context, + title: LunaText.title(text: widget.episode.title), + subtitle: RichText( + text: TextSpan( + style: TextStyle( + fontSize: LunaUI.FONT_SIZE_SUBTITLE, + color: Colors.white70, + ), + children: [ + _subtitle1(), + TextSpan(text: '\n'), + _subtitle2(queue), + ], + ), + maxLines: 2, + overflow: TextOverflow.fade, + softWrap: false, + ), + leading: _leading(), + trailing: _trailing(), + contentPadding: true, + onTap: _controller.toggle, + onLongPress: _handleEpisodeSettings, + color: context.watch().selectedEpisodes.contains(widget.episode.id) + ? LunaColours.accent.withOpacity(0.15) + : null, + ); + } + + Widget _expanded(SonarrQueueRecord queue) { + return LunaCard( + context: context, + child: InkWell( + child: Row( + children: [ + Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LSTitle(text: widget.episode.title, softWrap: true, maxLines: 12), - FutureBuilder( - future: context.watch().queue, - builder: (context, AsyncSnapshot> snapshot) { - SonarrQueueRecord _queue = snapshot.hasData - ? snapshot.data.firstWhere( - (record) => (record?.episode?.id ?? -1) == (widget?.episode?.id ?? -99), - orElse: () => null, - ) - : null; - return Padding( - child: Wrap( - direction: Axis.horizontal, - runSpacing: 10.0, - children: [ - if(!widget.episode.monitored) LSTextHighlighted( - text: 'Unmonitored', - bgColor: LunaColours.red, + Padding( + child: LunaText.title( + text: widget.episode.title, + softWrap: true, + maxLines: 12, + ), + padding: EdgeInsets.only(bottom: 8.0, left: 12.0, right: 12.0, top: 10.0), + ), + Padding( + child: Wrap( + direction: Axis.horizontal, + spacing: 6.0, + runSpacing: 6.0, + children: _highlightedNodes(queue), + ), + padding: EdgeInsets.only(bottom: 8.0, left: 12.0, right: 12.0, top: 0.0), + ), + Padding( + child: RichText( + text: TextSpan( + children: [ + TextSpan( + text: widget.episode.seasonNumber == 0 + ? 'Specials ${Constants.TEXT_EMDASH} Episode ${widget.episode.episodeNumber}' + : 'Season ${widget.episode.seasonNumber} ${Constants.TEXT_EMDASH} Episode ${widget.episode.episodeNumber}', + style: TextStyle( + color: LunaColours.accent, + fontWeight: LunaUI.FONT_WEIGHT_BOLD, + fontSize: LunaUI.FONT_SIZE_BUTTON, ), - if(_queue != null) LSTextHighlighted( - text: '${_queue?.quality?.quality?.name ?? 'Unknown'} ${Constants.TEXT_EMDASH} ${_queue.lunaPercentageComplete}%', - bgColor: LunaColours.purple, + ), + TextSpan(text: '\n'), + TextSpan( + style: TextStyle( + color: Colors.white70, + fontSize: LunaUI.FONT_SIZE_SUBTITLE, ), - if(_queue == null && widget.episode.hasFile) LSTextHighlighted( - bgColor: widget.episode.episodeFile.qualityCutoffNotMet - ? LunaColours.orange - : LunaColours.accent, - text: [ - widget.episode.episodeFile.quality.quality.name, - ' ${Constants.TEXT_EMDASH} ', - widget.episode.episodeFile.size.lunaBytesToString(), - ].join(), - ), - if(_queue == null && !widget.episode.hasFile && (widget.episode?.airDateUtc?.toLocal()?.isAfter(DateTime.now()) ?? true)) LSTextHighlighted( - bgColor: LunaColours.blue, - text: 'Unaired', - ), - if(_queue == null && !widget.episode.hasFile && (widget.episode?.airDateUtc?.toLocal()?.isBefore(DateTime.now()) ?? false)) LSTextHighlighted( - bgColor: LunaColours.red, - text: 'Missing', - ), - ], - ), - padding: EdgeInsets.only(top: 8.0, bottom: 2.0), - ); - }, + text: widget.episode.airDateUtc == null + ? 'Unknown Date' + : DateFormat.yMMMMd().format(widget.episode.airDateUtc.toLocal()), + ), + ], + ), + ), + padding: EdgeInsets.fromLTRB(12.0, 0.0, 12.0, 4.0), ), Padding( child: RichText( text: TextSpan( style: TextStyle( color: Colors.white70, - fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + fontSize: LunaUI.FONT_SIZE_SUBTITLE, + fontStyle: FontStyle.italic, ), - children: [ - TextSpan( - text: widget.episode.seasonNumber == 0 - ? 'Specials ${Constants.TEXT_EMDASH} Episode ${widget.episode.episodeNumber}\n' - : 'Season ${widget.episode.seasonNumber} ${Constants.TEXT_EMDASH} Episode ${widget.episode.episodeNumber}\n', - style: TextStyle( - color: LunaColours.accent, - fontWeight: LunaUI.FONT_WEIGHT_BOLD, - fontSize: Constants.UI_FONT_SIZE_STICKYHEADER, - ), - ), - TextSpan( - text: widget.episode.airDateUtc == null - ? 'Unknown Date' - : DateFormat.yMMMMd().format(widget.episode.airDateUtc.toLocal()), - ), - TextSpan(text: '\n\n'), - TextSpan( - text: widget.episode.overview == null || widget.episode.overview.isEmpty - ? 'No overview is available.' - : widget.episode.overview, - style: TextStyle( - fontStyle: FontStyle.italic, - ), - ) - ], + text: widget.episode.overview != null && widget.episode.overview.isNotEmpty + ? widget.episode.overview + : 'No summary is available.', ), ), - padding: EdgeInsets.only(top: 6.0, bottom: 10.0), + padding: EdgeInsets.fromLTRB(12.0, 2.0, 12.0, 4.0), ), - Padding( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Expanded( - child: LSButtonSlim( - text: 'Automatic', - onTap: _automaticSearch, - margin: EdgeInsets.only(right: 6.0), - ), - ), - Expanded( - child: LSButtonSlim( - text: 'Interactive', - backgroundColor: LunaColours.orange, - onTap: _interactiveSearch, - margin: EdgeInsets.only(left: 6.0), - ), - ), - ], - ), - padding: EdgeInsets.only(bottom: 2.0), + LunaButtonContainer( + padding: EdgeInsets.fromLTRB(6.0, 0.0, 6.0, 6.0), + children: _buttons(), ), ], ), - padding: EdgeInsets.symmetric(horizontal: 12.0, vertical: 10.0), - ), - ) - ], - ), - borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), - onTap: () => _expandableController.toggle(), - onLongPress: () => _handleEpisodeSettings(), - ), - color: context.watch().selectedEpisodes.contains(widget.episode.id) - ? LunaColours.accent.withOpacity(0.15) - : LunaDatabaseValue.THEME_AMOLED.data ? Colors.black : LunaColours.secondary, - ); - - - Widget get _collapsed => LSCardTile( - title: LSTitle(text: widget.episode.title, darken: !widget.episode.monitored), - subtitle: _subtitle, - leading: _leading, - trailing: _trailing, - padContent: true, - onTap: () => _expandableController.toggle(), - onLongPress: () async => _handleEpisodeSettings(), - color: context.watch().selectedEpisodes.contains(widget.episode.id) - ? LunaColours.accent.withOpacity(0.15) - : LunaDatabaseValue.THEME_AMOLED.data ? Colors.black : LunaColours.secondary, - ); - - Widget get _subtitle => FutureBuilder( - future: context.watch().queue, - builder: (context, AsyncSnapshot> snapshot) { - SonarrQueueRecord _queue = snapshot.hasData - ? snapshot.data.firstWhere( - (record) => (record?.episode?.id ?? -1) == (widget?.episode?.id ?? -99), - orElse: () => null, - ) - : null; - return RichText( - text: TextSpan( - style: TextStyle( - fontSize: Constants.UI_FONT_SIZE_SUBTITLE, - color: widget.episode.monitored ? Colors.white70 : Colors.white30, - ), - children: [ - TextSpan( - text: widget.episode.airDateUtc == null - ? 'Unknown Date' - : DateFormat.yMMMMd().format(widget.episode.airDateUtc.toLocal()), - ), - TextSpan(text: '\n'), - if(_queue != null) TextSpan( - style: TextStyle( - fontWeight: LunaUI.FONT_WEIGHT_BOLD, - color: LunaColours.purple, - ), - children: [ - TextSpan(text: _queue?.quality?.quality?.name ?? 'Unknown'), - TextSpan(text: ' ${Constants.TEXT_EMDASH} '), - TextSpan(text: '${_queue.lunaPercentageComplete}%'), - ], - ), - if(_queue == null && widget.episode.hasFile) TextSpan( - style: TextStyle( - fontWeight: LunaUI.FONT_WEIGHT_BOLD, - color: widget.episode.episodeFile.qualityCutoffNotMet - ? widget.episode.monitored ? LunaColours.orange : LunaColours.orange.withOpacity(0.30) - : widget.episode.monitored ? LunaColours.accent : LunaColours.accent.withOpacity(0.30), - ), - children: [ - TextSpan(text: widget.episode.episodeFile.quality.quality.name), - TextSpan(text: ' ${Constants.TEXT_EMDASH} '), - TextSpan(text: widget.episode.episodeFile.size.lunaBytesToString()), - ], - ), - if(_queue == null && !widget.episode.hasFile && (widget.episode?.airDateUtc?.toLocal()?.isAfter(DateTime.now()) ?? true)) TextSpan( - style: TextStyle( - fontWeight: LunaUI.FONT_WEIGHT_BOLD, - color: widget.episode.monitored ? LunaColours.blue : LunaColours.blue.withOpacity(0.30), - ), - text: 'Unaired', - ), - if(_queue == null && !widget.episode.hasFile && (widget.episode?.airDateUtc?.toLocal()?.isBefore(DateTime.now()) ?? false)) TextSpan( - style: TextStyle( - fontWeight: LunaUI.FONT_WEIGHT_BOLD, - color: widget.episode.monitored ? LunaColours.red : LunaColours.red.withOpacity(0.30), - ), - text: 'Missing', ), ], ), - overflow: TextOverflow.fade, - softWrap: false, - maxLines: 2, - ); - }, - ); - - Widget get _leading => IconButton( - icon: context.watch().selectedEpisodes.contains(widget.episode.id) - ? Icon(Icons.check) - : Text( - '${widget.episode.episodeNumber}', - textAlign: TextAlign.center, - style: TextStyle( - color: widget.episode.monitored - ? Colors.white - : Colors.white30, - fontWeight: LunaUI.FONT_WEIGHT_BOLD, - fontSize: Constants.UI_FONT_SIZE_SUBTITLE, - ), + borderRadius: BorderRadius.circular(LunaUI.BORDER_RADIUS), + onTap: _controller.toggle, + onLongPress: _handleEpisodeSettings, ), - onPressed: () => context.read().toggleSelectedEpisode(widget.episode.id), - ); + color: context.watch().selectedEpisodes.contains(widget.episode.id) + ? LunaColours.accent.withOpacity(0.15) + : null, + ); + } - Widget get _trailing => LSIconButton( - icon: Icons.search, - color: widget.episode.monitored - ? Colors.white - : Colors.white30, - onPressed: _automaticSearch, - onLongPress: _interactiveSearch, - ); + TextSpan _subtitle1() { + return TextSpan(text: widget.episode.airDateUtc == null + ? 'Unknown Date' + : DateFormat.yMMMMd().format(widget.episode.airDateUtc.toLocal()), + ); + } + + TextSpan _subtitle2(SonarrQueueRecord queue) { + return TextSpan( + children: [ + if(queue != null) TextSpan( + style: TextStyle( + fontWeight: LunaUI.FONT_WEIGHT_BOLD, + color: LunaColours.purple, + ), + children: [ + TextSpan(text: queue?.quality?.quality?.name ?? LunaUI.TEXT_EMDASH), + TextSpan(text: Constants.TEXT_EMDASH.lunaPad()), + TextSpan(text: '${queue.lunaPercentageComplete}%'), + ], + ), + if(queue == null && widget.episode.hasFile) TextSpan( + style: TextStyle( + fontWeight: LunaUI.FONT_WEIGHT_BOLD, + color: widget.episode.episodeFile.qualityCutoffNotMet + ? widget.episode.monitored ? LunaColours.orange : LunaColours.orange.withOpacity(0.30) + : widget.episode.monitored ? LunaColours.accent : LunaColours.accent.withOpacity(0.30), + ), + children: [ + TextSpan(text: widget.episode.episodeFile.quality.quality.name), + TextSpan(text: Constants.TEXT_EMDASH.lunaPad()), + TextSpan(text: widget.episode.episodeFile.size.lunaBytesToString()), + ], + ), + if(queue == null && !widget.episode.hasFile && (widget.episode?.airDateUtc?.toLocal()?.isAfter(DateTime.now()) ?? true)) TextSpan( + style: TextStyle( + fontWeight: LunaUI.FONT_WEIGHT_BOLD, + color: widget.episode.monitored ? LunaColours.blue : LunaColours.blue.withOpacity(0.30), + ), + text: 'Unaired', + ), + if(queue == null && !widget.episode.hasFile && (widget.episode?.airDateUtc?.toLocal()?.isBefore(DateTime.now()) ?? false)) TextSpan( + style: TextStyle( + fontWeight: LunaUI.FONT_WEIGHT_BOLD, + color: widget.episode.monitored ? LunaColours.red : LunaColours.red.withOpacity(0.30), + ), + text: 'Missing', + ), + ], + ); + } + + List _highlightedNodes(SonarrQueueRecord queue) { + return [ + if(!widget.episode.monitored) LunaHighlightedNode( + text: 'Unmonitored', + backgroundColor: LunaColours.red, + ), + if(queue != null) LunaHighlightedNode( + text: '${queue?.quality?.quality?.name ?? LunaUI.TEXT_EMDASH} ${LunaUI.TEXT_EMDASH} ${queue.lunaPercentageComplete}%', + backgroundColor: LunaColours.purple, + ), + if(queue == null && widget.episode.hasFile) LunaHighlightedNode( + backgroundColor: widget.episode.episodeFile.qualityCutoffNotMet ? LunaColours.orange : LunaColours.accent, + text: widget.episode.episodeFile.quality?.quality?.name ?? LunaUI.TEXT_EMDASH, + ), + if(queue == null && widget.episode.hasFile) LunaHighlightedNode( + backgroundColor: LunaColours.blueGrey, + text: widget.episode.episodeFile.size?.lunaBytesToString() ?? LunaUI.TEXT_EMDASH, + ), + if(queue == null && !widget.episode.hasFile && (widget.episode?.airDateUtc?.toLocal()?.isAfter(DateTime.now()) ?? true)) LunaHighlightedNode( + backgroundColor: LunaColours.blue, + text: 'Unaired', + ), + if(queue == null && !widget.episode.hasFile && (widget.episode?.airDateUtc?.toLocal()?.isBefore(DateTime.now()) ?? false)) LunaHighlightedNode( + backgroundColor: LunaColours.red, + text: 'Missing', + ), + ]; + } + + List _buttons() { + return [ + LunaButton.text( + text: 'Automatic', + onTap: _automaticSearch, + ), + LunaButton.text( + text: 'Interactive', + backgroundColor: LunaColours.orange, + onTap: _interactiveSearch, + ), + ]; + } + + Widget _leading() { + return IconButton( + icon: context.watch().selectedEpisodes.contains(widget.episode.id) + ? Icon(Icons.check) + : Text( + widget.episode.episodeNumber?.toString() ?? LunaUI.TEXT_EMDASH, + textAlign: TextAlign.center, + style: TextStyle( + color: widget.episode.monitored ? Colors.white : Colors.white30, + fontWeight: LunaUI.FONT_WEIGHT_BOLD, + fontSize: LunaUI.FONT_SIZE_SUBTITLE, + ), + ), + onPressed: () => context.read().toggleSelectedEpisode(widget.episode.id), + ); + } + + Widget _trailing() { + return LunaIconButton( + icon: Icons.search, + color: widget.episode.monitored ? Colors.white : Colors.white30, + onPressed: _automaticSearch, + onLongPress: _interactiveSearch, + ); + } Future _automaticSearch() async { Provider.of(context, listen: false).api.command.episodeSearch(episodeIds: [widget.episode.id]) - .then((_) => LSSnackBar( - context: context, + .then((_) => showLunaSuccessSnackBar( title: 'Searching for Episode...', message: widget.episode.title, - type: SNACKBAR_TYPE.success, )) .catchError((error, stack) { LunaLogger().error('Failed to search for episode: ${widget.episode.id}', error, stack); - LSSnackBar( - context: context, + showLunaErrorSnackBar( title: 'Failed to Search', - type: SNACKBAR_TYPE.failure, + error: error, ); }); } @@ -306,19 +318,16 @@ class _State extends State { episodeFileId: widget.episode.episodeFileId, ).then((_) { setState(() => widget.episode.hasFile = false); - LSSnackBar( - context: context, + showLunaSuccessSnackBar( title: 'Deleted Episode File', message: widget.episode.title, - type: SNACKBAR_TYPE.success, ); }) .catchError((error, stack) { LunaLogger().error('Failed to delete episode file: ${widget.episode.episodeFileId}', error, stack); - LSSnackBar( - context: context, + showLunaErrorSnackBar( title: 'Failed to Delete Episode File', - type: SNACKBAR_TYPE.failure, + error: error, ); }); } @@ -329,23 +338,16 @@ class _State extends State { if(context.read().api != null) context.read().api.episode.updateEpisode(episode: _episode) .then((_) { setState(() => widget.episode.monitored = _episode.monitored); - LSSnackBar( - context: context, - title: _episode.monitored - ? 'Monitoring' - : 'No Longer Monitoring', + showLunaSuccessSnackBar( + title: _episode.monitored ? 'Monitoring' : 'No Longer Monitoring', message: _episode.title, - type: SNACKBAR_TYPE.success, ); }) .catchError((error, stack) { LunaLogger().error('Failed to set episode monitored state: ${_episode.id}, ${_episode.monitored}', error, stack); - LSSnackBar( - context: context, - title: _episode.monitored - ? 'Failed to Start Monitoring' - : 'Failed to Stop Monitoring', - type: SNACKBAR_TYPE.failure, + showLunaErrorSnackBar( + title: _episode.monitored ? 'Failed to Start Monitoring' : 'Failed to Stop Monitoring', + error: error, ); }); } diff --git a/lib/modules/tautulli/core/extensions/tautulli_activity.dart b/lib/modules/tautulli/core/extensions/tautulli_activity.dart index b166cc43..7f8996a8 100644 --- a/lib/modules/tautulli/core/extensions/tautulli_activity.dart +++ b/lib/modules/tautulli/core/extensions/tautulli_activity.dart @@ -13,7 +13,7 @@ extension TautulliActivityExtension on TautulliActivity { value += [ if((this.streamCountDirectPlay ?? 0) > 0) '${this.streamCountDirectPlay} ${this.streamCountDirectPlay == 1 ? 'tautulli.DirectPlay'.tr() : 'tautulli.DirectPlays'.tr()}', if((this.streamCountDirectStream ?? 0) > 0) '${this.streamCountDirectStream} ${this.streamCountDirectStream == 1 ? 'tautulli.DirectStream'.tr() : 'tautulli.DirectStreams'.tr()}', - if((this.streamCountTranscode ?? 0) > 0) '${this.streamCountTranscode} ${this.streamCountTranscode == 1 ? 'tautulli.Transcode'.tr() : 'tautulli.Transcodes.tr()'}', + if((this.streamCountTranscode ?? 0) > 0) '${this.streamCountTranscode} ${this.streamCountTranscode == 1 ? 'tautulli.Transcode'.tr() : 'tautulli.Transcodes'.tr()}', ].join(', '); return value; } diff --git a/lib/modules/tautulli/core/webhooks.dart b/lib/modules/tautulli/core/webhooks.dart index 7f41a92f..5879d814 100644 --- a/lib/modules/tautulli/core/webhooks.dart +++ b/lib/modules/tautulli/core/webhooks.dart @@ -15,72 +15,110 @@ class TautulliWebhooks extends LunaWebhooks { } enum _EventType { + BUFFFER_WARNING, PLAYBACK_ERROR, PLAYBACK_PAUSE, PLAYBACK_RESUME, PLAYBACK_START, PLAYBACK_STOP, + PLEX_REMOTE_ACCESS_BACK_UP, + PLEX_REMOTE_ACCESS_DOWN, + PLEX_SERVER_BACK_UP, + PLEX_SERVER_DOWN, + PLEX_UPDATE_AVAILABLE, + RECENTLY_ADDED, + TAUTULLI_DATABASE_CORRUPTION, + TAUTULLI_UPDATE_AVAILABLE, + TRANSCODE_DECISION_CHANGE, + USER_CONCURRENT_STREAMS, + USER_NEW_DEVICE, + WATCHED, } extension _EventTypeExtension on _EventType { _EventType fromKey(String key) { switch(key) { + case 'BufferWarning': return _EventType.BUFFFER_WARNING; case 'PlaybackError': return _EventType.PLAYBACK_ERROR; case 'PlaybackPause': return _EventType.PLAYBACK_PAUSE; case 'PlaybackResume': return _EventType.PLAYBACK_RESUME; case 'PlaybackStart': return _EventType.PLAYBACK_START; case 'PlaybackStop': return _EventType.PLAYBACK_STOP; + case 'PlexRemoteAccessBackUp': return _EventType.PLEX_REMOTE_ACCESS_BACK_UP; + case 'PlexRemoteAccessDown': return _EventType.PLEX_REMOTE_ACCESS_DOWN; + case 'PlexServerBackUp': return _EventType.PLEX_SERVER_BACK_UP; + case 'PlexServerDown': return _EventType.PLEX_SERVER_DOWN; + case 'PlexUpdateAvailable': return _EventType.PLEX_UPDATE_AVAILABLE; + case 'RecentlyAdded': return _EventType.RECENTLY_ADDED; + case 'TautulliDatabaseCorruption': return _EventType.TAUTULLI_DATABASE_CORRUPTION; + case 'TautulliUpdateAvailable': return _EventType.TAUTULLI_UPDATE_AVAILABLE; + case 'TranscodeDecisionChange': return _EventType.TRANSCODE_DECISION_CHANGE; + case 'UserConcurrentStreams': return _EventType.USER_CONCURRENT_STREAMS; + case 'UserNewDevice': return _EventType.USER_NEW_DEVICE; + case 'Watched': return _EventType.WATCHED; } return null; } Future execute(Map data) async { switch(this) { + case _EventType.BUFFFER_WARNING: return _bufferWarning(data); case _EventType.PLAYBACK_ERROR: return _playbackErrorEvent(data); case _EventType.PLAYBACK_PAUSE: return _playbackPauseEvent(data); case _EventType.PLAYBACK_RESUME: return _playbackResumeEvent(data); case _EventType.PLAYBACK_START: return _playbackStartEvent(data); case _EventType.PLAYBACK_STOP: return _playbackStopEvent(data); + case _EventType.PLEX_REMOTE_ACCESS_BACK_UP: return _plexRemoteAccessBackUp(data); + case _EventType.PLEX_REMOTE_ACCESS_DOWN: return _plexRemoteAccessDown(data); + case _EventType.PLEX_SERVER_BACK_UP: return _plexServerBackUp(data); + case _EventType.PLEX_SERVER_DOWN: return _plexServerDown(data); + case _EventType.PLEX_UPDATE_AVAILABLE: return _plexUpdateAvailable(data); + case _EventType.RECENTLY_ADDED: return _recentlyAdded(data); + case _EventType.TAUTULLI_DATABASE_CORRUPTION: return _tautulliDatabaseCorruption(data); + case _EventType.TAUTULLI_UPDATE_AVAILABLE: return _tautulliUpdateAvailable(data); + case _EventType.TRANSCODE_DECISION_CHANGE: return _transcodeDecisionChangeEvent(data); + case _EventType.USER_CONCURRENT_STREAMS: return _userConcurrentStreams(data); + case _EventType.USER_NEW_DEVICE: return _userNewDevice(data); + case _EventType.WATCHED: return _watchedEvent(data); } } - Future _playbackErrorEvent(Map data) async { - int userId = int.tryParse(data['user_id']); + Future _bufferWarning(Map data) async => _goToActivityDetails(data['session_id']); + Future _playbackErrorEvent(Map data) async => _goToUserDetails(int.tryParse(data['user_id'])); + Future _playbackPauseEvent(Map data) async => _goToActivityDetails(data['session_id']); + Future _playbackResumeEvent(Map data) async => _goToActivityDetails(data['session_id']); + Future _playbackStartEvent(Map data) async => _goToActivityDetails(data['session_id']); + Future _playbackStopEvent(Map data) async => _goToUserDetails(int.tryParse(data['user_id'])); + Future _plexRemoteAccessBackUp(Map data) async => TautulliLogsRouter().navigateTo(LunaState.navigatorKey.currentContext); + Future _plexRemoteAccessDown(Map data) async => TautulliLogsRouter().navigateTo(LunaState.navigatorKey.currentContext); + Future _plexServerBackUp(Map data) async => TautulliLogsRouter().navigateTo(LunaState.navigatorKey.currentContext); + Future _plexServerDown(Map data) async => TautulliLogsRouter().navigateTo(LunaState.navigatorKey.currentContext); + Future _plexUpdateAvailable(Map data) async => TautulliCheckForUpdatesRouter().navigateTo(LunaState.navigatorKey.currentContext); + Future _recentlyAdded(Map data) async => TautulliRecentlyAddedRouter().navigateTo(LunaState.navigatorKey.currentContext); + Future _tautulliDatabaseCorruption(Map data) async => TautulliLogsRouter().navigateTo(LunaState.navigatorKey.currentContext); + Future _tautulliUpdateAvailable(Map data) async => TautulliCheckForUpdatesRouter().navigateTo(LunaState.navigatorKey.currentContext); + Future _transcodeDecisionChangeEvent(Map data) async => _goToActivityDetails(data['session_id']); + Future _userConcurrentStreams(Map data) async => _goToUserDetails(int.tryParse(data['user_id'])); + Future _userNewDevice(Map data) async => _goToUserDetails(int.tryParse(data['user_id'])); + Future _watchedEvent(Map data) async => _goToUserDetails(int.tryParse(data['user_id'])); + + Future _goToHome() async { + return LunaModule.TAUTULLI.launch(); + } + + Future _goToUserDetails(int userId) async { if(userId != null) return TautulliUserDetailsRouter().navigateTo( LunaState.navigatorKey.currentContext, userId: userId, ); + return _goToHome(); } - Future _playbackPauseEvent(Map data) async { - String sessionId = data['session_id']; - if(sessionId != null) return TautulliActivityDetailsRouter().navigateTo( + Future _goToActivityDetails(String sessionId) async { + if(sessionId != null && sessionId.isNotEmpty) return TautulliActivityDetailsRouter().navigateTo( LunaState.navigatorKey.currentContext, sessionId: sessionId, ); + return _goToHome(); } - - Future _playbackResumeEvent(Map data) async { - String sessionId = data['session_id']; - if(sessionId != null) return TautulliActivityDetailsRouter().navigateTo( - LunaState.navigatorKey.currentContext, - sessionId: sessionId, - ); - } - - Future _playbackStartEvent(Map data) async { - String sessionId = data['session_id']; - if(sessionId != null) return TautulliActivityDetailsRouter().navigateTo( - LunaState.navigatorKey.currentContext, - sessionId: sessionId, - ); - } - - Future _playbackStopEvent(Map data) async { - int userId = int.tryParse(data['user_id']); - if(userId != null) return TautulliUserDetailsRouter().navigateTo( - LunaState.navigatorKey.currentContext, - userId: userId, - ); - } -} \ No newline at end of file +} diff --git a/lib/modules/tautulli/routes/activity_details/widgets/button_terminate_session.dart b/lib/modules/tautulli/routes/activity_details/widgets/button_terminate_session.dart index cc1af6b2..2dc862ac 100644 --- a/lib/modules/tautulli/routes/activity_details/widgets/button_terminate_session.dart +++ b/lib/modules/tautulli/routes/activity_details/widgets/button_terminate_session.dart @@ -15,7 +15,7 @@ class TautulliActivityDetailsTerminateSessionButton extends StatelessWidget { Widget build(BuildContext context) { return LunaButtonContainer( children: [ - LunaButton( + LunaButton.text( text: 'tautulli.TerminateSession'.tr(), backgroundColor: LunaColours.red, onTap: () async { diff --git a/lib/modules/tautulli/routes/logs_plex_media_scanner/widgets/log_tile.dart b/lib/modules/tautulli/routes/logs_plex_media_scanner/widgets/log_tile.dart index 441170cd..0495a5ad 100644 --- a/lib/modules/tautulli/routes/logs_plex_media_scanner/widgets/log_tile.dart +++ b/lib/modules/tautulli/routes/logs_plex_media_scanner/widgets/log_tile.dart @@ -1,11 +1,9 @@ -import 'package:expandable/expandable.dart'; import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; import 'package:tautulli/tautulli.dart'; class TautulliLogsPlexMediaScannerLogTile extends StatelessWidget { final TautulliPlexLog log; - final ExpandableController _controller = ExpandableController(); TautulliLogsPlexMediaScannerLogTile({ Key key, @@ -13,77 +11,31 @@ class TautulliLogsPlexMediaScannerLogTile extends StatelessWidget { }) : super(key: key); @override - Widget build(BuildContext context) => LSExpandable( - controller: _controller, - collapsed: _collapsed(context), - expanded: _expanded(context), - ); + Widget build(BuildContext context) { + return LunaExpandableListTile( + title: log.message.trim(), + collapsedSubtitle1: _subtitle1(), + collapsedSubtitle2: _subtitle2(), + expandedTableContent: _tableContent(), + ); + } - Widget _collapsed(BuildContext context) => LSCardTile( - title: LSTitle(text: log.message.trim()), - subtitle: _subtitle, - padContent: true, - onTap: () => _controller.toggle(), - ); + TextSpan _subtitle1() => TextSpan(text: log.timestamp ?? LunaUI.TEXT_EMDASH); - Widget get _subtitle => RichText( - text: TextSpan( + TextSpan _subtitle2() { + return TextSpan( + text: log.level ?? LunaUI.TEXT_EMDASH, style: TextStyle( - color: Colors.white70, - fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + color: LunaColours.accent, + fontWeight: LunaUI.FONT_WEIGHT_BOLD, ), - children: [ - TextSpan(text: '${log.level}\n'), - TextSpan( - text: log.timestamp, - style: TextStyle( - color: LunaColours.accent, - fontWeight: LunaUI.FONT_WEIGHT_BOLD, - ) - ), - ], - ), - softWrap: false, - overflow: TextOverflow.fade, - maxLines: 2, - ); - - Widget _expanded(BuildContext context) => LSCard( - child: InkWell( - child: Row( - children: [ - Expanded( - child: Padding( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LSTitle(text: log.message.trim(), softWrap: true, maxLines: 12), - Padding( - child: Wrap( - direction: Axis.horizontal, - runSpacing: 10.0, - children: [ - LSTextHighlighted( - text: log.level, - bgColor: LunaColours.blue, - ), - LSTextHighlighted( - text: log.timestamp, - bgColor: LunaColours.accent, - ), - ], - ), - padding: EdgeInsets.only(top: 8.0, bottom: 2.0), - ), - ], - ), - padding: EdgeInsets.symmetric(horizontal: 12.0, vertical: 10.0), - ), - ), - ], - ), - borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), - onTap: () => _controller.toggle(), - ), - ); + ); + } + + List _tableContent() { + return [ + LunaTableContent(title: 'level', body: log.level), + LunaTableContent(title: 'timestamp', body: log.timestamp), + ]; + } } diff --git a/lib/modules/tautulli/routes/logs_plex_media_server/widgets/log_tile.dart b/lib/modules/tautulli/routes/logs_plex_media_server/widgets/log_tile.dart index 05866176..0a0a7b09 100644 --- a/lib/modules/tautulli/routes/logs_plex_media_server/widgets/log_tile.dart +++ b/lib/modules/tautulli/routes/logs_plex_media_server/widgets/log_tile.dart @@ -1,11 +1,9 @@ -import 'package:expandable/expandable.dart'; import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; import 'package:tautulli/tautulli.dart'; class TautulliLogsPlexMediaServerLogTile extends StatelessWidget { final TautulliPlexLog log; - final ExpandableController _controller = ExpandableController(); TautulliLogsPlexMediaServerLogTile({ Key key, @@ -13,77 +11,31 @@ class TautulliLogsPlexMediaServerLogTile extends StatelessWidget { }) : super(key: key); @override - Widget build(BuildContext context) => LSExpandable( - controller: _controller, - collapsed: _collapsed(context), - expanded: _expanded(context), - ); + Widget build(BuildContext context) { + return LunaExpandableListTile( + title: log.message.trim(), + collapsedSubtitle1: _subtitle1(), + collapsedSubtitle2: _subtitle2(), + expandedTableContent: _tableContent(), + ); + } - Widget _collapsed(BuildContext context) => LSCardTile( - title: LSTitle(text: log.message.trim()), - subtitle: _subtitle, - padContent: true, - onTap: () => _controller.toggle(), - ); + TextSpan _subtitle1() => TextSpan(text: log.timestamp ?? LunaUI.TEXT_EMDASH); - Widget get _subtitle => RichText( - text: TextSpan( + TextSpan _subtitle2() { + return TextSpan( + text: log.level ?? LunaUI.TEXT_EMDASH, style: TextStyle( - color: Colors.white70, - fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + color: LunaColours.accent, + fontWeight: LunaUI.FONT_WEIGHT_BOLD, ), - children: [ - TextSpan(text: '${log.level}\n'), - TextSpan( - text: log.timestamp, - style: TextStyle( - color: LunaColours.accent, - fontWeight: LunaUI.FONT_WEIGHT_BOLD, - ) - ), - ], - ), - softWrap: false, - overflow: TextOverflow.fade, - maxLines: 2, - ); - - Widget _expanded(BuildContext context) => LSCard( - child: InkWell( - child: Row( - children: [ - Expanded( - child: Padding( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LSTitle(text: log.message.trim(), softWrap: true, maxLines: 12), - Padding( - child: Wrap( - direction: Axis.horizontal, - runSpacing: 10.0, - children: [ - LSTextHighlighted( - text: log.level, - bgColor: LunaColours.blue, - ), - LSTextHighlighted( - text: log.timestamp, - bgColor: LunaColours.accent, - ), - ], - ), - padding: EdgeInsets.only(top: 8.0, bottom: 2.0), - ), - ], - ), - padding: EdgeInsets.symmetric(horizontal: 12.0, vertical: 10.0), - ), - ), - ], - ), - borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), - onTap: () => _controller.toggle(), - ), - ); + ); + } + + List _tableContent() { + return [ + LunaTableContent(title: 'level', body: log.level), + LunaTableContent(title: 'timestamp', body: log.timestamp), + ]; + } } diff --git a/lib/modules/tautulli/routes/logs_tautulli/widgets/log_tile.dart b/lib/modules/tautulli/routes/logs_tautulli/widgets/log_tile.dart index d89c15cd..d9d2a161 100644 --- a/lib/modules/tautulli/routes/logs_tautulli/widgets/log_tile.dart +++ b/lib/modules/tautulli/routes/logs_tautulli/widgets/log_tile.dart @@ -1,11 +1,9 @@ -import 'package:expandable/expandable.dart'; import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; import 'package:tautulli/tautulli.dart'; class TautulliLogsTautulliLogTile extends StatelessWidget { final TautulliLog log; - final ExpandableController _controller = ExpandableController(); TautulliLogsTautulliLogTile({ Key key, @@ -13,81 +11,32 @@ class TautulliLogsTautulliLogTile extends StatelessWidget { }) : super(key: key); @override - Widget build(BuildContext context) => LSExpandable( - controller: _controller, - collapsed: _collapsed(context), - expanded: _expanded(context), - ); + Widget build(BuildContext context) { + return LunaExpandableListTile( + title: log.message.trim(), + collapsedSubtitle1: _subtitle1(), + collapsedSubtitle2: _subtitle2(), + expandedTableContent: _tableContent(), + ); + } - Widget _collapsed(BuildContext context) => LSCardTile( - title: LSTitle(text: log.message.trim()), - subtitle: _subtitle, - padContent: true, - onTap: () => _controller.toggle(), - ); + TextSpan _subtitle1() => TextSpan(text: log.timestamp ?? LunaUI.TEXT_EMDASH); - Widget get _subtitle => RichText( - text: TextSpan( + TextSpan _subtitle2() { + return TextSpan( + text: log.level ?? LunaUI.TEXT_EMDASH, style: TextStyle( - color: Colors.white70, - fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + color: LunaColours.accent, + fontWeight: LunaUI.FONT_WEIGHT_BOLD, ), - children: [ - TextSpan(text: '${log.level}\n'), - TextSpan( - text: log.timestamp, - style: TextStyle( - color: LunaColours.accent, - fontWeight: LunaUI.FONT_WEIGHT_BOLD, - ) - ), - ], - ), - softWrap: false, - overflow: TextOverflow.fade, - maxLines: 2, - ); - - Widget _expanded(BuildContext context) => LSCard( - child: InkWell( - child: Row( - children: [ - Expanded( - child: Padding( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LSTitle(text: log.message.trim(), softWrap: true, maxLines: 12), - Padding( - child: Wrap( - direction: Axis.horizontal, - runSpacing: 10.0, - children: [ - LSTextHighlighted( - text: log.level, - bgColor: LunaColours.blue, - ), - LSTextHighlighted( - text: log.timestamp, - bgColor: LunaColours.accent, - ), - ], - ), - padding: EdgeInsets.only(top: 8.0, bottom: 2.0), - ), - Padding( - child: LSSubtitle(text: log.thread), - padding: EdgeInsets.only(top: 6.0, bottom: 2.0), - ), - ], - ), - padding: EdgeInsets.symmetric(horizontal: 12.0, vertical: 10.0), - ), - ), - ], - ), - borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), - onTap: () => _controller.toggle(), - ), - ); + ); + } + + List _tableContent() { + return [ + LunaTableContent(title: 'level', body: log.level), + LunaTableContent(title: 'timestamp', body: log.timestamp), + LunaTableContent(title: 'thread', body: log.thread), + ]; + } } diff --git a/lib/modules/tautulli/routes/media_details/widgets/switcher_buttons.dart b/lib/modules/tautulli/routes/media_details/widgets/switcher_buttons.dart index 4f271d4d..bc5c51e1 100644 --- a/lib/modules/tautulli/routes/media_details/widgets/switcher_buttons.dart +++ b/lib/modules/tautulli/routes/media_details/widgets/switcher_buttons.dart @@ -34,7 +34,7 @@ class _State extends State { Widget get _buttons { if(_blacklist.contains(widget.type)) return Container(); - return LSContainerRow( + return LunaButtonContainer( children: _buttonBuilder(), ); } @@ -59,13 +59,10 @@ class _State extends State { } } - Widget _button(TautulliMediaDetailsSwitcherType type, Color color) => Expanded( - child: LSButton( - text: type.label, - onTap: () => _onTap(type), - backgroundColor: color, - reducedMargin: true, - ), + Widget _button(TautulliMediaDetailsSwitcherType type, Color color) => LunaButton.text( + text: type.label, + onTap: () => _onTap(type), + backgroundColor: color, ); void _onTap(TautulliMediaDetailsSwitcherType value) { diff --git a/localization/lunasea/nb-NO.json b/localization/lunasea/nb-NO.json index d9faf036..271b69b2 100644 --- a/localization/lunasea/nb-NO.json +++ b/localization/lunasea/nb-NO.json @@ -1,11 +1,16 @@ { "lunasea.AnErrorHasOccurred": "En feil har inntruffet", + "lunasea.Cancel": "Avbryt", + "lunasea.ChangeProfiles": "Endre profiler", + "lunasea.CheckLogsMessage": "Sjekk loggføring for flere detaljer", "lunasea.ComingSoon": "Kommer snart", + "lunasea.Dashboard": "Oversikt", "lunasea.GoBack": "Gå tilbake", "lunasea.ModuleIsNotEnabled": "{} er ikke påskrudd", "lunasea.Refresh": "Gjenoppfrisk", "lunasea.ReturnToDashboard": "Tilbake til oversikt", "lunasea.SearchTextBar": "Søk …", "lunasea.Settings": "Innstillinger", - "lunasea.TryAgain": "Prøv igjen" + "lunasea.TryAgain": "Prøv igjen", + "lunasea.Unknown": "Ukjent" } diff --git a/localization/lunasea/ru.json b/localization/lunasea/ru.json index c03f2287..94e8b7ee 100644 --- a/localization/lunasea/ru.json +++ b/localization/lunasea/ru.json @@ -1,5 +1,6 @@ { "lunasea.AnErrorHasOccurred": "Произошла ошибка", + "lunasea.Cancel": "Отмена", "lunasea.ChangeProfiles": "Изменить профили", "lunasea.ComingSoon": "Скоро будет", "lunasea.Dashboard": "Панель управления", diff --git a/localization/lunasea/sv.json b/localization/lunasea/sv.json index 6b2e36a4..69f3380c 100644 --- a/localization/lunasea/sv.json +++ b/localization/lunasea/sv.json @@ -1,5 +1,6 @@ { "lunasea.AnErrorHasOccurred": "Ett fel har inträffat", + "lunasea.Cancel": "Avbryt", "lunasea.ChangeProfiles": "Byta Profil", "lunasea.CheckLogsMessage": "Kolla loggen för mera detaljer", "lunasea.ComingSoon": "Kommer Snart", diff --git a/localization/radarr/de.json b/localization/radarr/de.json index b9cd203d..36e91bcb 100644 --- a/localization/radarr/de.json +++ b/localization/radarr/de.json @@ -5,6 +5,7 @@ "radarr.All": "Alle", "radarr.Alphabetical": "Alphabetisch", "radarr.Approved": "Genehmigt", + "radarr.Automatic": "Automatisch", "radarr.AvailabilityUnknown": "Verfügbarkeit Unbekannt", "radarr.AvailableIn": "Verfügbar in {}", "radarr.AvailableToday": "Heute Verfügbar", @@ -22,6 +23,9 @@ "radarr.HistoryDescription": "Zeige kürzliche Aktivität", "radarr.InCinemasIn": "In den Kinos in {}", "radarr.InCinemasToday": "Heute in den Kinos", + "radarr.Interactive": "Interaktiv", + "radarr.ManualImport": "Manueller Import", + "radarr.ManualImportDescription": "Importiere Filme vom Dateisystem", "radarr.MinimumAvailability": "Minimale Verfügbarkeit", "radarr.Missing": "Fehlend", "radarr.MonitorMovie": "Überwache Film", @@ -37,10 +41,13 @@ "radarr.NoHistoryFound": "Kein Verlauf gefunden", "radarr.NoMoviesFound": "Keine Filme gefunden", "radarr.NoResultsFound": "Keine Ergebnisse gefunden", + "radarr.NoSubdirectoriesFound": "Keine Unterverzeichnisse gefunden", "radarr.NoSummaryIsAvailable": "Keine Zusammenfassung verfügbar.", + "radarr.ParentDirectory": "Übergeordnetes Verzeichnis", "radarr.QualityProfile": "Qualitätsprofil", "radarr.Queue": "Warteschlange", "radarr.QueueDescription": "Zeige aktiven & eingereihten Inhalt", + "radarr.Quick": "Schnell", "radarr.RefreshMovie": "Film aktualisieren", "radarr.Rejected": "Abgelehnt", "radarr.Released": "Veröffentlicht", diff --git a/localization/radarr/en.json b/localization/radarr/en.json index c45ffda4..d9c1ace5 100644 --- a/localization/radarr/en.json +++ b/localization/radarr/en.json @@ -5,6 +5,7 @@ "radarr.AddMovieAndSearch": "Add + Search", "radarr.Alphabetical": "Alphabetical", "radarr.Approved": "Approved", + "radarr.Automatic": "Automatic", "radarr.AvailabilityUnknown": "Availability Unknown", "radarr.AvailableIn": "Available in {}", "radarr.AvailableToday": "Available Today", @@ -12,16 +13,21 @@ "radarr.CinemaDateUnknown": "Cinema Date Unknown", "radarr.CutoffUnmet": "Cutoff Unmet", "radarr.DateAdded": "Date Added", + "radarr.DirectoryNotFound": "Directory Not Found", "radarr.Discover": "Discover", "radarr.DownloadFailed": "Download Failed", "radarr.DownloadIgnored": "Download Ignored", "radarr.EditMovie": "Edit Movie", + "radarr.FileBrowser": "File Browser…", "radarr.FilterCatalogue": "Filter Catalogue", "radarr.GrabbedFrom": "Grabbed from {}", "radarr.History": "History", "radarr.HistoryDescription": "View Recent Activity", "radarr.InCinemasIn": "In Cinemas in {}", "radarr.InCinemasToday": "In Cinemas Today", + "radarr.Interactive": "Interactive", + "radarr.ManualImport": "Manual Import", + "radarr.ManualImportDescription": "Import Movies from the Filesystem", "radarr.MinimumAvailability": "Minimum Availability", "radarr.Missing": "Missing", "radarr.Monitored": "Monitored", @@ -34,13 +40,17 @@ "radarr.MovieImported": "Movie Imported ({})", "radarr.MovieNotFound": "Movie Not Found", "radarr.MoviePath": "Movie Path", + "radarr.NoFilesFound": "No Files Found", "radarr.NoHistoryFound": "No History Found", "radarr.NoMoviesFound": "No Movies Found", "radarr.NoResultsFound": "No Results Found", + "radarr.NoSubdirectoriesFound": "No Subdirectories Found", "radarr.NoSummaryIsAvailable": "No summary is available.", + "radarr.ParentDirectory": "Parent Directory", "radarr.QualityProfile": "Quality Profile", "radarr.Queue": "Queue", "radarr.QueueDescription": "View Active & Queued Content", + "radarr.Quick": "Quick", "radarr.RefreshMovie": "Refresh Movie", "radarr.Rejected": "Rejected", "radarr.Released": "Released", diff --git a/localization/radarr/fr.json b/localization/radarr/fr.json index 942ff0e9..86be9ac1 100644 --- a/localization/radarr/fr.json +++ b/localization/radarr/fr.json @@ -5,6 +5,7 @@ "radarr.All": "Tout", "radarr.Alphabetical": "Alphabétique", "radarr.Approved": "Approuvé", + "radarr.Automatic": "Automatique", "radarr.AvailabilityUnknown": "Disponibilité inconnue", "radarr.AvailableIn": "Disponible dans {}", "radarr.AvailableToday": "Disponible aujourd'hui", @@ -22,6 +23,9 @@ "radarr.HistoryDescription": "Voir les activités récentes", "radarr.InCinemasIn": "Dans les cinémas en {}", "radarr.InCinemasToday": "Au cinéma aujourd'hui", + "radarr.Interactive": "Interactive", + "radarr.ManualImport": "Importation manuelle", + "radarr.ManualImportDescription": "Importer des films depuis le système de fichiers", "radarr.MinimumAvailability": "Disponibilité minimale", "radarr.Missing": "Manquant", "radarr.MonitorMovie": "Surveiller le film", @@ -37,10 +41,13 @@ "radarr.NoHistoryFound": "Pas d'historique trouvé", "radarr.NoMoviesFound": "Aucun film trouvé", "radarr.NoResultsFound": "Aucun résultat", + "radarr.NoSubdirectoriesFound": "Aucun sous-répertoire trouvé", "radarr.NoSummaryIsAvailable": "Aucun résumé n'est disponible.", + "radarr.ParentDirectory": "Répertoire parent", "radarr.QualityProfile": "Profil de qualité", "radarr.Queue": "File d'attente", "radarr.QueueDescription": "Voir le contenu actif et en attente", + "radarr.Quick": "Rapide", "radarr.RefreshMovie": "Rafraichir le film", "radarr.Rejected": "Rejeté", "radarr.Released": "Sortie", diff --git a/localization/tautulli/de.json b/localization/tautulli/de.json index ba200db4..0a0f8d34 100644 --- a/localization/tautulli/de.json +++ b/localization/tautulli/de.json @@ -10,12 +10,12 @@ "tautulli.Video": "Video", "tautulli.Users": "Benutzer", "tautulli.User": "Benutzer", - "tautulli.Transcodes": "Transkodierungen", - "tautulli.Transcode": "Transkodieren", + "tautulli.Transcodes": "Umwandlungen", + "tautulli.Transcode": "Umwandeln", "tautulli.Title": "Titel", - "tautulli.Throttled": "Throttled", + "tautulli.Throttled": "Gebremst", "tautulli.TerminationMessage": "Abbruch-Begründung", - "tautulli.TerminationAttachMessage": "Du kannst optional unten eine Abbruch-Begründung angeben.", + "tautulli.TerminationAttachMessage": "Du kannst optional eine Abbruch-Begründung angeben.", "tautulli.Subtitle": "Untertitel", "tautulli.Stream": "Stream", "tautulli.SessionsMany": "{} Sitzungen", diff --git a/localization/tautulli/nb-NO.json b/localization/tautulli/nb-NO.json index 0967ef42..5da9a286 100644 --- a/localization/tautulli/nb-NO.json +++ b/localization/tautulli/nb-NO.json @@ -1 +1,70 @@ -{} +{ + "tautulli.ETA": "Estimert tid til ankomst", + "tautulli.Throttled": "Strupet", + "tautulli.TerminationAttachMessage": "Du kan alternativt legge ved en avsluttingsmelding nedenfor.", + "tautulli.Location": "Sted", + "tautulli.DeletingTemporarySessionsFailed": "Klarte ikke å slette midlertidige økter", + "tautulli.DeletingTemporarySessionsDescription": "Midlertidige økter slettes …", + "tautulli.DeletingTemporarySessions": "Sletter midlertidige økter …", + "tautulli.DeletingImageCacheFailed": "Klarte ikke å slette bildehurtiglager", + "tautulli.DeletingImageCacheDescription": "Tautulli-bildehurtiglageret blir slettet …", + "tautulli.Burn": "Brenn", + "tautulli.BackupDatabase": "Sikkerhetskopier database", + "tautulli.BackupConfiguration": "Sikkhetskopier oppsett", + "tautulli.BackingUpDatabaseDescription": "Sikkerhetskopierer databasen din i bakgrunnen …", + "tautulli.Year": "År", + "tautulli.ViewWebGUI": "Vis nettbasert grensesnitt", + "tautulli.Video": "Video", + "tautulli.Users": "Brukere", + "tautulli.User": "Bruker", + "tautulli.Transcodes": "Transkoder", + "tautulli.Transcode": "Transkod", + "tautulli.Title": "Tittel", + "tautulli.TerminateSessionFailed": "Klarte ikke å avslutte økt", + "tautulli.TerminateSession": "Avslutt økt", + "tautulli.TerminatedSession": "Økt avsluttet", + "tautulli.TerminationMessage": "Avsluttingsmelding", + "tautulli.TerminationConfirmMessage": "Ønsker du å avslutte denne økten?", + "tautulli.Terminate": "Avslutt", + "tautulli.Subtitle": "Undertekst", + "tautulli.Stream": "Strøm", + "tautulli.SessionsMany": "{} økter", + "tautulli.SessionsOne": "Én økt", + "tautulli.Sessions": "Økter", + "tautulli.SessionEnded": "Sesong slutt", + "tautulli.Season": "Sesong {}", + "tautulli.Quality": "Kvalitet", + "tautulli.Product": "Produkt", + "tautulli.Player": "Spiller", + "tautulli.Platform": "Plattform", + "tautulli.NoActiveStreams": "Ingen aktive strømmer", + "tautulli.None": "Ingen", + "tautulli.More": "Mer", + "tautulli.Metadata": "Metadata", + "tautulli.Library": "Bibliotek", + "tautulli.History": "Historikk", + "tautulli.Episode": "Episode {}", + "tautulli.Duration": "Varighet", + "tautulli.DirectStreams": "Direkte strømmer", + "tautulli.DirectStream": "Direkte strøm", + "tautulli.DirectPlays": "Direkte avspillinger", + "tautulli.DirectPlay": "Direkte avspilling", + "tautulli.DeletingImageCache": "Sletter bildehurtiglager …", + "tautulli.DeletingCacheFailed": "Klarte ikke å slette hurtiglager", + "tautulli.DeletingCacheDescription": "Tautulli-hurtiglageret blir slettet …", + "tautulli.DeletingCache": "Sletter hurtiglager …", + "tautulli.DeleteTemporarySessions": "Slett midlertidige økter", + "tautulli.DeleteImageCache": "Slett bildehurtiglager", + "tautulli.DeleteCache": "Slett hurtiglager", + "tautulli.Copy": "Kopier", + "tautulli.Container": "Beholder", + "tautulli.Bandwidth": "Båndbredde", + "tautulli.BackingUpConfigurationFailed": "Klarte ikke å sikkerhetskopiere oppsett", + "tautulli.BackingUpDatabaseFailed": "Klarte ikke å sikkerhetskopiere database", + "tautulli.BackingUpDatabase": "Sikkerhetskopierer database …", + "tautulli.BackingUpConfigurationDescription": "Sikkerhetskopierer oppsetet ditt i bakgrunnen", + "tautulli.BackingUpConfiguration": "Sikkerhetskopierer oppsett …", + "tautulli.Audio": "Lyd", + "tautulli.ActivityDetails": "Aktivitetsdetaljer", + "tautulli.Activity": "Aktivitet" +} diff --git a/localization/tautulli/ru.json b/localization/tautulli/ru.json index 0967ef42..188e5a8e 100644 --- a/localization/tautulli/ru.json +++ b/localization/tautulli/ru.json @@ -1 +1,41 @@ -{} +{ + "tautulli.Year": "Год", + "tautulli.Video": "Видео", + "tautulli.Users": "Пользователи", + "tautulli.User": "Пользователь", + "tautulli.Title": "Название", + "tautulli.Subtitle": "Субтитры", + "tautulli.Season": "Сезон {}", + "tautulli.Quality": "Качество", + "tautulli.Product": "Продукт", + "tautulli.Player": "Плеер", + "tautulli.Platform": "Платформа", + "tautulli.None": "Ничего", + "tautulli.More": "Ещё", + "tautulli.Metadata": "Метаданные", + "tautulli.Location": "Местоположение", + "tautulli.Library": "Библиотека", + "tautulli.History": "История", + "tautulli.Episode": "Эпизод {}", + "tautulli.Duration": "Продолжительность", + "tautulli.DeletingTemporarySessionsFailed": "Не удалось удалить временные сеансы", + "tautulli.DeletingTemporarySessionsDescription": "Временные сеансы удаляются", + "tautulli.DeletingImageCache": "Удаление кэша изображения…", + "tautulli.DeletingCacheFailed": "Не удалось удалить кэш", + "tautulli.DeletingCache": "Удаление кэша…", + "tautulli.DeleteImageCache": "Удалить кэш изображения", + "tautulli.DeleteCache": "Удалить кэш", + "tautulli.Copy": "Копировать", + "tautulli.Container": "Контейнер", + "tautulli.Bandwidth": "Пропускная способность", + "tautulli.BackupDatabase": "Резервная база данных", + "tautulli.BackupConfiguration": "Конфигурация резервного копирования", + "tautulli.BackingUpDatabaseFailed": "Не удалось создать резервную копию базы данных", + "tautulli.BackingUpDatabaseDescription": "Резервное копирование базы данных в фоновом режиме", + "tautulli.BackingUpDatabase": "Резервное копирование базы данных…", + "tautulli.BackingUpConfigurationFailed": "Не удалось создать резервную копию конфигурации", + "tautulli.BackingUpConfigurationDescription": "Резервное копирование конфигурации в фоновом режиме", + "tautulli.BackingUpConfiguration": "Резервное копирование конфигурации…", + "tautulli.Audio": "Аудио", + "tautulli.Activity": "Активность" +} diff --git a/localization/tautulli/sv.json b/localization/tautulli/sv.json index 0967ef42..de9809d2 100644 --- a/localization/tautulli/sv.json +++ b/localization/tautulli/sv.json @@ -1 +1,70 @@ -{} +{ + "tautulli.Year": "År", + "tautulli.ViewWebGUI": "Visa Web GUI", + "tautulli.Video": "Video", + "tautulli.Users": "Användare", + "tautulli.User": "Användare", + "tautulli.Transcodes": "Omkodningar", + "tautulli.Transcode": "Omkoda", + "tautulli.Title": "Titel", + "tautulli.Throttled": "Strypt", + "tautulli.TerminateSessionFailed": "Det gick inte att Tvångsavsluta Session", + "tautulli.TerminateSession": "Tvångsavsluta Session", + "tautulli.TerminatedSession": "Tvångsavslutad Session", + "tautulli.TerminationMessage": "Tvångsavslutnings Meddelande", + "tautulli.TerminationConfirmMessage": "Vill du tvångsavsluta denna sessionen?", + "tautulli.TerminationAttachMessage": "Du kan skriva in ett tvångsavslutnings meddelande nedan.", + "tautulli.Terminate": "Tvångsavsluta", + "tautulli.Subtitle": "Undertext", + "tautulli.Stream": "Ström", + "tautulli.SessionsMany": "{} Sessioner", + "tautulli.SessionsOne": "1 Session", + "tautulli.Sessions": "Sessioner", + "tautulli.SessionEnded": "Session Stoppad", + "tautulli.Season": "Säsong {}", + "tautulli.Quality": "Kvalitet", + "tautulli.Product": "Produkt", + "tautulli.Player": "Spelare", + "tautulli.Platform": "Platform", + "tautulli.NoActiveStreams": "Inga Aktiva Strömmar", + "tautulli.None": "Inget", + "tautulli.More": "Mer", + "tautulli.Metadata": "Metadata", + "tautulli.Location": "Plats", + "tautulli.Library": "Bibliotek", + "tautulli.History": "Historik", + "tautulli.ETA": "ETA", + "tautulli.Episode": "Episod {}", + "tautulli.Duration": "Speltid", + "tautulli.DirectStreams": "Direktströmmar", + "tautulli.DirectStream": "Direktström", + "tautulli.DirectPlays": "Direktuppspelningar", + "tautulli.DirectPlay": "Direktuppspelning", + "tautulli.DeletingTemporarySessionsFailed": "Ett fel uppstod vid Radering av Temporära Sessioner", + "tautulli.DeletingTemporarySessionsDescription": "Temporära sessioner raderas", + "tautulli.DeletingTemporarySessions": "Raderar Temporära Sessioner…", + "tautulli.DeletingImageCacheFailed": "Ett fel uppstod vid Radering av Cache", + "tautulli.DeletingImageCacheDescription": "Tautulli bild cache raderas", + "tautulli.DeletingImageCache": "Raderar Bild Cache…", + "tautulli.DeletingCacheFailed": "Ett fel uppstod vid Radering av Cache", + "tautulli.DeletingCacheDescription": "Tautulli cache raderas", + "tautulli.DeletingCache": "Raderar Cache…", + "tautulli.DeleteTemporarySessions": "Radera Temporära Sessioner", + "tautulli.DeleteImageCache": "Radera Bild Cache", + "tautulli.DeleteCache": "Radera Cache", + "tautulli.Copy": "Kopiera", + "tautulli.Container": "Behållare", + "tautulli.Burn": "Bränn", + "tautulli.Bandwidth": "Bandbredd", + "tautulli.BackupDatabase": "Säkerhetskopiera Databas", + "tautulli.BackupConfiguration": "Säkerhetskopiera Konfiguration", + "tautulli.BackingUpDatabaseFailed": "Ett fel uppstod vid Säkerhetskopiering av Databasen", + "tautulli.BackingUpDatabaseDescription": "Säkerhetskopierar din konfiguration i bakgrunden", + "tautulli.BackingUpDatabase": "Säkerhetskopierar Databasen…", + "tautulli.BackingUpConfigurationFailed": "Ett fel uppstod vid Säkerhetskopiering", + "tautulli.BackingUpConfigurationDescription": "Säkerhetskopierar din konfiguration i bakgrunden", + "tautulli.BackingUpConfiguration": "Säkerhetskopiera Konfiguration…", + "tautulli.Audio": "Ljud", + "tautulli.ActivityDetails": "Aktivitetsdetaljer", + "tautulli.Activity": "Aktivitet" +} diff --git a/pubspec.lock b/pubspec.lock index 24b789eb..0d57daa5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,14 +7,14 @@ packages: name: _fe_analyzer_shared url: "https://pub.dartlang.org" source: hosted - version: "17.0.0" + version: "18.0.0" analyzer: dependency: "direct dev" description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" args: dependency: transitive description: @@ -63,7 +63,7 @@ packages: name: build_daemon url: "https://pub.dartlang.org" source: hosted - version: "2.1.9" + version: "2.1.10" build_resolvers: dependency: transitive description: @@ -98,7 +98,7 @@ packages: name: built_value url: "https://pub.dartlang.org" source: hosted - version: "8.0.2" + version: "8.0.3" characters: dependency: transitive description: @@ -238,14 +238,14 @@ packages: name: dbus url: "https://pub.dartlang.org" source: hosted - version: "0.2.3" + version: "0.2.5" dio: dependency: "direct main" description: name: dio url: "https://pub.dartlang.org" source: hosted - version: "4.0.0-beta7" + version: "4.0.0-prev2" easy_localization: dependency: "direct main" description: @@ -280,7 +280,7 @@ packages: name: expandable url: "https://pub.dartlang.org" source: hosted - version: "4.1.4" + version: "5.0.1" ffi: dependency: transitive description: @@ -845,10 +845,10 @@ packages: radarr: dependency: "direct main" description: - name: radarr - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0-pre.1" + path: "/Users/jagandeepbrar/Git/Packages/radarr" + relative: false + source: path + version: "2.0.0-pre.3" share: dependency: "direct main" description: @@ -862,7 +862,7 @@ packages: name: shared_preferences url: "https://pub.dartlang.org" source: hosted - version: "2.0.4" + version: "2.0.5" shared_preferences_linux: dependency: transitive description: @@ -904,7 +904,7 @@ packages: name: shelf url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.1.0" shelf_web_socket: dependency: transitive description: @@ -937,7 +937,7 @@ packages: name: sonarr url: "https://pub.dartlang.org" source: hosted - version: "2.0.0-pre.1" + version: "2.0.0-pre.2" source_gen: dependency: transitive description: @@ -1007,7 +1007,7 @@ packages: name: tautulli url: "https://pub.dartlang.org" source: hosted - version: "2.0.0-pre.2" + version: "2.0.0-pre.3" term_glyph: dependency: transitive description: @@ -1126,7 +1126,7 @@ packages: name: win32 url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.4" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c035880b..3de1589a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,10 +12,10 @@ dependencies: cloud_firestore: ^1.0.1 # ANDROID IOS MACOS WEB connectivity_plus: ^1.0.0 # ANDROID IOS LINUX MACOS WEB WINDOWS convert: ^3.0.0 # ANDROID IOS LINUX MACOS WEB WINDOWS - dio: ^4.0.0-beta7 # ANDROID IOS LINUX MACOS WEB WINDOWS + dio: ^4.0.0-prev2 # ANDROID IOS LINUX MACOS WEB WINDOWS easy_localization: ^3.0.0 # ANDROID IOS LINUX MACOS WEB encrypt: ^5.0.0-beta.1 # ANDROID IOS LINUX MACOS WEB WINDOWS - expandable: ^4.1.4 # ANDROID IOS LINUX MACOS WEB WINDOWS + expandable: ^5.0.1 # ANDROID IOS LINUX MACOS WEB WINDOWS file_picker: ^3.0.0 # ANDROID IOS WEB firebase_analytics: ^7.1.1 # ANDROID IOS WEB firebase_auth: ^1.0.1 # ANDROID IOS MACOS WEB @@ -41,13 +41,14 @@ dependencies: percent_indicator: ^2.1.9+1 # ANDROID IOS LINUX MACOS WEB WINDOWS provider: ^5.0.0 # ANDROID IOS LINUX MACOS WEB WINDOWS quick_actions: ^0.5.0 # ANDROID IOS - radarr: ^2.0.0-pre.1 # ANDROID IOS LINUX MACOS WEB WINDOWS + radarr: # ANDROID IOS LINUX MACOS WEB WINDOWS + path: /Users/jagandeepbrar/Git/Packages/radarr share: ^2.0.1 # ANDROID IOS - sonarr: ^2.0.0-pre.1 # ANDROID IOS LINUX MACOS WEB WINDOWS + sonarr: ^2.0.0-pre.2 # ANDROID IOS LINUX MACOS WEB WINDOWS stack_trace: ^1.10.0 # ANDROID IOS LINUX MACOS WEB WINDOWS supercharged: ^2.0.0 # ANDROID IOS LINUX MACOS WEB WINDOWS table_calendar: ^2.3.3 # ANDROID IOS LINUX MACOS WEB WINDOWS - tautulli: ^2.0.0-pre.2 # ANDROID IOS LINUX MACOS WEB WINDOWS + tautulli: ^2.0.0-pre.3 # ANDROID IOS LINUX MACOS WEB WINDOWS tuple: ^2.0.0 # ANDROID IOS LINUX MACOS WEB WINDOWS url_launcher: ^6.0.2 # ANDROID IOS LINUX MACOS WEB WINDOWS uuid: ^3.0.1 # ANDROID IOS LINUX MACOS WEB WINDOWS @@ -55,7 +56,7 @@ dependencies: xml: ^5.0.2 # ANDROID IOS LINUX MACOS WEB WINDOWS dev_dependencies: - analyzer: ^1.1.0 + analyzer: ^1.2.0 build_runner: ^1.11.5 hive_generator: ^1.0.0