[Release] (chore): Dirty v5.0.0+50000013 (#382) (#383)

* [iOS/Build] Add GoogleUtilities pod to prevent archive errors

* (chore) version bump

* [Git] Initialize blackbox

* [Git] Initialize blackbox

* [Git/Blackbox] Add me@jagandeepbrar.io as admin

* registered in blackbox: ios/ExportOptions.plist

* registered in blackbox: ios/GoogleService-Info.plist

* registered in blackbox: android/key.properties

* registered in blackbox: android/app/google-services.json

* Re-encrypted keys

* registered in blackbox: android/key.jks

* Updated key.properties, re-encrypted

* [Android/Keystore] Update same key.properties

* [Tautulli/Activity] Incorrectly localized "Transcodes" string

* [Radarr, UI] Implemented quick manual import, Implemented new design of ExpandableTile across UI (#378)

* Use local path for radarr package

* [Radarr/Manual Import] Browse filesystem, trigger quick import

* [UI/Buttons] Normalize the height of all buttons

* Update packages

* [ListView] Exposed itemExtent property

* [Lidarr/Releases] Move from LSExpandable to LunaExpandableListTile implementation

* [Tautulli/Logs] Use the new expandable table card styling

* [SABnzbd/History] Use the newer expandable table card styling

* [NZBGet/History] Use the newer expandable table card styling

* [UI/Button] Reduce size of the loader and icon to closer match text size

* [Sonarr/Releases] Use the newer expandable table card styling

* [Sonarr/Episodes] Use the newer expandable table card styling

* [UI/Table] Safe-guard title and body text, show emdash when receiving null data

* [Radarr/Details] Show monitor status and properly update the overview table on edit or monitor status changes

* Updated changelog

* [Sonarr/Queue & Episodes] Cleaned up implementation of expandable tile

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (14 of 14 strings)

Translation: LunaSea/LunaSea
Translate-URL: https://hosted.weblate.org/projects/lunasea/lunasea/nb_NO/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (14 of 14 strings)

Translation: LunaSea/LunaSea
Translate-URL: https://hosted.weblate.org/projects/lunasea/lunasea/sv/

* Translated using Weblate (German)

Currently translated at 100.0% (68 of 68 strings)

Translation: LunaSea/Tautulli
Translate-URL: https://hosted.weblate.org/projects/lunasea/tautulli/de/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 80.8% (55 of 68 strings)

Translation: LunaSea/Tautulli
Translate-URL: https://hosted.weblate.org/projects/lunasea/tautulli/nb_NO/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (68 of 68 strings)

Translation: LunaSea/Tautulli
Translate-URL: https://hosted.weblate.org/projects/lunasea/tautulli/sv/

* Translated using Weblate (Russian)

Currently translated at 57.3% (39 of 68 strings)

Translation: LunaSea/Tautulli
Translate-URL: https://hosted.weblate.org/projects/lunasea/tautulli/ru/

* (chore) Build localizations

* [Tautulli/Webhooks] Implement webhook data handlers for all Tautulli webhooks (#380)

* [Tautulli/Webhooks] Handle transcode decision change and watched event types
* [Radarr/Sonarr] Safe-guard webhooks by pushing the home route on invalid received data blocks
* [Tautulli/Webhooks] Implemented all webhooks

* Translated using Weblate (French)

Currently translated at 100.0% (78 of 78 strings)

Translation: LunaSea/Radarr
Translate-URL: https://hosted.weblate.org/projects/lunasea/radarr/fr/

* Translated using Weblate (German)

Currently translated at 100.0% (78 of 78 strings)

Translation: LunaSea/Radarr
Translate-URL: https://hosted.weblate.org/projects/lunasea/radarr/de/

* Translated using Weblate (Russian)

Currently translated at 92.8% (13 of 14 strings)

Translation: LunaSea/LunaSea
Translate-URL: https://hosted.weblate.org/projects/lunasea/lunasea/ru/

* [Release] (chore): Dirty v5.0.0+50000013 (#382)

* [Radarr/Manual Import] Add search bar for host system path, add stub manual import page

* [Radarr/Manual Import] Change path bar text to "File Browser..."

* [Radarr/Manual Import] Pass "/" if textController text is null

* [Radarr/Import] Stub details state

* [Database] Correctly register LunaIndexerIcon hive adapter

* [Radarr/Import] Filter directories when typing path, Fetch manual import results with stub cards

* [UI/Bottom Bar] Abstract bottom action bar, use on add movie page

* [UI/Navigation Bar] Add option to attach stickied top actions to navigation bar

* [Radarr/Edit] Use stickied bottom action bar for buttons

* [Radarr/Details] use sticky navigation actions for interactive and automatic search

* [Dialogs] CheckboxTiles were padded too aggressively in the X-axis

* [Sentry] Remove remaining references to Sentry

* (chore) Build localizations

* (chore) Update changelog

* Preparing to shift GitHub repository structure

Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Patrik Hauguth <patrik.hauguth@gmail.com>
Co-authored-by: cpt-kuesel <jln.sltr@pm.me>
Co-authored-by: Artem <KovalevArtem.ru@gmail.com>
Co-authored-by: Antoine C <contact+weblate@antoinecardon.fr>
This commit is contained in:
Jagandeep Brar
2021-03-24 08:12:41 -05:00
committed by GitHub
parent 57404f2dc2
commit dec1845764
138 changed files with 2794 additions and 2284 deletions

2
.blackbox/.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
blackbox-admins.txt text eol=lf
blackbox-files.txt text eol=lf

View File

@@ -0,0 +1 @@
me@jagandeepbrar.io

View File

@@ -0,0 +1,5 @@
android/app/google-services.json
android/key.jks
android/key.properties
ios/ExportOptions.plist
ios/GoogleService-Info.plist

BIN
.blackbox/pubring.kbx Normal file

Binary file not shown.

BIN
.blackbox/trustdb.gpg Normal file

Binary file not shown.

32
.gitignore vendored
View File

@@ -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

View File

@@ -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

Binary file not shown.

BIN
android/key.jks.gpg Normal file

Binary file not shown.

BIN
android/key.properties.gpg Normal file

Binary file not shown.

View File

@@ -1,4 +1,4 @@
storePassword=<your password>
keyPassword=<your password>
keyAlias=<your alias>
storeFile=<full path to .jks keystore>
storeFile=../key.jks

View File

@@ -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": []
}

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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"
}

View File

@@ -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": "Активность"
}

View File

@@ -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"
}

BIN
ios/ExportOptions.plist.gpg Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -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

View File

@@ -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

View File

@@ -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",

View File

@@ -9,6 +9,7 @@ class LunaDatabase extends LunaModuleDatabase {
Hive.registerAdapter(LunaLogHiveObjectAdapter());
Hive.registerAdapter(LunaLogTypeAdapter());
Hive.registerAdapter(LunaBrowserAdapter());
Hive.registerAdapter(LunaIndexerIconAdapter());
}
@override

View File

@@ -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';

View File

@@ -0,0 +1,3 @@
export 'bottom_bar/badge.dart';
export 'bottom_bar/action_bar.dart';
export 'bottom_bar/navigation_bar.dart';

View File

@@ -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<Widget> 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,
),
);
}
}

View File

@@ -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<IconData> icons;
final List<String> titles;
final List<ScrollController> scrollControllers;
final List<Widget> topActions;
final Function(int) onTabChange;
final List<Widget> 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<StatefulWidget> createState() => _State();
}
class _State extends State<LunaBottomNavigationBar> {
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);
}
}
}

View File

@@ -1,2 +0,0 @@
export 'bottom_navigation_bar/badge.dart';
export 'bottom_navigation_bar/bottom_navigation_bar.dart';

View File

@@ -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<IconData> icons;
final List<String> titles;
final List<ScrollController> scrollControllers;
final Function(int) onTabChange;
final List<Widget> 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<StatefulWidget> createState() => _State();
}
class _State extends State<LunaBottomNavigationBar> {
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);
}
}
}

View File

@@ -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,
);
}
}
/// 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,
);
}
}

View File

@@ -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';

View File

@@ -1,2 +0,0 @@
export 'button/button.dart';
export 'button/button_slim.dart';

View File

@@ -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: <Widget>[
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,
),
),
],
);
}

View File

@@ -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: <Widget>[
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,
),
),
],
);
}

View File

@@ -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<StatefulWidget> createState() => _State();
}
class _State extends State<LSExpandable> {
@override
Widget build(BuildContext context) => ExpandableNotifier(
controller: widget.controller,
child: Expandable(
collapsed: widget.collapsed,
expanded: widget.expanded,
),
);
}

View File

@@ -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,
),
],
),
],
),

View File

@@ -59,9 +59,13 @@ class _State extends State<LSTypewriterMessage> 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,
),
],
),
],
),

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
class LunaListView extends StatelessWidget {
final List<Widget> 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),

View File

@@ -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,
),
),
);

View File

@@ -3,10 +3,12 @@ import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
class LunaListViewModal extends StatelessWidget {
final List<Widget> 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),

View File

@@ -7,6 +7,7 @@ class LunaPagedListView<T> 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<T> 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<T> extends State<LunaPagedListView<T>> {
child: PagedListView<int, T>(
pagingController: widget.pagingController,
scrollController: widget.scrollController,
itemExtent: widget.itemExtent,
builderDelegate: PagedChildBuilderDelegate<T>(
itemBuilder: widget.itemBuilder,
firstPageErrorIndicatorBuilder: (context) => LunaMessage.error(onTap: () => Future.sync(() => widget.pagingController.refresh())),

View File

@@ -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),
],
),
],
),
);

View File

@@ -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,

View File

@@ -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()),
],
),
],

View File

@@ -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;

View File

@@ -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<LunaHighlightedNode> expandedHighlightedNodes;
final List<LunaTableContent> expandedTableContent;
final List<LunaButton> 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<LunaExpandableListTile> {
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<LunaExpandableListTile> {
onLongPress: widget.onLongPress,
contentPadding: widget.collapsedSubtitle2 != null,
trailing: widget.collapsedTrailing,
leading: widget.collapsedLeading,
color: widget.backgroundColor,
);
}
@@ -148,6 +162,7 @@ class _State extends State<LunaExpandableListTile> {
onTap: controller.toggle,
onLongPress: widget.onLongPress,
),
color: widget.backgroundColor,
);
}
}

View File

@@ -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,
);
}
}

View File

@@ -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,
);
}
}

View File

@@ -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,
);
}
}

View File

@@ -8,7 +8,6 @@ import 'package:lunasea/core.dart';
///
/// Runs app in guarded zone to attempt to capture fatal (crashing) errors
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await _init();
runZonedGuarded(
() => runApp(
@@ -35,10 +34,8 @@ Future<void> 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<void> _init() async {
WidgetsFlutterBinding.ensureInitialized();
//Set system UI overlay style (navbar, statusbar)
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
systemNavigationBarColor: Colors.black,

View File

@@ -218,22 +218,16 @@ class _State extends State<LidarrAddDetails> {
);
},
),
LSContainerRow(
children: <Widget>[
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,
),
),
],
),

View File

@@ -144,9 +144,13 @@ class _State extends State<LidarrEditArtist> {
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((_) {}),
),
],
),
],
);

View File

@@ -110,7 +110,7 @@ class _State extends State<LidarrSearchResults> {
? [LSGenericMessage(text: 'No Results Found')]
: List.generate(
filtered.length,
(index) => LidarrSearchResultTile(data: filtered[index]),
(index) => LidarrReleasesTile(release: filtered[index]),
);
return LSListViewStickyHeader(
controller: _scrollController,

View File

@@ -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<StatefulWidget> createState() => _State();
}
class _State extends State<LidarrReleasesTile> {
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<LunaHighlightedNode> _highlightedNodes() {
return [
LunaHighlightedNode(
text: widget.release.protocol.lunaCapitalizeFirstLetters(),
backgroundColor: lunaProtocolColor,
),
];
}
List<LunaTableContent> _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<LunaButton> _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<void> _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<dynamic> 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<void> _showWarnings(BuildContext context) async => await LunaDialogs().showRejections(context, data.rejections?.cast<String>() ?? []);
Future<void> _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<dynamic> 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<void> _showWarnings() async => await LunaDialogs().showRejections(context, (widget.release.rejections ?? []).cast<String>());
}

View File

@@ -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<StatefulWidget> 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<NZBGetHistoryTile> {
@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<LunaHighlightedNode> _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<void> _handlePopup(BuildContext context) async {
List values = await NZBGetDialogs.historySettings(context, data.name);
List<LunaTableContent> _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<LunaButton> _expandedTableButtons() {
return [
LunaButton.text(
text: 'Delete',
backgroundColor: LunaColours.red,
onTap: () async => _deleteButton(),
),
];
}
Future<void> _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<void> _deleteButton(BuildContext context) async {
Future<void> _deleteButton() async {
List<dynamic> 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();
}
}

View File

@@ -265,6 +265,32 @@ class RadarrAPIHelper {
return false;
}
/// Execute a quick import.
Future<bool> quickImport({
@required BuildContext context,
@required String path,
bool showSnackbar = true,
}) async {
assert(path != null && path.isNotEmpty);
if(context.read<RadarrState>().enabled) {
return await context.read<RadarrState>().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<bool> automaticSearch({
@required BuildContext context,

View File

@@ -301,39 +301,36 @@ class RadarrDialogs {
text: 'Close',
onPressed: () => Navigator.of(context, rootNavigator: true).pop(),
),
],
title: LSDialog.title(text: 'Tags'),
content: Selector<RadarrState, Future<List<RadarrTag>>>(
selector: (_, state) => state.tags,
builder: (context, future, _) => FutureBuilder(
future: future,
builder: (context, AsyncSnapshot<List<RadarrTag>> 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<List<RadarrTag>> 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<RadarrMoviesEditState>().tags.where((tag) => tag.id == snapshot.data[index].id).length != 0,
onChanged: (selected) {
List<RadarrTag> _tags = context.read<RadarrMoviesEditState>().tags;
selected ? _tags.add(snapshot.data[index]) : _tags.removeWhere((tag) => tag.id == snapshot.data[index].id);
context.read<RadarrMoviesEditState>().tags = _tags;
},
),
value: context.watch<RadarrMoviesEditState>().tags.where((tag) => tag.id == snapshot.data[index].id).length != 0,
onChanged: (selected) {
List<RadarrTag> _tags = context.read<RadarrMoviesEditState>().tags;
selected ? _tags.add(snapshot.data[index]) : _tags.removeWhere((tag) => tag.id == snapshot.data[index].id);
context.read<RadarrMoviesEditState>().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<List<RadarrTag>> 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<List<RadarrTag>> 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<RadarrAddMovieDetailsState>().tags.where((tag) => tag.id == snapshot.data[index].id).length != 0,
onChanged: (selected) {
List<RadarrTag> _tags = context.read<RadarrAddMovieDetailsState>().tags;
selected ? _tags.add(snapshot.data[index]) : _tags.removeWhere((tag) => tag.id == snapshot.data[index].id);
context.read<RadarrAddMovieDetailsState>().tags = _tags;
},
),
value: context.watch<RadarrAddMovieDetailsState>().tags.where((tag) => tag.id == snapshot.data[index].id).length != 0,
onChanged: (selected) {
List<RadarrTag> _tags = context.read<RadarrAddMovieDetailsState>().tags;
selected ? _tags.add(snapshot.data[index]) : _tags.removeWhere((tag) => tag.id == snapshot.data[index].id);
context.read<RadarrAddMovieDetailsState>().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;
}

View File

@@ -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);

View File

@@ -44,35 +44,17 @@ extension _EventTypeExtension on _EventType {
}
}
Future<void> _downloadEvent(Map<dynamic, dynamic> data) async {
int movieId = int.tryParse(data['id']);
Future<void> _downloadEvent(Map<dynamic, dynamic> data) async => _goToMovieDetails(int.tryParse(data['id']));
Future<void> _grabEvent(Map<dynamic, dynamic> data) async => _goToMovieDetails(int.tryParse(data['id']));
Future<void> _healthEvent(Map<dynamic, dynamic> data) async => RadarrSystemStatusRouter().navigateTo(LunaState.navigatorKey.currentContext);
Future<void> _renameEvent(Map<dynamic, dynamic> data) async => _goToMovieDetails(int.tryParse(data['id']));
Future<void> _testEvent(Map<dynamic, dynamic> data) async => LunaModule.RADARR.launch();
Future<void> _goToMovieDetails(int movieId) async {
if(movieId != null) return RadarrMoviesDetailsRouter().navigateTo(
LunaState.navigatorKey.currentContext,
movieId: movieId,
);
}
Future<void> _grabEvent(Map<dynamic, dynamic> data) async {
int movieId = int.tryParse(data['id']);
if(movieId != null) return RadarrMoviesDetailsRouter().navigateTo(
LunaState.navigatorKey.currentContext,
movieId: movieId,
);
}
Future<void> _healthEvent(Map<dynamic, dynamic> data) async {
return RadarrSystemStatusRouter().navigateTo(LunaState.navigatorKey.currentContext);
}
Future<void> _renameEvent(Map<dynamic, dynamic> data) async {
int movieId = int.tryParse(data['id']);
if(movieId != null) return RadarrMoviesDetailsRouter().navigateTo(
LunaState.navigatorKey.currentContext,
movieId: movieId,
);
}
Future<void> _testEvent(Map<dynamic, dynamic> data) async {
return LunaModule.RADARR.launch();
}
}

View File

@@ -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';

View File

@@ -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<List<Object>> 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<RadarrRootFolder> rootFolders, List<RadarrQualityProfile> qualityProfiles, List<RadarrTag> 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<RadarrAddMovieDetailsState>().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<RadarrRootFolder> rootFolders,
List<RadarrQualityProfile> qualityProfiles,
List<RadarrTag> tags,
}) {
context.read<RadarrAddMovieDetailsState>().initializeAvailability();
context.read<RadarrAddMovieDetailsState>().initializeQualityProfile(qualityProfiles);
context.read<RadarrAddMovieDetailsState>().initializeRootFolder(rootFolders);
context.read<RadarrAddMovieDetailsState>().initializeTags(tags);
return LunaListView(
controller: scrollController,
children: [
RadarrAddMovieSearchResultTile(
movie: context.read<RadarrAddMovieDetailsState>().movie,
onTapShowOverview: true,
exists: false,
isExcluded: false,
),
RadarrAddMovieDetailsMonitoredTile(),
RadarrAddMovieDetailsRootFolderTile(),
RadarrAddMovieDetailsMinimumAvailabilityTile(),
RadarrAddMovieDetailsQualityProfileTile(),
RadarrAddMovieDetailsTagsTile(),
],
);
}
}

View File

@@ -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<RadarrRootFolder> rootFolders,
@required List<RadarrQualityProfile> qualityProfiles,
@required List<RadarrTag> 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<RadarrRootFolder> 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<RadarrQualityProfile> 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<RadarrTag> _tags = [];
List<RadarrTag> get tags => _tags;
@@ -78,6 +73,9 @@ class RadarrAddMovieDetailsState extends ChangeNotifier {
RadarrDatabaseValue.ADD_MOVIE_DEFAULT_TAGS.put((tags ?? []).map<int>((tag) => tag.id).toList());
notifyListeners();
}
void initializeTags(List<RadarrTag> 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;

View File

@@ -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';

View File

@@ -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),
],
);
}
}

View File

@@ -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),

View File

@@ -86,7 +86,7 @@ class _State extends State<RadarrCatalogueRoute> 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<RadarrCatalogueRoute> 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<RadarrCatalogueRoute> with AutomaticKeepAliveClientMi
bool ascending = context.read<RadarrState>().moviesSortAscending;
// Filter
List<RadarrMovie> 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

View File

@@ -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<RadarrMoviesEditState, LunaLoadingState>((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<RadarrState>().movies,
context.watch<RadarrState>().qualityProfiles,
context.watch<RadarrState>().tags,
context.select<RadarrState, Future<List<RadarrMovie>>>((state) => state.movies),
context.select<RadarrState, Future<List<RadarrQualityProfile>>>((state) => state.qualityProfiles),
context.select<RadarrState, Future<List<RadarrTag>>>((state) => state.tags),
]),
builder: (context, AsyncSnapshot<List<Object>> 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<RadarrQualityProfile> qualityProfiles, List<RadarrTag> tags) {
return ChangeNotifierProvider(
create: (_) => RadarrMoviesEditState(movie: movie, qualityProfiles: qualityProfiles, tags: tags),
builder: (context, _) {
if(context.watch<RadarrMoviesEditState>().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<RadarrQualityProfile> qualityProfiles,
List<RadarrTag> tags,
}) {
if(context.read<RadarrMoviesEditState>().movie == null) {
context.read<RadarrMoviesEditState>().movie = movie;
context.read<RadarrMoviesEditState>().initializeQualityProfile(qualityProfiles);
context.read<RadarrMoviesEditState>().initializeTags(tags);
}
return LunaListView(
controller: scrollController,
children: [
RadarrMoviesEditMonitoredTile(),
RadarrMoviesEditMinimumAvailabilityTile(),
RadarrMoviesEditQualityProfileTile(profiles: qualityProfiles),
RadarrMoviesEditPathTile(),
RadarrMoviesEditTagsTile(),
],
);
}
}

View File

@@ -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<RadarrQualityProfile> qualityProfiles,
@required List<RadarrTag> 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<RadarrQualityProfile> 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<RadarrTag> _tags;
List<RadarrTag> get tags => _tags;
@@ -65,4 +74,7 @@ class RadarrMoviesEditState extends ChangeNotifier {
_tags = tags;
notifyListeners();
}
void initializeTags(List<RadarrTag> tags) {
_tags = (tags ?? []).where((tag) => (movie.tags ?? []).contains(tag.id)).toList();
}
}

View File

@@ -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';

View File

@@ -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(),
],
);
}
}

View File

@@ -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<RadarrMoviesEditState>().state,
onTap: () async {
context.read<RadarrMoviesEditState>().state = LunaLoadingState.ACTIVE;
RadarrMovie updatedMovie = movie.updateEdits(context.read<RadarrMoviesEditState>());
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<RadarrMoviesEditState>().state,
onTap: () async {
context.read<RadarrMoviesEditState>().state = LunaLoadingState.ACTIVE;
if(context.read<RadarrMoviesEditState>().movie != null) {
RadarrMovie movie = context.read<RadarrMoviesEditState>().movie.updateEdits(context.read<RadarrMoviesEditState>());
bool result = await RadarrAPIHelper().updateMovie(context: context, movie: movie);
if(result) Navigator.of(context).lunaSafetyPop();
}
},
);
}
}

View File

@@ -0,0 +1,3 @@
export 'manual_import/route.dart';
export 'manual_import/state.dart';
export 'manual_import/widgets.dart';

View File

@@ -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<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
@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<RadarrManualImportState, Future<RadarrFileSystem>>((state) => state.directories),
builder: (context, AsyncSnapshot<RadarrFileSystem> 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<RadarrManualImportState>().fetchDirectories(
context,
context.read<RadarrManualImportState>().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<RadarrManualImportState, String>(
selector: (_, state) => state.currentPath,
builder: (context, path, _) {
List<RadarrFileSystemDirectory> 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<RadarrFileSystemDirectory> _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();
}
}

View File

@@ -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<RadarrFileSystem> _directories;
Future<RadarrFileSystem> get directories => _directories;
void fetchDirectories(BuildContext context, String path) {
if(context.read<RadarrState>().enabled) {
_directories = context.read<RadarrState>().api.fileSystem.get(
path: path,
includeFiles: false,
allowFoldersWithoutTrailingSlashes: false,
).whenComplete(() => currentPath = path ?? '');
}
notifyListeners();
}
}

View File

@@ -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';

View File

@@ -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<RadarrManualImportPathBar> createState() => _State();
}
class _State extends State<RadarrManualImportPathBar> {
@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<RadarrManualImportState>().currentPathTextController,
autofocus: false,
onChanged: (value) {
context.read<RadarrManualImportState>().currentPath = value;
if(value.endsWith('/') || value.isEmpty) {
context.read<RadarrManualImportState>().fetchDirectories(context, value);
}
},
margin: EdgeInsets.fromLTRB(12.0, 0.0, 12.0, 14.0),
),
),
],
),
padding: EdgeInsets.only(top: 1.0, bottom: 1.0),
);
}
}

View File

@@ -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<RadarrManualImportState>().currentPath;
RadarrAPIHelper().quickImport(context: context, path: path);
},
),
LunaButton.text(
text: 'radarr.Interactive'.tr(),
backgroundColor: LunaColours.orange,
onTap: () async {
String path = context.read<RadarrManualImportState>().currentPath;
RadarrManualImportDetailsRouter().navigateTo(
context,
path: (path ?? '').isNotEmpty ? path : '/',
);
},
),
],
);
}
}

View File

@@ -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<StatefulWidget> createState() => _State();
}
class _State extends State<RadarrManualImportDirectoryTile> {
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<RadarrManualImportState>().fetchDirectories(
context,
widget.directory.path,
);
}
}
);
}
}

View File

@@ -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<StatefulWidget> createState() => _State();
}
class _State extends State<RadarrManualImportParentDirectoryTile> {
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<RadarrManualImportState>().fetchDirectories(
context,
widget.fileSystem.parent,
);
}
},
);
}
}

View File

@@ -0,0 +1,3 @@
export 'manual_import_details/route.dart';
export 'manual_import_details/state.dart';
export 'manual_import_details/widgets.dart';

View File

@@ -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<void> 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<StatefulWidget> 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<RadarrManualImportDetailsState, Future<List<RadarrManualImport>>>((state) => state.manualImport),
builder: (context, AsyncSnapshot<List<RadarrManualImport>> snapshot) {
if(snapshot.hasError) {
if(snapshot.connectionState != ConnectionState.waiting) LunaLogger().error(
'Unable to fetch Radarr manual import: ${context.read<RadarrManualImportDetailsState>().path}',
snapshot.error,
snapshot.stackTrace,
);
return LunaMessage.error(
onTap: () => context.read<RadarrManualImportDetailsState>().fetchManualImport(context),
);
}
if(snapshot.connectionState == ConnectionState.done && snapshot.hasData) {
return _list(context, manualImport: snapshot.data);
}
return LunaLoader();
},
);
}
Widget _list(BuildContext context, {
@required List<RadarrManualImport> manualImport,
}) {
if((manualImport?.length ?? 0) == 0) return LunaMessage(
text: 'radarr.NoFilesFound'.tr(),
buttonText: 'lunasea.Refresh'.tr(),
onTap: () => context.read<RadarrManualImportDetailsState>().fetchManualImport(context),
);
return LunaListViewBuilder(
controller: scrollController,
itemCount: manualImport.length,
itemBuilder: (context, index) => RadarrManualImportDetailsImportTile(manualImport: manualImport[index]),
);
}
}

View File

@@ -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<List<RadarrManualImport>> _manualImport;
Future<List<RadarrManualImport>> get manualImport => _manualImport;
Future<void> fetchManualImport(BuildContext context) async {
if(context.read<RadarrState>().enabled) {
_manualImport = context.read<RadarrState>().api.manualImport.get(
folder: path,
filterExistingFiles: true,
);
}
notifyListeners();
}
}

View File

@@ -0,0 +1 @@
export 'widgets/import_tile.dart';

View File

@@ -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),
);
}
}

View File

@@ -33,25 +33,32 @@ class _State extends State<RadarrMoreRoute> 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),
),
],

View File

@@ -35,16 +35,15 @@ class _State extends State<_RadarrMoviesDetailsRoute> with LunaLoadCallbackMixin
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
RadarrMovie movie;
PageController _pageController;
bool _loaded = false;
@override
Future<void> loadCallback() async {
if(widget.movieId > 0) {
_findMovie(await context.read<RadarrState>().movies);
RadarrMovie result = _findMovie(await context.read<RadarrState>().movies);
setState(() => movie = result);
context.read<RadarrState>().fetchQualityProfiles();
context.read<RadarrState>().fetchTags();
await context.read<RadarrState>().resetSingleMovie(widget.movieId);
_findMovie(await context.read<RadarrState>().movies);
}
}
@@ -54,15 +53,11 @@ class _State extends State<_RadarrMoviesDetailsRoute> with LunaLoadCallbackMixin
_pageController = PageController(initialPage: RadarrDatabaseValue.NAVIGATION_INDEX_MOVIE_DETAILS.data);
}
void _findMovie(List<RadarrMovie> movies) {
RadarrMovie _movie = movies.firstWhere(
RadarrMovie _findMovie(List<RadarrMovie> movies) {
return movies.firstWhere(
(movie) => movie.id == widget.movieId,
orElse: () => null,
);
if(mounted) setState(() {
movie = _movie;
_loaded = true;
});
}
List<RadarrTag> _findTags(List<int> tagIds, List<RadarrTag> 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<RadarrState>(
builder: (context, state, _) => FutureBuilder(
future: Future.wait([state.qualityProfiles, state.tags]),
future: Future.wait([state.qualityProfiles, state.tags, state.movies]),
builder: (context, AsyncSnapshot<List<Object>> 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<RadarrTag> tags = _findTags(movie.tags, snapshot.data[1]);
return _pages(qualityProfile, tags);

View File

@@ -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';

View File

@@ -37,11 +37,12 @@ class _State extends State<RadarrMovieDetailsFilesFileBlock> {
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,

View File

@@ -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<IconData> icons = [Icons.subject_rounded, Icons.insert_drive_file_outlined, Icons.history, Icons.person];
static const List<String> titles = ['Overview', 'Files', 'History', 'Cast & Crew'];
static List<ScrollController> 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<StatefulWidget> createState() => _State();
}
class _State extends State<RadarrMovieDetailsNavigationBar> {
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<void> _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<void> _manual() async => RadarrReleasesRouter().navigateTo(context, movieId: widget.movie.id ?? -1);
}

View File

@@ -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<StatefulWidget> createState() => _State();
}
class _State extends State<RadarrMovieDetailsOverviewDownloadButtons> {
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<void> _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<void> _manual(BuildContext context) async => RadarrReleasesRouter().navigateTo(context, movieId: widget.movie.id ?? -1);
}

View File

@@ -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),
],

View File

@@ -36,7 +36,6 @@ class _State extends State<RadarrMovieDetailsOverviewPage> with AutomaticKeepAli
children: [
RadarrMovieDetailsOverviewDescriptionTile(movie: widget.movie),
RadarrMovieDetailsOverviewLinksSection(movie: widget.movie),
RadarrMovieDetailsOverviewDownloadButtons(movie: widget.movie),
RadarrMovieDetailsOverviewInformationBlock(
movie: widget.movie,
qualityProfile: widget.qualityProfile,

View File

@@ -18,15 +18,17 @@ class _State extends State<RadarrReleasesTile> {
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<RadarrReleasesTile> {
List<LunaButton> _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,

View File

@@ -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<StatefulWidget> 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<SABnzbdHistoryTile> {
@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<LunaTableContent> _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<LunaHighlightedNode> _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<void> _enterStages(BuildContext context) async {
List<LunaButton> _expandedButtons() {
return [
LunaButton.text(
text: 'Stages',
onTap: () async => _enterStages(),
),
LunaButton.text(
text: 'Delete',
backgroundColor: LunaColours.red,
onTap: () async => _delete(),
),
];
}
Future<void> _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<void> _handlePopup(BuildContext context) async {
List values = await SABnzbdDialogs.historySettings(context, data.name, data.failed);
Future<void> _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<void> _delete(BuildContext context) async {
Future<void> _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<void> _password(BuildContext context) async {
Future<void> _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<void> _retry(BuildContext context) async {
SABnzbdAPI.from(Database.currentProfileObject).retryFailedJob(data.nzoId)
.then((_) => _handleRefresh(context, 'Retrying Job'))
Future<void> _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();
}
}

View File

@@ -49,7 +49,7 @@ class SearchResultTile extends StatelessWidget {
List<LunaButton> _tableButtons(BuildContext context) {
return [
LunaButton.slim(
LunaButton.text(
text: 'search.Download'.tr(),
onTap: () async => _sendToClient(context),
),

View File

@@ -79,7 +79,7 @@ class _State extends State<SettingsHeaderRoute> with LunaScrollControllerMixin {
Widget _addHeader() {
return LunaButtonContainer(
children: [
LunaButton(
LunaButton.text(
text: 'Add Header',
onTap: () async {
await HeaderUtility().addHeader(context, headers: _headers());

View File

@@ -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,
),
],
),
],
);
}

View File

@@ -31,7 +31,7 @@ class _State extends State<SettingsAccountSignedInPage> {
Widget _signOutButton() {
return LunaButtonContainer(
children: [
LunaButton(
LunaButton.text(
text: 'Sign Out',
backgroundColor: LunaColours.red,
onTap: () async {

Some files were not shown because too many files have changed in this diff Show More