diff --git a/.all-contributorsrc b/.all-contributorsrc
index ecb2e0c0..fa93d456 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -61,6 +61,15 @@
"contributions": [
"translation"
]
+ },
+ {
+ "login": "iFelix18",
+ "name": "Davide",
+ "avatar_url": "https://avatars.githubusercontent.com/u/19800006?v=4",
+ "profile": "https://github.com/iFelix18",
+ "contributions": [
+ "translation"
+ ]
}
],
"contributorsPerLine": 7,
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 61356df5..74bd826c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,42 @@
# LunaSea Changelog
+## v5.0.0 (50000013)
+
+#### NEW
+- `[-Arr/Releases]` Match torrent seeder colours to the web GUI
+- `[Firebase/Analytics]` Added analytics for breadcrumb tracking in crashes
+- `[Logger]` Utilize a new, custom built on-device logging system
+- `[Logger]` Compact/delete old log entries once the log count passes 100
+- `[Logger]` Exported logs are now in JSON format
+- `[Routing]` Protect all module routes by showing a "Not Enabled" screen when the module is not enabled
+- `[Settings/System]` Added toggle to disable Firebase Analytics
+- `[Tautulli/Activity]` Show custom season titles from Plex's new TV agent
+
+#### TWEAKS
+- `[Radarr/Files]` Add languages to file block
+- `[Radarr/Files]` Small changes to table titles
+- `[System/Logs]` Reimplemented logger view
+- `[System/Logs]` Removed viewing the stack trace within the application (still viewable within the exported logs)
+- `[System/Logs]` Added the exception to the expanded table instead of a separate dialog popup
+- `[Tautulli/Activity]` Minor UI tweaks including showing the full season title and italicizing the episode title
+- `[UI/Navbar]` Jump instead of animate to page when tapping a navbar item
+
+#### FIXES
+- `[Firebase/Crashes]` Do not log DioError/networking exceptions
+- `[Flutter]` Updated packages
+- `[Flutter]` Upgrade all Comet.Tools packages to null-safety/NNBD
+- `[Radarr/Releases]` Negative format scores were not being shown
+- `[Share]` The sharesheet could break after the first time it was opened
+- `[Strings]` Safe-guard many substring operations
+- `[Tautulli/Activity]` Play/paused/buffering icon was not properly left aligned to the text
+- `[Tautulli/Users]` User images would not be fetched on newer versions of Tautulli
+- `[UI/UX]` Modal scroll controller was not being attached to the ListView in a bottom modal sheet
+- `[UI/UX]` Do not log errors when network images fail to load
+- `[UI/UX]` Prevent attempting to load background images that are passed an empty URL
+- `[URLs]` Safe-guard launching specific invalid URLs
+
+---
+
## v5.0.0 (50000012)
#### NEW
diff --git a/README.md b/README.md
index 9936603d..dce941fb 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-#
LunaSea
+#
LunaSea
[](https://www.lunasea.app/appstore)
[](https://www.lunasea.app/playstore)
@@ -60,7 +60,7 @@ Thank you to all tools and services for offering free subscriptions for open-sou
## Contributors
-[](#contributors-)
+[](#contributors-)
Thank you to everyone who has helped contribute to LunaSea, from development to localization efforts!
@@ -76,6 +76,7 @@ Thank you to everyone who has helped contribute to LunaSea, from development to
 Allan Nordhøy 🌍 |
 cpt-kuesel 🌍 |
 KovalevArtem 🌍 |
+  Davide 🌍 |
diff --git a/assets/changelog.json b/assets/changelog.json
index 6cf55a61..512ba84a 100644
--- a/assets/changelog.json
+++ b/assets/changelog.json
@@ -1,38 +1,129 @@
{
- "motd": "This build now includes support for infinite scrolling in the indexer search! Each page will return 50 results, and will load the next page of results as you near the bottom of the list. Please note that each new page load is an additional API hit, so please consider that when using indexers with limited API calls.\n\nBecause of the added support for infinite scrolling, there is no longer the option to sort or filter the fetched results, as these are not directly supported within the newznab specification. Client-sided sorting and filtering are not compatible with the concept of infinite scroll, so now releases will be shown in the indexer's returned positioning.",
+ "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": "Radarr/\nFiles",
+ "module": "-Arr/\nReleases",
"changes": [
- "Add video and audio codec to the media file card"
+ "Match torrent seeder colours to the web GUI"
]
},
{
- "module": "Search",
+ "module": "Firebase/\nAnalytics",
"changes": [
- "Now supports infinite scrolling for loading additional pages of results",
- "Much faster parsing of XML responses",
- "Fully adapted for localization"
+ "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": "Search",
+ "module": "Radarr/\nFiles",
"changes": [
- "Now returns 50 results per infinite scrolling page",
- "Removed support for sorting the catalogue and instead show the results in the order the indexer returns the data",
- "Removed support for filtering releases by a search query",
- "Minor tweaks to the icons on the indexer, category, and subcategory tiles"
+ "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": "Search",
+ "module": "Firebase/\nCrashes",
"changes": [
- "Search would only return 10 results",
- "Age of the release could show as \"Unknown Age\" in some cases"
+ "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"
]
}
]
diff --git a/assets/images/lidarr/noalbumart.png b/assets/images/blanks/audio.png
similarity index 100%
rename from assets/images/lidarr/noalbumart.png
rename to assets/images/blanks/audio.png
diff --git a/assets/images/tautulli/platform.png b/assets/images/blanks/platform.png
similarity index 100%
rename from assets/images/tautulli/platform.png
rename to assets/images/blanks/platform.png
diff --git a/assets/images/tautulli/streams.png b/assets/images/blanks/stream.png
similarity index 100%
rename from assets/images/tautulli/streams.png
rename to assets/images/blanks/stream.png
diff --git a/assets/images/lidarr/noartistposter.png b/assets/images/blanks/user.png
similarity index 100%
rename from assets/images/lidarr/noartistposter.png
rename to assets/images/blanks/user.png
diff --git a/assets/images/radarr/nomovieposter.png b/assets/images/blanks/video.png
similarity index 100%
rename from assets/images/radarr/nomovieposter.png
rename to assets/images/blanks/video.png
diff --git a/assets/branding/icon.png b/assets/images/branding/icon.png
similarity index 100%
rename from assets/branding/icon.png
rename to assets/images/branding/icon.png
diff --git a/assets/branding/icon_adaptive.png b/assets/images/branding/icon_adaptive.png
similarity index 100%
rename from assets/branding/icon_adaptive.png
rename to assets/images/branding/icon_adaptive.png
diff --git a/assets/branding/icon_drawer.png b/assets/images/branding/icon_drawer.png
similarity index 100%
rename from assets/branding/icon_drawer.png
rename to assets/images/branding/icon_drawer.png
diff --git a/assets/branding/splash.png b/assets/images/branding/splash.png
similarity index 100%
rename from assets/branding/splash.png
rename to assets/images/branding/splash.png
diff --git a/assets/images/colors/accent.png b/assets/images/colors/accent.png
deleted file mode 100644
index 20783ca4..00000000
Binary files a/assets/images/colors/accent.png and /dev/null differ
diff --git a/assets/images/colors/black.png b/assets/images/colors/black.png
deleted file mode 100644
index 7c301776..00000000
Binary files a/assets/images/colors/black.png and /dev/null differ
diff --git a/assets/images/colors/secondary.png b/assets/images/colors/secondary.png
deleted file mode 100644
index 097d2d55..00000000
Binary files a/assets/images/colors/secondary.png and /dev/null differ
diff --git a/assets/images/sonarr/noseriesposter.png b/assets/images/sonarr/noseriesposter.png
deleted file mode 100644
index 6607ea21..00000000
Binary files a/assets/images/sonarr/noseriesposter.png and /dev/null differ
diff --git a/assets/images/tautulli/nouserthumb.png b/assets/images/tautulli/nouserthumb.png
deleted file mode 100644
index 9c9e33ac..00000000
Binary files a/assets/images/tautulli/nouserthumb.png and /dev/null differ
diff --git a/assets/localization/de.json b/assets/localization/de.json
index d4e6f7df..bf318c30 100644
--- a/assets/localization/de.json
+++ b/assets/localization/de.json
@@ -1,16 +1,17 @@
{
"lunasea.AnErrorHasOccurred": "Ein Fehler ist aufgetreten",
+ "lunasea.Cancel": "Abbrechen",
"lunasea.ChangeProfiles": "Profile wechseln",
- "lunasea.CheckLogsMessage": "ĂśberprĂĽfe die Logs fĂĽr mehr Details",
+ "lunasea.CheckLogsMessage": "ĂśberprĂĽfe die Protokolle fĂĽr mehr Details",
"lunasea.ComingSoon": "Bald verfĂĽgbar",
- "lunasea.Dashboard": "Dashboard",
+ "lunasea.Dashboard": "Instrumentenanlage",
"lunasea.GoBack": "ZurĂĽck",
- "lunasea.ModuleIsNotEnabled": "{} Ist Nicht Aktiviert",
+ "lunasea.ModuleIsNotEnabled": "{} ist nicht aktiviert",
"lunasea.Refresh": "Aktualisieren",
- "lunasea.ReturnToDashboard": "ZurĂĽck zum Dashboard",
- "lunasea.SearchTextBar": "Suche…",
+ "lunasea.ReturnToDashboard": "ZurĂĽck zur Instrumentenanlage",
+ "lunasea.SearchTextBar": "Suche …",
"lunasea.Settings": "Einstellungen",
- "lunasea.TryAgain": "Erneut Versuchen",
+ "lunasea.TryAgain": "Erneut versuchen",
"lunasea.Unknown": "Unbekannt",
"radarr.AddMovie": "Film hinzufĂĽgen",
"radarr.AddMovieAndSearch": "HinzufĂĽgen + Suchen",
@@ -27,7 +28,7 @@
"radarr.DateAdded": "HinzugefĂĽgt am",
"radarr.Discover": "Entdecken",
"radarr.DownloadFailed": "Herunterladen gescheitert",
- "radarr.DownloadIgnored": "Download ignoriert",
+ "radarr.DownloadIgnored": "Herunterladen ignoriert",
"radarr.EditMovie": "Film bearbeiten",
"radarr.FilterCatalogue": "Katalog filtern",
"radarr.GrabbedFrom": "Gegrabbed von {}",
@@ -70,7 +71,7 @@
"radarr.SortCatalogue": "Katalog sortieren",
"radarr.Studio": "Studio",
"radarr.SystemStatus": "Systemstatus",
- "radarr.SystemStatusDescription": "Systemstatus & Speicherplatz",
+ "radarr.SystemStatusDescription": "Systemstatus und Speicherplatz",
"radarr.Tags": "Tags",
"radarr.TagsDescription": "Verwalte deine Tags",
"radarr.Type": "Typ",
@@ -88,8 +89,8 @@
"search.FailedToDownloadNZB": "Herunterladen der NZB fehlgeschlagen",
"search.DownloadToDevice": "Auf Gerät herunterladen",
"search.DownloadingNZBToDevice": "Lädt NZB auf deinem Gerät herunter",
- "search.Downloading": "Herunterladen…",
- "search.Download": "Download",
+ "search.Downloading": "Herunterladen …",
+ "search.Download": "Herunterladen",
"search.Comments": "Kommentare",
"search.Categories": "Kategorien",
"search.Category": "Kategorie",
@@ -99,9 +100,77 @@
"search.Subcategories": "Unterkategorien",
"search.Size": "Größe",
"search.SentTo": "An {} gesendet",
- "search.SentNZBData": "NZB Daten gesendet",
+ "search.SentNZBData": "NZB-Daten gesendet",
"search.Search": "Suche",
"search.Results": "Ergebnisse",
"search.NoSubcategoriesFound": "Keine Unterkategorien gefunden",
- "search.NoResultsFound": "Keine Ergebnisse gefunden"
+ "search.NoResultsFound": "Keine Ergebnisse gefunden",
+ "tautulli.TerminateSessionFailed": "Abbrechen der Sitzung fehlgeschlagen",
+ "tautulli.TerminateSession": "Sitzung abbrechen",
+ "tautulli.TerminatedSession": "Abgebrochene Sitzung",
+ "tautulli.TerminationConfirmMessage": "Möchtest du diese Sitzung abbrechen?",
+ "tautulli.Terminate": "Abbrechen",
+ "tautulli.Episode": "Folge {}",
+ "tautulli.Year": "Jahr",
+ "tautulli.ViewWebGUI": "Weboberfläche ansehen",
+ "tautulli.Video": "Video",
+ "tautulli.Users": "Benutzer",
+ "tautulli.User": "Benutzer",
+ "tautulli.Transcodes": "Transkodierungen",
+ "tautulli.Transcode": "Transkodieren",
+ "tautulli.Title": "Titel",
+ "tautulli.Throttled": "Throttled",
+ "tautulli.TerminationMessage": "Abbruch-BegrĂĽndung",
+ "tautulli.TerminationAttachMessage": "Du kannst optional unten eine Abbruch-BegrĂĽndung angeben.",
+ "tautulli.Subtitle": "Untertitel",
+ "tautulli.Stream": "Stream",
+ "tautulli.SessionsMany": "{} Sitzungen",
+ "tautulli.SessionsOne": "1 Sitzung",
+ "tautulli.Sessions": "Sitzungen",
+ "tautulli.SessionEnded": "Sitzung beendet",
+ "tautulli.Season": "Staffel {}",
+ "tautulli.Quality": "Qualität",
+ "tautulli.Product": "Produkt",
+ "tautulli.Player": "Player",
+ "tautulli.Platform": "Plattform",
+ "tautulli.NoActiveStreams": "Keine aktiven Streams",
+ "tautulli.None": "Nichts",
+ "tautulli.More": "Mehr",
+ "tautulli.Metadata": "Metadaten",
+ "tautulli.Location": "Ort",
+ "tautulli.Library": "Bibliothek",
+ "tautulli.History": "Verlauf",
+ "tautulli.ETA": "Restzeit",
+ "tautulli.Duration": "Dauer",
+ "tautulli.DirectStreams": "Direct Streams",
+ "tautulli.DirectStream": "Direct Stream",
+ "tautulli.DirectPlays": "Direct Plays",
+ "tautulli.DirectPlay": "Direct Play",
+ "tautulli.DeletingTemporarySessionsFailed": "Löschen der temporären Sitzungen fehlgeschlagen",
+ "tautulli.DeletingTemporarySessionsDescription": "Temporäre Sitzungen werden gelöscht",
+ "tautulli.DeletingTemporarySessions": "Lösche temporäre Sitzungen…",
+ "tautulli.DeletingImageCacheFailed": "Löschen des Bild-Zwischenspeicher fehlgeschlagen",
+ "tautulli.DeletingImageCacheDescription": "Tautulli Bild-Zwischenspeicher wird gelöscht",
+ "tautulli.DeletingImageCache": "Lösche Bild-Zwischenspeicher…",
+ "tautulli.DeletingCacheFailed": "Löschen des Zwischenspeichers fehlgeschlagen",
+ "tautulli.DeletingCacheDescription": "Tautulli Zwischenspeicher wird gelöscht",
+ "tautulli.DeletingCache": "Lösche Zwischenspeicher…",
+ "tautulli.DeleteTemporarySessions": "Lösche temporäre Sitzungen",
+ "tautulli.DeleteImageCache": "Bild-Zwischenspeicher löschen",
+ "tautulli.DeleteCache": "Zwischenspeicher löschen",
+ "tautulli.Copy": "Kopieren",
+ "tautulli.Container": "Container",
+ "tautulli.Burn": "Einbrennen",
+ "tautulli.Bandwidth": "Bandbreite",
+ "tautulli.BackupDatabase": "Datenbank sichern",
+ "tautulli.BackupConfiguration": "Konfiguration sichern",
+ "tautulli.BackingUpDatabaseFailed": "Sichern der Datenbank fehlgeschlagen",
+ "tautulli.BackingUpDatabaseDescription": "Deine Datenbank wird im Hintergrund gesichert",
+ "tautulli.BackingUpDatabase": "Datenbank wird gesichert…",
+ "tautulli.BackingUpConfigurationFailed": "Sichern der Konfiguration fehlgeschlagen",
+ "tautulli.BackingUpConfigurationDescription": "Deine Konfiguration wird im Hintergrund gesichert",
+ "tautulli.BackingUpConfiguration": "Konfiguration wird gesichert…",
+ "tautulli.Audio": "Audio",
+ "tautulli.ActivityDetails": "Aktivitätsdetails",
+ "tautulli.Activity": "Aktivität"
}
\ No newline at end of file
diff --git a/assets/localization/en.json b/assets/localization/en.json
index 63789b2b..88e2381c 100644
--- a/assets/localization/en.json
+++ b/assets/localization/en.json
@@ -1,5 +1,6 @@
{
"lunasea.AnErrorHasOccurred": "An Error Has Occurred",
+ "lunasea.Cancel": "Cancel",
"lunasea.ChangeProfiles": "Change Profiles",
"lunasea.CheckLogsMessage": "Check the logs for more details",
"lunasea.ComingSoon": "Coming Soon",
@@ -103,5 +104,73 @@
"search.SentNZBData": "Sent NZB Data",
"search.SentTo": "Sent to {}",
"search.Size": "Size",
- "search.Subcategories": "Subcategories"
+ "search.Subcategories": "Subcategories",
+ "tautulli.Activity": "Activity",
+ "tautulli.ActivityDetails": "Activity Details",
+ "tautulli.Audio": "Audio",
+ "tautulli.BackingUpConfiguration": "Backing Up Configuration…",
+ "tautulli.BackingUpConfigurationDescription": "Backing up your configuration in the background",
+ "tautulli.BackingUpConfigurationFailed": "Failed to Backup Configuration",
+ "tautulli.BackingUpDatabase": "Backing Up Database…",
+ "tautulli.BackingUpDatabaseDescription": "Backing up your database in the background",
+ "tautulli.BackingUpDatabaseFailed": "Failed to Backup Database",
+ "tautulli.BackupConfiguration": "Backup Configuration",
+ "tautulli.BackupDatabase": "Backup Database",
+ "tautulli.Bandwidth": "Bandwidth",
+ "tautulli.Burn": "Burn",
+ "tautulli.Container": "Container",
+ "tautulli.Copy": "Copy",
+ "tautulli.DeleteCache": "Delete Cache",
+ "tautulli.DeleteImageCache": "Delete Image Cache",
+ "tautulli.DeleteTemporarySessions": "Delete Temporary Sessions",
+ "tautulli.DeletingCache": "Deleting Cache…",
+ "tautulli.DeletingCacheDescription": "Tautulli cache is being deleted",
+ "tautulli.DeletingCacheFailed": "Failed to Delete Cache",
+ "tautulli.DeletingImageCache": "Deleting Image Cache…",
+ "tautulli.DeletingImageCacheDescription": "Tautulli image cache is being deleted",
+ "tautulli.DeletingImageCacheFailed": "Failed to Delete Image Cache",
+ "tautulli.DeletingTemporarySessions": "Deleting Temporary Sessions…",
+ "tautulli.DeletingTemporarySessionsDescription": "Temporary sessions are being deleted",
+ "tautulli.DeletingTemporarySessionsFailed": "Failed to Delete Temporary Sessions",
+ "tautulli.DirectPlay": "Direct Play",
+ "tautulli.DirectPlays": "Direct Plays",
+ "tautulli.DirectStream": "Direct Stream",
+ "tautulli.DirectStreams": "Direct Streams",
+ "tautulli.Duration": "Duration",
+ "tautulli.Episode": "Episode {}",
+ "tautulli.ETA": "ETA",
+ "tautulli.History": "History",
+ "tautulli.Library": "Library",
+ "tautulli.Location": "Location",
+ "tautulli.Metadata": "Metadata",
+ "tautulli.More": "More",
+ "tautulli.None": "None",
+ "tautulli.NoActiveStreams": "No Active Streams",
+ "tautulli.Platform": "Platform",
+ "tautulli.Player": "Player",
+ "tautulli.Product": "Product",
+ "tautulli.Quality": "Quality",
+ "tautulli.Season": "Season {}",
+ "tautulli.SessionEnded": "Session Ended",
+ "tautulli.Sessions": "Sessions",
+ "tautulli.SessionsOne": "1 Session",
+ "tautulli.SessionsMany": "{} Sessions",
+ "tautulli.Stream": "Stream",
+ "tautulli.Subtitle": "Subtitle",
+ "tautulli.Terminate": "Terminate",
+ "tautulli.TerminationAttachMessage": "You can optionally attach a termination message below.",
+ "tautulli.TerminationConfirmMessage": "Do you want to terminate this session?",
+ "tautulli.TerminationMessage": "Termination Message",
+ "tautulli.TerminatedSession": "Terminated Session",
+ "tautulli.TerminateSession": "Terminate Session",
+ "tautulli.TerminateSessionFailed": "Failed to Terminate Session",
+ "tautulli.Throttled": "Throttled",
+ "tautulli.Title": "Title",
+ "tautulli.Transcode": "Transcode",
+ "tautulli.Transcodes": "Transcodes",
+ "tautulli.User": "User",
+ "tautulli.Users": "Users",
+ "tautulli.Video": "Video",
+ "tautulli.ViewWebGUI": "View Web GUI",
+ "tautulli.Year": "Year"
}
\ No newline at end of file
diff --git a/assets/localization/fr.json b/assets/localization/fr.json
index 3c44a329..0d1fd530 100644
--- a/assets/localization/fr.json
+++ b/assets/localization/fr.json
@@ -1,7 +1,8 @@
{
"lunasea.AnErrorHasOccurred": "Une erreur s'est produite",
+ "lunasea.Cancel": "Annuler",
"lunasea.ChangeProfiles": "Changer de profil",
- "lunasea.CheckLogsMessage": "Vérifier les logs pour plus de détails",
+ "lunasea.CheckLogsMessage": "Vérifier les journaux pour plus de détails",
"lunasea.ComingSoon": "Prochainement",
"lunasea.Dashboard": "Tableau de bord",
"lunasea.GoBack": "Retour en arrière",
@@ -26,8 +27,8 @@
"radarr.CutoffUnmet": "Qualité préférée non atteinte",
"radarr.DateAdded": "Date ajoutée",
"radarr.Discover": "Découvrir",
- "radarr.DownloadFailed": "Echec du téléchargement",
- "radarr.DownloadIgnored": "Télécharger Ignoré",
+ "radarr.DownloadFailed": "Échec du téléchargement",
+ "radarr.DownloadIgnored": "Téléchargement ignoré",
"radarr.EditMovie": "Modifier le film",
"radarr.FilterCatalogue": "Catalogue des filtres",
"radarr.GrabbedFrom": "Récupéré depuis {}",
@@ -51,7 +52,7 @@
"radarr.NoMoviesFound": "Aucun film trouvé",
"radarr.NoResultsFound": "Aucun résultat",
"radarr.NoSummaryIsAvailable": "Aucun résumé n'est disponible.",
- "radarr.QualityProfile": "Profil de la qualité",
+ "radarr.QualityProfile": "Profil de qualité",
"radarr.Queue": "File d'attente",
"radarr.QueueDescription": "Voir le contenu actif et en attente",
"radarr.RefreshMovie": "Rafraichir le film",
@@ -71,8 +72,8 @@
"radarr.Studio": "Studio",
"radarr.SystemStatus": "État du système",
"radarr.SystemStatusDescription": "État du système et espace disque",
- "radarr.Tags": "Tags",
- "radarr.TagsDescription": "Gérer vos tags",
+ "radarr.Tags": "Étiquettes",
+ "radarr.TagsDescription": "Gérer vos étiquettes",
"radarr.Type": "Type",
"radarr.UnmonitorMovie": "Ne plus surveiller le film",
"radarr.Unmonitored": "Non surveillé",
@@ -82,5 +83,94 @@
"radarr.ViewWebGUI": "Voir l'interface web",
"radarr.Wanted": "Recherché",
"radarr.Weight": "Poids",
- "radarr.Year": "Année"
+ "radarr.Year": "Année",
+ "search.Subcategories": "Sous-catégories",
+ "search.Size": "Taille",
+ "search.SentTo": "Envoyé à {}",
+ "search.SentNZBData": "Données NZB envoyées",
+ "search.Search": "Rechercher",
+ "search.Results": "Résultats",
+ "search.NoSubcategoriesFound": "Aucune sous-catégorie trouvée",
+ "search.NoResultsFound": "Aucun résultat",
+ "search.NoCategoriesFound": "Aucune catégorie trouvée",
+ "search.FailedToSend": "Erreur lors de l'envoi",
+ "search.FailedToDownloadNZB": "Erreur lors du téléchargement du NZB",
+ "search.DownloadToDevice": "Télécharger sur l'appareil",
+ "search.DownloadingNZBToDevice": "Télécharger le NZB sur votre appareil",
+ "search.Downloading": "Téléchargement en cours…",
+ "search.Download": "Télécharger",
+ "search.Comments": "Commentaires",
+ "search.Categories": "Catégories",
+ "search.Category": "Catégorie",
+ "search.Alphabetical": "Alphabétique",
+ "search.AllSubcategories": "Toutes les sous-catégories",
+ "search.Age": "Âge",
+ "tautulli.Burn": "Brûler",
+ "tautulli.Year": "Année",
+ "tautulli.ViewWebGUI": "Afficher l'interface Web",
+ "tautulli.Video": "Vidéo",
+ "tautulli.Users": "Utilisateurs",
+ "tautulli.User": "Utilisateur",
+ "tautulli.Transcodes": "Transcodes",
+ "tautulli.Transcode": "Transcode",
+ "tautulli.Title": "Titre",
+ "tautulli.Throttled": "Throttled",
+ "tautulli.TerminateSessionFailed": "Échec de la fin de la session",
+ "tautulli.TerminateSession": "Mettre fin Ă la session",
+ "tautulli.TerminatedSession": "Session terminée",
+ "tautulli.TerminationMessage": "Message de fin de session",
+ "tautulli.TerminationConfirmMessage": "Voulez-vous mettre fin Ă cette session ?",
+ "tautulli.TerminationAttachMessage": "Vous pouvez éventuellement joindre un message ci-dessous.",
+ "tautulli.Terminate": "Terminer",
+ "tautulli.Subtitle": "Sous-titre",
+ "tautulli.Stream": "Stream",
+ "tautulli.SessionsMany": "{} Sessions",
+ "tautulli.SessionsOne": "1 Session",
+ "tautulli.Sessions": "Sessions",
+ "tautulli.SessionEnded": "Session terminée",
+ "tautulli.Season": "Saison {}",
+ "tautulli.Quality": "Qualité",
+ "tautulli.Product": "Produit",
+ "tautulli.Player": "Player",
+ "tautulli.Platform": "Platforme",
+ "tautulli.NoActiveStreams": "Aucun stream en cours",
+ "tautulli.None": "Aucun",
+ "tautulli.More": "Plus",
+ "tautulli.Metadata": "Métadonnées",
+ "tautulli.Location": "Localisation",
+ "tautulli.Library": "Bibliothèque",
+ "tautulli.History": "Historique",
+ "tautulli.ETA": "Temps restant",
+ "tautulli.Episode": "Épisode {}",
+ "tautulli.Duration": "Durée",
+ "tautulli.DirectStreams": "Direct Streams",
+ "tautulli.DirectStream": "Direct Stream",
+ "tautulli.DirectPlays": "Lectures directe",
+ "tautulli.DirectPlay": "Lecture directe",
+ "tautulli.DeletingTemporarySessionsFailed": "Échec de la suppression des sessions temporaires",
+ "tautulli.DeletingTemporarySessionsDescription": "Les sessions temporaires ont été supprimés",
+ "tautulli.DeletingImageCacheDescription": "Le cache des images de Tautulli a été supprimé",
+ "tautulli.DeletingTemporarySessions": "Suppression des sessions temporaires…",
+ "tautulli.DeletingImageCacheFailed": "Échec de la suppression du cache d'image",
+ "tautulli.DeletingImageCache": "Suppression du cache des images…",
+ "tautulli.DeletingCacheFailed": "Échec de la suppression du cache",
+ "tautulli.DeletingCacheDescription": "Le cache de Tautulli est en cours de suppression",
+ "tautulli.DeletingCache": "Suppression du cache en cours…",
+ "tautulli.DeleteTemporarySessions": "Supprimer les sessions temporaires",
+ "tautulli.DeleteImageCache": "Supprimer le cache des images",
+ "tautulli.DeleteCache": "Supprimer le cache",
+ "tautulli.Copy": "Copier",
+ "tautulli.Container": "Container",
+ "tautulli.Bandwidth": "Bande passante",
+ "tautulli.BackupDatabase": "Sauvegarder la base de donnée",
+ "tautulli.BackupConfiguration": "Sauvegarder la configuration",
+ "tautulli.BackingUpDatabaseFailed": "Échec de la sauvegarde de la base de données",
+ "tautulli.BackingUpDatabaseDescription": "Sauvegarde de votre base de données en arrière-plan",
+ "tautulli.BackingUpDatabase": "Sauvegarde de la base de données…",
+ "tautulli.BackingUpConfigurationFailed": "Échec de la sauvegarde de la configuration",
+ "tautulli.BackingUpConfigurationDescription": "Sauvegarde de votre configuration en arrière-plan",
+ "tautulli.BackingUpConfiguration": "Sauvegarde de la configuration…",
+ "tautulli.Audio": "Audio",
+ "tautulli.ActivityDetails": "Détails de l'activité",
+ "tautulli.Activity": "Activité"
}
\ No newline at end of file
diff --git a/assets/localization/it.json b/assets/localization/it.json
new file mode 100644
index 00000000..32d0f937
--- /dev/null
+++ b/assets/localization/it.json
@@ -0,0 +1,107 @@
+{
+ "lunasea.AnErrorHasOccurred": "C'è stato un errore",
+ "lunasea.ChangeProfiles": "Cambia profili",
+ "lunasea.CheckLogsMessage": "Controlla i registri per maggiori dettagli",
+ "lunasea.ComingSoon": "Prossimamente",
+ "lunasea.Dashboard": "Pannello di controllo",
+ "lunasea.GoBack": "Indietro",
+ "lunasea.ModuleIsNotEnabled": "{} non è abilitato",
+ "lunasea.Refresh": "Ricarica",
+ "lunasea.ReturnToDashboard": "Torna al pannello di controllo",
+ "lunasea.SearchTextBar": "Ricerca…",
+ "lunasea.Settings": "Impostazioni",
+ "lunasea.TryAgain": "Riprova",
+ "lunasea.Unknown": "Sconosciuto",
+ "radarr.AddMovie": "Aggiungi Film",
+ "radarr.AddMovieAndSearch": "Aggiungi + Cerca",
+ "radarr.Age": "EtĂ ",
+ "radarr.All": "Tutti",
+ "radarr.Alphabetical": "Alfabetico",
+ "radarr.Approved": "Approvato",
+ "radarr.AvailabilityUnknown": "DisponibilitĂ Sconosciuta",
+ "radarr.AvailableIn": "Disponibile in {}",
+ "radarr.AvailableToday": "Disponibile Oggi",
+ "radarr.BackupDatabase": "Fai un backup della banca dati",
+ "radarr.CinemaDateUnknown": "Data Cinema Sconosciuta",
+ "radarr.CutoffUnmet": "Soglia Non Raggiunta",
+ "radarr.DateAdded": "Data Aggiunta",
+ "radarr.Discover": "Scopri",
+ "radarr.DownloadFailed": "Scaricamento fallito",
+ "radarr.DownloadIgnored": "Scaricamento ignorato",
+ "radarr.EditMovie": "Modifica il film",
+ "radarr.FilterCatalogue": "Filtra catalogo",
+ "radarr.GrabbedFrom": "Preso da {}",
+ "radarr.History": "Cronologia",
+ "radarr.HistoryDescription": "Visualizza l'attivitĂ recente",
+ "radarr.InCinemasIn": "Nei cinema in {}",
+ "radarr.InCinemasToday": "Nei cinema oggi",
+ "radarr.MinimumAvailability": "DisponibilitĂ minima",
+ "radarr.Missing": "Mancante",
+ "radarr.MonitorMovie": "Monitora il film",
+ "radarr.Monitored": "Monitorato",
+ "radarr.MonitoredDescription": "Monitora per nuove versioni",
+ "radarr.More": "Di piĂą",
+ "radarr.MovieFileDeleted": "File film eliminato",
+ "radarr.MovieFileRenamed": "File film rinominato",
+ "radarr.MovieImported": "Film importato ({})",
+ "radarr.MovieNotFound": "Film non trovato",
+ "radarr.MoviePath": "Percorso film",
+ "radarr.Movies": "Film",
+ "radarr.NoHistoryFound": "Nessuna cronologia trovata",
+ "radarr.NoMoviesFound": "Nessun film trovato",
+ "radarr.NoResultsFound": "Nessun risultato trovato",
+ "radarr.NoSummaryIsAvailable": "Nessun riepilogo disponibile.",
+ "radarr.QualityProfile": "Profilo qualitĂ ",
+ "radarr.Queue": "Coda",
+ "radarr.QueueDescription": "Visualizza il contenuto attivo e in coda",
+ "radarr.RefreshMovie": "Ricarica il film",
+ "radarr.Rejected": "Rifiutato",
+ "radarr.Released": "Rilasciato",
+ "radarr.ReleasedToday": "Rilasciato oggi",
+ "radarr.RemoveMovie": "Rimuovi il film",
+ "radarr.RootFolder": "Cartella root",
+ "radarr.RunRSSSync": "Esegui sincronizzazione RSS",
+ "radarr.Runtime": "Durata",
+ "radarr.Search": "Cerca",
+ "radarr.SearchAllMissing": "Cerca tutti mancanti",
+ "radarr.SearchFor": "Cerco {}",
+ "radarr.Seeders": "Seeder",
+ "radarr.Size": "Dimensioni",
+ "radarr.SortCatalogue": "Ordina catalogo",
+ "radarr.Studio": "Studio",
+ "radarr.SystemStatus": "Stato sistema",
+ "radarr.SystemStatusDescription": "Stato sistema e spazio su disco",
+ "radarr.Tags": "Etichette",
+ "radarr.TagsDescription": "Gestisci le tue etichette",
+ "radarr.Type": "Tipo",
+ "radarr.UnmonitorMovie": "Non monitorare il film",
+ "radarr.Unmonitored": "Non monitorato",
+ "radarr.Upcoming": "Imminente",
+ "radarr.UpdateLibrary": "Aggiorna la libreria",
+ "radarr.UpdateMovie": "Aggiorna il film",
+ "radarr.ViewWebGUI": "Visualizza Web GUI",
+ "radarr.Wanted": "Ricercato",
+ "radarr.Weight": "Peso",
+ "radarr.Year": "Anno",
+ "search.Subcategories": "Sottocategorie",
+ "search.Size": "Dimensioni",
+ "search.SentTo": "Invia a {}",
+ "search.SentNZBData": "Dati NZB inviati",
+ "search.Search": "Cerca",
+ "search.Results": "Risultati",
+ "search.NoSubcategoriesFound": "Nessuna sottocategoria trovata",
+ "search.NoResultsFound": "Nessun risultato trovato",
+ "search.NoCategoriesFound": "Nessuna categoria trovata",
+ "search.FailedToSend": "Invio non riuscito",
+ "search.FailedToDownloadNZB": "Impossibile scaricare NZB",
+ "search.DownloadToDevice": "Scarica sul dispositivo",
+ "search.DownloadingNZBToDevice": "Scaricamento di NZB sul tuo dispositivo",
+ "search.Downloading": "Scaricamento in corso…",
+ "search.Download": "Scarica",
+ "search.Comments": "Commenti",
+ "search.Categories": "Categorie",
+ "search.Category": "Categoria",
+ "search.Alphabetical": "Alfabetico",
+ "search.AllSubcategories": "Tutte Le Sottocategorie",
+ "search.Age": "EtĂ "
+}
\ No newline at end of file
diff --git a/assets/localization/sv.json b/assets/localization/sv.json
index df021d78..79e95e15 100644
--- a/assets/localization/sv.json
+++ b/assets/localization/sv.json
@@ -1,37 +1,107 @@
{
"lunasea.AnErrorHasOccurred": "Ett fel har inträffat",
+ "lunasea.ChangeProfiles": "Byta Profil",
+ "lunasea.CheckLogsMessage": "Kolla loggen för mera detaljer",
+ "lunasea.ComingSoon": "Kommer Snart",
+ "lunasea.Dashboard": "Instrumentbräda",
+ "lunasea.GoBack": "Tillbaka",
+ "lunasea.ModuleIsNotEnabled": "{} Är Inte Aktiverad",
"lunasea.Refresh": "Uppdatera",
+ "lunasea.ReturnToDashboard": "Gå Tillbaka till Instrumentbrädan",
"lunasea.SearchTextBar": "Sök…",
"lunasea.Settings": "Inställningar",
+ "lunasea.TryAgain": "Försök Igen",
+ "lunasea.Unknown": "Okänd",
"radarr.AddMovie": "Lägg till Film",
"radarr.AddMovieAndSearch": "Lägg till + Sök",
+ "radarr.Age": "Ă…lder",
+ "radarr.All": "Alla",
+ "radarr.Alphabetical": "Alfabetiskt",
+ "radarr.Approved": "Godkänd",
+ "radarr.AvailabilityUnknown": "Tillgänglighet Okänd",
+ "radarr.AvailableIn": "Tillgänglig i {}",
+ "radarr.AvailableToday": "Tillgänglig Idag",
+ "radarr.BackupDatabase": "Säkerhetskopiera Databasen",
+ "radarr.CinemaDateUnknown": "Okänt Biograf Datum",
+ "radarr.CutoffUnmet": "Avgränsande ej tillgodosedd",
+ "radarr.DateAdded": "Datum tillagt",
"radarr.Discover": "Utforska",
+ "radarr.DownloadFailed": "Nedladdning Misslyckades",
+ "radarr.DownloadIgnored": "Ignorerad Nedladdning",
"radarr.EditMovie": "Redigera Film",
"radarr.FilterCatalogue": "Filterkatalog",
+ "radarr.GrabbedFrom": "Hämtad från {}",
"radarr.History": "Historik",
"radarr.HistoryDescription": "Visa Senaste Aktivitet",
+ "radarr.InCinemasIn": "Visas pĂĄ Biograf {}",
+ "radarr.InCinemasToday": "PĂĄ Biograf Idag",
"radarr.MinimumAvailability": "Minsta Tillgänglighet",
"radarr.Missing": "Saknas",
+ "radarr.MonitorMovie": "Bevaka Film",
"radarr.Monitored": "Bevakad",
"radarr.MonitoredDescription": "Bevaka för nya utgåvor",
"radarr.More": "Mer",
+ "radarr.MovieFileDeleted": "Filmfil Raderad",
+ "radarr.MovieFileRenamed": "Filmfil Omdöpt",
+ "radarr.MovieImported": "Film Importerad ({})",
"radarr.MovieNotFound": "Film Hittades Inte",
"radarr.MoviePath": "Film Sökväg",
"radarr.Movies": "Filmer",
+ "radarr.NoHistoryFound": "Ingen Historik Hittad",
"radarr.NoMoviesFound": "Inga Filmer Funna",
"radarr.NoResultsFound": "Inga Resultat Funna",
"radarr.NoSummaryIsAvailable": "Ingen sammanfattning finns tillgänglig.",
"radarr.QualityProfile": "Kvalitetsprofil",
"radarr.Queue": "Kö",
"radarr.QueueDescription": "Visa Aktivt & Köat Innehåll",
+ "radarr.RefreshMovie": "Uppdatera Film",
+ "radarr.Rejected": "Avvisad",
+ "radarr.Released": "Släppt",
+ "radarr.ReleasedToday": "Släpptes Idag",
+ "radarr.RemoveMovie": "Ta Bort Film",
"radarr.RootFolder": "Rotmapp",
+ "radarr.RunRSSSync": "Kör RSS Synkronisering",
+ "radarr.Runtime": "Speltid",
"radarr.Search": "Sök",
+ "radarr.SearchAllMissing": "Sök Alla Saknade",
"radarr.SearchFor": "Sök efter {}",
+ "radarr.Seeders": "Seedare",
+ "radarr.Size": "Storlek",
"radarr.SortCatalogue": "Sortera Katalog",
+ "radarr.Studio": "Studio",
"radarr.SystemStatus": "Systemstatus",
"radarr.SystemStatusDescription": "Systemstatus & Disk Utrymme",
"radarr.Tags": "Taggar",
"radarr.TagsDescription": "Hantera Dina Taggar",
+ "radarr.Type": "Typ",
+ "radarr.UnmonitorMovie": "Obevaka Film",
+ "radarr.Unmonitored": "Obevakad",
"radarr.Upcoming": "Kommande",
- "radarr.UpdateMovie": "Uppdatera Film"
+ "radarr.UpdateLibrary": "Uppdatera Bibliotek",
+ "radarr.UpdateMovie": "Uppdatera Film",
+ "radarr.ViewWebGUI": "Visa Webb GUI",
+ "radarr.Wanted": "Ă–nskad",
+ "radarr.Weight": "Vikt",
+ "radarr.Year": "Ă…r",
+ "search.Size": "Storlek",
+ "search.Search": "Sök",
+ "search.NoResultsFound": "Inga Resultat Funna",
+ "search.Alphabetical": "Alfabetiskt",
+ "search.Age": "Ă…lder",
+ "search.Subcategories": "Underkategorier",
+ "search.SentTo": "Skickat till {}",
+ "search.SentNZBData": "NZB Data Skickat",
+ "search.Results": "Resultat",
+ "search.NoSubcategoriesFound": "Inga Underkategorier Funna",
+ "search.NoCategoriesFound": "Inga Kategorier Funna",
+ "search.FailedToSend": "Misslyckades att Skicka",
+ "search.FailedToDownloadNZB": "Misslyckades med att Ladda ned NZB",
+ "search.DownloadToDevice": "Ladda ned till Enhet",
+ "search.DownloadingNZBToDevice": "Laddar ned NZB till din enhet",
+ "search.Downloading": "Laddar ned…",
+ "search.Download": "Ladda ned",
+ "search.Comments": "Kommentarer",
+ "search.Categories": "Kategorier",
+ "search.Category": "Kategori",
+ "search.AllSubcategories": "Alla Underkategorier"
}
\ No newline at end of file
diff --git a/derry.yaml b/derry.yaml
new file mode 100644
index 00000000..a19ad78a
--- /dev/null
+++ b/derry.yaml
@@ -0,0 +1,12 @@
+generate:
+ runner: flutter packages pub run build_runner build --delete-conflicting-outputs
+ localization: dart ./scripts/generate/localization.dart
+build:
+ android:
+ apk: ./scripts/build/apk.sh
+ aab: ./scripts/build/aab.sh
+ ios:
+ ipa: ./scripts/build/ipa.sh
+ archive:
+ - flutter clean
+ - flutter build ios
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index abf5b83a..f86ca006 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -1,5 +1,5 @@
PODS:
- - cloud_firestore (1.0.0):
+ - cloud_firestore (1.0.1):
- Firebase/Firestore (= 7.3.0)
- firebase_core
- Flutter
@@ -40,9 +40,14 @@ PODS:
- file_picker (0.0.1):
- DKImagePickerController/PhotoGallery
- Flutter
+ - Firebase/Analytics (7.3.0):
+ - Firebase/Core
- Firebase/Auth (7.3.0):
- Firebase/CoreOnly
- FirebaseAuth (~> 7.3.0)
+ - Firebase/Core (7.3.0):
+ - Firebase/CoreOnly
+ - FirebaseAnalytics (= 7.3.0)
- Firebase/CoreOnly (7.3.0):
- FirebaseCore (= 7.3.0)
- Firebase/Crashlytics (7.3.0):
@@ -57,11 +62,15 @@ PODS:
- Firebase/Storage (7.3.0):
- Firebase/CoreOnly
- FirebaseStorage (~> 7.3.0)
- - firebase_auth (1.0.0):
+ - firebase_analytics (7.1.1):
+ - Firebase/Analytics (= 7.3.0)
+ - firebase_core
+ - Flutter
+ - firebase_auth (1.0.1):
- Firebase/Auth (= 7.3.0)
- firebase_core
- Flutter
- - firebase_core (1.0.0):
+ - firebase_core (1.0.1):
- Firebase/CoreOnly (= 7.3.0)
- Flutter
- firebase_crashlytics (1.0.0):
@@ -75,6 +84,15 @@ PODS:
- Firebase/Storage (= 7.3.0)
- firebase_core
- Flutter
+ - FirebaseAnalytics (7.3.0):
+ - FirebaseCore (~> 7.0)
+ - FirebaseInstallations (~> 7.0)
+ - GoogleAppMeasurement (= 7.3.0)
+ - GoogleUtilities/AppDelegateSwizzler (~> 7.0)
+ - GoogleUtilities/MethodSwizzler (~> 7.0)
+ - GoogleUtilities/Network (~> 7.0)
+ - "GoogleUtilities/NSData+zlib (~> 7.0)"
+ - nanopb (~> 2.30906.0)
- FirebaseAuth (7.3.0):
- FirebaseCore (~> 7.0)
- GoogleUtilities/AppDelegateSwizzler (~> 7.0)
@@ -117,6 +135,12 @@ PODS:
- FirebaseCore (~> 7.0)
- GTMSessionFetcher/Core (~> 1.4)
- Flutter (1.0.0)
+ - GoogleAppMeasurement (7.3.0):
+ - GoogleUtilities/AppDelegateSwizzler (~> 7.0)
+ - GoogleUtilities/MethodSwizzler (~> 7.0)
+ - GoogleUtilities/Network (~> 7.0)
+ - "GoogleUtilities/NSData+zlib (~> 7.0)"
+ - nanopb (~> 2.30906.0)
- GoogleDataTransport (8.1.0):
- nanopb (~> 2.30906.0)
- GoogleUtilities/AppDelegateSwizzler (7.2.2):
@@ -127,6 +151,8 @@ PODS:
- PromisesObjC (~> 1.2)
- GoogleUtilities/Logger (7.2.2):
- GoogleUtilities/Environment
+ - GoogleUtilities/MethodSwizzler (7.2.2):
+ - GoogleUtilities/Logger
- GoogleUtilities/Network (7.2.2):
- GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib"
@@ -157,7 +183,7 @@ PODS:
- SDWebImage (5.10.4):
- SDWebImage/Core (= 5.10.4)
- SDWebImage/Core (5.10.4)
- - share_plus (0.0.1):
+ - share (0.0.1):
- Flutter
- shared_preferences (0.0.1):
- Flutter
@@ -170,6 +196,7 @@ DEPENDENCIES:
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- Firebase/Messaging
+ - firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`)
- firebase_auth (from `.symlinks/plugins/firebase_auth/ios`)
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
- firebase_crashlytics (from `.symlinks/plugins/firebase_crashlytics/ios`)
@@ -182,7 +209,7 @@ DEPENDENCIES:
- package_info (from `.symlinks/plugins/package_info/ios`)
- path_provider (from `.symlinks/plugins/path_provider/ios`)
- quick_actions (from `.symlinks/plugins/quick_actions/ios`)
- - share_plus (from `.symlinks/plugins/share_plus/ios`)
+ - share (from `.symlinks/plugins/share/ios`)
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
- url_launcher (from `.symlinks/plugins/url_launcher/ios`)
@@ -191,6 +218,7 @@ SPEC REPOS:
- DKImagePickerController
- DKPhotoGallery
- Firebase
+ - FirebaseAnalytics
- FirebaseAuth
- FirebaseCore
- FirebaseCoreDiagnostics
@@ -199,6 +227,7 @@ SPEC REPOS:
- FirebaseInstanceID
- FirebaseMessaging
- FirebaseStorage
+ - GoogleAppMeasurement
- GoogleDataTransport
- GoogleUtilities
- GTMSessionFetcher
@@ -215,6 +244,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/connectivity_plus/ios"
file_picker:
:path: ".symlinks/plugins/file_picker/ios"
+ firebase_analytics:
+ :path: ".symlinks/plugins/firebase_analytics/ios"
firebase_auth:
:path: ".symlinks/plugins/firebase_auth/ios"
firebase_core:
@@ -240,8 +271,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/path_provider/ios"
quick_actions:
:path: ".symlinks/plugins/quick_actions/ios"
- share_plus:
- :path: ".symlinks/plugins/share_plus/ios"
+ share:
+ :path: ".symlinks/plugins/share/ios"
shared_preferences:
:path: ".symlinks/plugins/shared_preferences/ios"
url_launcher:
@@ -253,17 +284,19 @@ CHECKOUT OPTIONS:
:tag: 7.3.0
SPEC CHECKSUMS:
- cloud_firestore: efd6d2bafe5147690cff2ccfbde13752a56fa281
+ cloud_firestore: 69d71054fcab9a1c0d4779362f85b94aec7ff7d7
connectivity_plus: f5e5e0b894ec1187bfab9816d6c2f034da8d46da
DKImagePickerController: b5eb7f7a388e4643264105d648d01f727110fc3d
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1
Firebase: 26223c695fe322633274198cb19dca8cb7e54416
- firebase_auth: f07f3ee03813d95157ee08ce763c649c656174b5
- firebase_core: f47224dd6a9b928b4cf0128b0d004ec9e47d4bf2
+ firebase_analytics: ee4abb07bfa857e7c95d97144efe9eb68be414cd
+ firebase_auth: 9f6491ea8e44570323361ae713a2ae3175b3f21a
+ firebase_core: d2e03528e2a600891f6f460b5e92932624480d1d
firebase_crashlytics: a62319db93baf2bfeca8a2384af3f29e3d62b09c
firebase_messaging: fc1811236795c2313b8339c35d31295b1cd8486f
firebase_storage: a4b292db6551a64c3697c7e67cb1df524b494bb1
+ FirebaseAnalytics: 2580c2d62535ae7b644143d48941fcc239ea897a
FirebaseAuth: c224a0cf1afa0949bd5c7bfcf154b4f5ce8ddef2
FirebaseCore: 4d3c72622ce0e2106aaa07bb4b2935ba2c370972
FirebaseCoreDiagnostics: d50e11039e5984d92c8a512be2395f13df747350
@@ -274,6 +307,7 @@ SPEC CHECKSUMS:
FirebaseMessaging: 68d1bcb14880189558a8ae57167abe0b7e417232
FirebaseStorage: 5002b1895bfe74a5ce92ad54f966e6162d0da2e5
Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c
+ GoogleAppMeasurement: 8d3c0aeede16ab7764144b5a4ca8e1d4323841b7
GoogleDataTransport: 116c84c4bdeb76be2a7a46de51244368f9794eab
GoogleUtilities: 31c5b01f978a70c6cff2afc6272b3f1921614b43
GTMSessionFetcher: b3503b20a988c4e20cc189aa798fd18220133f52
@@ -286,7 +320,7 @@ SPEC CHECKSUMS:
quick_actions: 6cb2390c4dab0e737c94573c27e18d9666710720
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
SDWebImage: c666b97e1fa9c64b4909816a903322018f0a9c84
- share_plus: 2e6276f752c279a84cd68a9fc27dfbbe2a74b072
+ share: 0b2c3e82132f5888bccca3351c504d0003b3b410
shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d
SwiftyGif: 5d4af95df24caf1c570dbbcb32a3b8a0763bc6d7
url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index f825c68d..23694519 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -442,7 +442,7 @@
"${BUILT_PRODUCTS_DIR}/FirebaseStorage/FirebaseStorage.framework",
"${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework",
"${BUILT_PRODUCTS_DIR}/GoogleDataTransport/GoogleDataTransport.framework",
- "${BUILT_PRODUCTS_DIR}/GoogleUtilities/GoogleUtilities.framework",
+ "${BUILT_PRODUCTS_DIR}/GoogleUtilities-00567490/GoogleUtilities.framework",
"${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework",
"${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework",
"${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework",
@@ -455,7 +455,7 @@
"${BUILT_PRODUCTS_DIR}/package_info/package_info.framework",
"${BUILT_PRODUCTS_DIR}/path_provider/path_provider.framework",
"${BUILT_PRODUCTS_DIR}/quick_actions/quick_actions.framework",
- "${BUILT_PRODUCTS_DIR}/share_plus/share_plus.framework",
+ "${BUILT_PRODUCTS_DIR}/share/share.framework",
"${BUILT_PRODUCTS_DIR}/shared_preferences/shared_preferences.framework",
"${BUILT_PRODUCTS_DIR}/url_launcher/url_launcher.framework",
);
@@ -486,7 +486,7 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/quick_actions.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/share_plus.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/share.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher.framework",
);
diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index a28140cf..fb2dffc4 100644
--- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -27,8 +27,6 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
-
-
-
-
+
+
-
-
_openBoxes() async {
- await Hive.openBox('lunasea');
await Hive.openBox('alerts');
await Hive.openBox('indexers');
+ await Hive.openBox('logs');
+ await Hive.openBox('lunasea');
await Hive.openBox('profiles');
}
/// Set the default state for all hive boxes.
- void setDefaults({ bool clearAlerts = false }) {
+ void setDefaults({ bool clearEverything = false }) {
//Clear all the boxes
- clearAllBoxes(clearAlerts: clearAlerts);
+ clearAllBoxes(clearEverything: clearEverything);
//Set default profile & enabled profile
profilesBox.put('default', ProfileHiveObject.empty());
lunaSeaBox.put(LunaDatabaseValue.ENABLED_PROFILE.key, 'default');
}
//Get boxes
- static Box get lunaSeaBox => Hive.box('lunasea');
static Box get alertsBox => Hive.box('alerts');
- static Box get profilesBox => Hive.box('profiles');
- static Box get indexersBox => Hive.box('indexers');
+ static Box get indexersBox => Hive.box('indexers');
+ static Box get logsBox => Hive.box('logs');
+ static Box get lunaSeaBox => Hive.box('lunasea');
+ static Box get profilesBox => Hive.box('profiles');
//Clear boxes
+ void clearAlertsBox() => alertsBox.deleteAll(alertsBox.keys);
+ void clearIndexersBox() => indexersBox.deleteAll(indexersBox.keys);
+ void clearLogsBox() => logsBox.deleteAll(logsBox.keys);
void clearLunaSeaBox() => lunaSeaBox.deleteAll(lunaSeaBox.keys);
void clearProfilesBox() => profilesBox.deleteAll(profilesBox.keys);
- void clearIndexersBox() => indexersBox.deleteAll(indexersBox.keys);
- void clearAlertsBox() => alertsBox.deleteAll(alertsBox.keys);
- void clearAllBoxes({ bool clearAlerts = false }) {
+
+ void clearAllBoxes({ bool clearEverything = false }) {
clearLunaSeaBox();
clearProfilesBox();
clearIndexersBox();
- if(clearAlerts) clearAlertsBox();
+ if(clearEverything) {
+ clearAlertsBox();
+ clearLogsBox();
+ }
}
//Profile values
diff --git a/lib/core/database/luna_database.dart b/lib/core/database/luna_database.dart
index a5048080..e79adc2f 100644
--- a/lib/core/database/luna_database.dart
+++ b/lib/core/database/luna_database.dart
@@ -4,6 +4,10 @@ import 'package:lunasea/core.dart';
class LunaDatabase extends LunaModuleDatabase {
@override
void registerAdapters() {
+ Hive.registerAdapter(IndexerHiveObjectAdapter());
+ Hive.registerAdapter(ProfileHiveObjectAdapter());
+ Hive.registerAdapter(LunaLogHiveObjectAdapter());
+ Hive.registerAdapter(LunaLogTypeAdapter());
Hive.registerAdapter(LunaBrowserAdapter());
}
@@ -16,7 +20,8 @@ class LunaDatabase extends LunaModuleDatabase {
case LunaDatabaseValue.SELECTED_BROWSER: data[value.key] = (LunaDatabaseValue.SELECTED_BROWSER.data as LunaBrowser).key; break;
// Primitive values
case LunaDatabaseValue.ENABLED_PROFILE:
- case LunaDatabaseValue.ENABLED_SENTRY:
+ case LunaDatabaseValue.ENABLE_FIREBASE_ANALYTICS:
+ case LunaDatabaseValue.ENABLE_FIREBASE_CRASHLYTICS:
case LunaDatabaseValue.THEME_AMOLED:
case LunaDatabaseValue.THEME_AMOLED_BORDER:
case LunaDatabaseValue.THEME_IMAGE_BACKGROUND_OPACITY:
@@ -43,7 +48,8 @@ class LunaDatabase extends LunaModuleDatabase {
case LunaDatabaseValue.SELECTED_BROWSER: value.put(LunaBrowser.APPLE_SAFARI.fromKey(config[key])); break;
// Primitive values
case LunaDatabaseValue.ENABLED_PROFILE:
- case LunaDatabaseValue.ENABLED_SENTRY:
+ case LunaDatabaseValue.ENABLE_FIREBASE_ANALYTICS:
+ case LunaDatabaseValue.ENABLE_FIREBASE_CRASHLYTICS:
case LunaDatabaseValue.THEME_AMOLED:
case LunaDatabaseValue.THEME_AMOLED_BORDER:
case LunaDatabaseValue.THEME_IMAGE_BACKGROUND_OPACITY:
@@ -63,7 +69,8 @@ class LunaDatabase extends LunaModuleDatabase {
LunaDatabaseValue valueFromKey(String key) {
switch(key) {
case 'profile': return LunaDatabaseValue.ENABLED_PROFILE;
- case 'LUNASEA_ENABLED_SENTRY': return LunaDatabaseValue.ENABLED_SENTRY;
+ case 'LUNASEA_ENABLE_FIREBASE_ANALYTICS': return LunaDatabaseValue.ENABLE_FIREBASE_ANALYTICS;
+ case 'LUNASEA_ENABLE_FIREBASE_CRASHLYTICS': return LunaDatabaseValue.ENABLE_FIREBASE_CRASHLYTICS;
case 'LUNASEA_THEME_AMOLED': return LunaDatabaseValue.THEME_AMOLED;
case 'LUNASEA_THEME_AMOLED_BORDER': return LunaDatabaseValue.THEME_AMOLED_BORDER;
case 'LUNASEA_THEME_IMAGE_BACKGROUND_OPACITY': return LunaDatabaseValue.THEME_IMAGE_BACKGROUND_OPACITY;
@@ -84,7 +91,8 @@ class LunaDatabase extends LunaModuleDatabase {
enum LunaDatabaseValue {
ENABLED_PROFILE,
- ENABLED_SENTRY,
+ ENABLE_FIREBASE_ANALYTICS,
+ ENABLE_FIREBASE_CRASHLYTICS,
THEME_AMOLED,
THEME_AMOLED_BORDER,
THEME_IMAGE_BACKGROUND_OPACITY,
@@ -104,7 +112,8 @@ extension LunaDatabaseValueExtension on LunaDatabaseValue {
String get key {
switch(this) {
case LunaDatabaseValue.ENABLED_PROFILE: return 'profile';
- case LunaDatabaseValue.ENABLED_SENTRY: return 'LUNASEA_ENABLED_SENTRY';
+ case LunaDatabaseValue.ENABLE_FIREBASE_ANALYTICS: return 'LUNASEA_ENABLE_FIREBASE_ANALYTICS';
+ case LunaDatabaseValue.ENABLE_FIREBASE_CRASHLYTICS: return 'LUNASEA_ENABLE_FIREBASE_CRASHLYTICS';
case LunaDatabaseValue.THEME_AMOLED: return 'LUNASEA_THEME_AMOLED';
case LunaDatabaseValue.THEME_AMOLED_BORDER: return 'LUNASEA_THEME_AMOLED_BORDER';
case LunaDatabaseValue.THEME_IMAGE_BACKGROUND_OPACITY: return 'LUNASEA_THEME_IMAGE_BACKGROUND_OPACITY';
@@ -130,7 +139,8 @@ extension LunaDatabaseValueExtension on LunaDatabaseValue {
case LunaDatabaseValue.THEME_AMOLED: return box.get(this.key, defaultValue: false);
case LunaDatabaseValue.THEME_AMOLED_BORDER: return box.get(this.key, defaultValue: false);
case LunaDatabaseValue.THEME_IMAGE_BACKGROUND_OPACITY: return box.get(this.key, defaultValue: 10);
- case LunaDatabaseValue.ENABLED_SENTRY: return box.get(this.key, defaultValue: true);
+ case LunaDatabaseValue.ENABLE_FIREBASE_ANALYTICS: return box.get(this.key, defaultValue: true);
+ case LunaDatabaseValue.ENABLE_FIREBASE_CRASHLYTICS: return box.get(this.key, defaultValue: true);
case LunaDatabaseValue.QUICK_ACTIONS_LIDARR: return box.get(this.key, defaultValue: false);
case LunaDatabaseValue.QUICK_ACTIONS_RADARR: return box.get(this.key, defaultValue: false);
case LunaDatabaseValue.QUICK_ACTIONS_SONARR: return box.get(this.key, defaultValue: false);
@@ -148,7 +158,8 @@ extension LunaDatabaseValueExtension on LunaDatabaseValue {
final box = Database.lunaSeaBox;
switch(this) {
case LunaDatabaseValue.ENABLED_PROFILE: if(value.runtimeType == String) box.put(this.key, value); return;
- case LunaDatabaseValue.ENABLED_SENTRY: if(value.runtimeType == bool) box.put(this.key, value); return;
+ case LunaDatabaseValue.ENABLE_FIREBASE_ANALYTICS: if(value.runtimeType == bool) box.put(this.key, value); return;
+ case LunaDatabaseValue.ENABLE_FIREBASE_CRASHLYTICS: if(value.runtimeType == bool) box.put(this.key, value); return;
case LunaDatabaseValue.THEME_AMOLED: if(value.runtimeType == bool) box.put(this.key, value); return;
case LunaDatabaseValue.THEME_AMOLED_BORDER: if(value.runtimeType == bool) box.put(this.key, value); return;
case LunaDatabaseValue.THEME_IMAGE_BACKGROUND_OPACITY: if(value.runtimeType == int) box.put(this.key, value); return;
diff --git a/lib/core/extensions.dart b/lib/core/extensions.dart
index 25a5d62b..0b9f5ea8 100644
--- a/lib/core/extensions.dart
+++ b/lib/core/extensions.dart
@@ -1,7 +1,6 @@
export 'extensions/datetime.dart';
export 'extensions/double.dart';
export 'extensions/duration.dart';
-export 'extensions/flog_log_level.dart';
export 'extensions/iap_product_details.dart';
export 'extensions/int.dart';
export 'extensions/navigator_state.dart';
diff --git a/lib/core/extensions/flog_log_level.dart b/lib/core/extensions/flog_log_level.dart
deleted file mode 100644
index d03868e7..00000000
--- a/lib/core/extensions/flog_log_level.dart
+++ /dev/null
@@ -1,18 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:lunasea/core.dart';
-import 'package:f_logs/f_logs.dart';
-
-extension FLogLogLevelExtension on LogLevel {
- /// Return a readable string version of the log level.
- String get lunaName => this.toString().substring(9);
-
- /// Return a [Color] corresponding to the log level.
- Color get lunaColour {
- switch(this) {
- case LogLevel.WARNING: return LunaColours.orange;
- case LogLevel.ERROR: return LunaColours.red;
- case LogLevel.FATAL: return LunaColours.accent;
- default: return LunaColours.blueGrey;
- }
- }
-}
\ No newline at end of file
diff --git a/lib/core/extensions/page_controller.dart b/lib/core/extensions/page_controller.dart
index 60b3e10d..e649b1f7 100644
--- a/lib/core/extensions/page_controller.dart
+++ b/lib/core/extensions/page_controller.dart
@@ -10,4 +10,9 @@ extension PageControllerExtension on PageController {
curve: Curves.decelerate,
);
}
+
+ /// Jump a [PageView] to the given index.
+ Future lunaJumpToPage(int index) async {
+ if(this.hasClients) this.jumpToPage(index);
+ }
}
diff --git a/lib/core/extensions/string.dart b/lib/core/extensions/string.dart
index 8fd9cb1f..892f41f5 100644
--- a/lib/core/extensions/string.dart
+++ b/lib/core/extensions/string.dart
@@ -8,7 +8,11 @@ extension StringExtension on String {
String lunaCapitalizeFirstLetters({ Pattern pattern = ' ' }) {
List split = this.split(pattern);
for(var i=0; i {
host: fields[1] as String,
apiKey: fields[2] as String,
headers: (fields[3] as Map)?.cast(),
+ icon: fields[4] as LunaIndexerIcon,
);
}
@override
void write(BinaryWriter writer, IndexerHiveObject obj) {
writer
- ..writeByte(4)
+ ..writeByte(5)
..writeByte(0)
..write(obj.displayName)
..writeByte(1)
@@ -35,7 +36,9 @@ class IndexerHiveObjectAdapter extends TypeAdapter {
..writeByte(2)
..write(obj.apiKey)
..writeByte(3)
- ..write(obj.headers);
+ ..write(obj.headers)
+ ..writeByte(4)
+ ..write(obj.icon);
}
@override
diff --git a/lib/core/models/hive_objects/profile.dart b/lib/core/models/configuration/profile.dart
similarity index 100%
rename from lib/core/models/hive_objects/profile.dart
rename to lib/core/models/configuration/profile.dart
diff --git a/lib/core/models/hive_objects/profile.g.dart b/lib/core/models/configuration/profile.g.dart
similarity index 100%
rename from lib/core/models/hive_objects/profile.g.dart
rename to lib/core/models/configuration/profile.g.dart
diff --git a/lib/core/models/hive_objects.dart b/lib/core/models/hive_objects.dart
deleted file mode 100644
index 8e6ecf4d..00000000
--- a/lib/core/models/hive_objects.dart
+++ /dev/null
@@ -1,2 +0,0 @@
-export 'hive_objects/indexer.dart';
-export 'hive_objects/profile.dart';
diff --git a/lib/core/models/log.dart b/lib/core/models/log.dart
new file mode 100644
index 00000000..9d09bf73
--- /dev/null
+++ b/lib/core/models/log.dart
@@ -0,0 +1,88 @@
+import 'package:flutter/material.dart';
+import 'package:lunasea/core.dart';
+import 'package:stack_trace/stack_trace.dart';
+
+part 'log.g.dart';
+
+/// Hive database object containing all information on a log
+@HiveType(typeId: 23, adapterName: 'LunaLogHiveObjectAdapter')
+class LunaLogHiveObject extends HiveObject {
+ LunaLogHiveObject({
+ @required this.timestamp,
+ @required this.type,
+ this.className,
+ this.methodName,
+ @required this.message,
+ this.error,
+ this.stackTrace,
+ });
+
+ factory LunaLogHiveObject.withMessage({
+ @required LunaLogType type,
+ @required String message,
+ String className,
+ String methodName,
+ }) {
+ assert(type != null);
+ int timestamp = DateTime.now().millisecondsSinceEpoch;
+ return LunaLogHiveObject(
+ timestamp: timestamp,
+ type: type,
+ message: message,
+ );
+ }
+
+ factory LunaLogHiveObject.withError({
+ @required LunaLogType type,
+ @required String message,
+ @required dynamic error,
+ @required StackTrace stackTrace,
+ String className,
+ String methodName,
+ }) {
+ assert(type != null);
+ int timestamp = DateTime.now().millisecondsSinceEpoch;
+ Trace trace = stackTrace == null ? null : Trace.from(stackTrace);
+ String _className, _methodName;
+ if((trace?.frames?.length ?? 0) >= 1) {
+ _className = trace.frames[0]?.uri?.toString();
+ _methodName = trace.frames[0]?.member?.toString();
+ }
+ return LunaLogHiveObject(
+ timestamp: timestamp,
+ type: type,
+ className: className ?? _className,
+ methodName: methodName ?? _methodName,
+ message: message,
+ error: error?.toString(),
+ stackTrace: trace?.toString(),
+ );
+ }
+
+ Map toMap() {
+ return {
+ "timestamp": DateTime.fromMillisecondsSinceEpoch(timestamp).toIso8601String(),
+ "type": type.name,
+ if(className != null && className.isNotEmpty) "class_name": className,
+ if(methodName != null && methodName.isNotEmpty) "method_name": methodName,
+ "message": message,
+ if(error != null && (error?.isNotEmpty ?? false)) "error": error,
+ if(stackTrace != null && (stackTrace?.isNotEmpty ?? false)) "stack_trace": stackTrace?.trim()?.split('\n') ?? [],
+ };
+ }
+
+ @HiveField(0)
+ final int timestamp;
+ @HiveField(1)
+ final LunaLogType type;
+ @HiveField(2)
+ final String className;
+ @HiveField(3)
+ final String methodName;
+ @HiveField(4)
+ final String message;
+ @HiveField(5)
+ final String error;
+ @HiveField(6)
+ final String stackTrace;
+}
diff --git a/lib/core/models/log.g.dart b/lib/core/models/log.g.dart
new file mode 100644
index 00000000..f652d560
--- /dev/null
+++ b/lib/core/models/log.g.dart
@@ -0,0 +1,59 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'log.dart';
+
+// **************************************************************************
+// TypeAdapterGenerator
+// **************************************************************************
+
+class LunaLogHiveObjectAdapter extends TypeAdapter {
+ @override
+ final int typeId = 23;
+
+ @override
+ LunaLogHiveObject read(BinaryReader reader) {
+ final numOfFields = reader.readByte();
+ final fields = {
+ for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
+ };
+ return LunaLogHiveObject(
+ timestamp: fields[0] as int,
+ type: fields[1] as LunaLogType,
+ className: fields[2] as String,
+ methodName: fields[3] as String,
+ message: fields[4] as String,
+ error: fields[5] as String,
+ stackTrace: fields[6] as String,
+ );
+ }
+
+ @override
+ void write(BinaryWriter writer, LunaLogHiveObject obj) {
+ writer
+ ..writeByte(7)
+ ..writeByte(0)
+ ..write(obj.timestamp)
+ ..writeByte(1)
+ ..write(obj.type)
+ ..writeByte(2)
+ ..write(obj.className)
+ ..writeByte(3)
+ ..write(obj.methodName)
+ ..writeByte(4)
+ ..write(obj.message)
+ ..writeByte(5)
+ ..write(obj.error)
+ ..writeByte(6)
+ ..write(obj.stackTrace);
+ }
+
+ @override
+ int get hashCode => typeId.hashCode;
+
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ other is LunaLogHiveObjectAdapter &&
+ runtimeType == other.runtimeType &&
+ typeId == other.typeId;
+}
diff --git a/lib/core/models/router/page_router.dart b/lib/core/models/router/page_router.dart
index 57b957ff..e932a54d 100644
--- a/lib/core/models/router/page_router.dart
+++ b/lib/core/models/router/page_router.dart
@@ -19,14 +19,29 @@ abstract class LunaPageRouter {
/// Define the route in the passed in router without any parameters required.
///
- /// Already has a definition that:
+ /// Already defines:
/// - Calls [FluroRouter.define] with the [fullRoute]
/// - Creates a basic [Handler]
/// - Sets the transition type to [LunaRouter.transitionType]
- void noParameterRouteDefinition(FluroRouter router, Widget widget) => router.define(
- fullRoute,
- handler: Handler(handlerFunc: (context, params) => widget),
- transitionType: LunaRouter.transitionType,
+ /// - Checks that the module is enabled, and if not returns a `[LunaNotEnabledRoute] instead of the intended route
+ void noParameterRouteDefinition(
+ FluroRouter router,
+ Widget widget,
+ { bool homeRoute = false }
+ );
+
+ /// Define the route in the passed in router with some parameters required.
+ ///
+ /// Already defines:
+ /// - Calls [FluroRouter.define] with the [fullRoute]
+ /// - Creates a basic [Handler]
+ /// - Sets the transition type to [LunaRouter.transitionType]
+ /// - Checks that the module is enabled, and if not returns a `[LunaNotEnabledRoute] instead of the intended route
+ /// - Parses and injects the required parameters into the route for the handler
+ void withParameterRouteDefinition(
+ FluroRouter router,
+ Widget Function(BuildContext, Map>) handlerFunc,
+ { bool homeRoute = false }
);
LunaPageRouter(this.fullRoute);
diff --git a/lib/core/models/types.dart b/lib/core/models/types.dart
index d8a7c218..75200e7a 100644
--- a/lib/core/models/types.dart
+++ b/lib/core/models/types.dart
@@ -1,2 +1,4 @@
export 'types/browser.dart';
+export 'types/indexer_icon.dart';
export 'types/loading_state.dart';
+export 'types/log_type.dart';
diff --git a/lib/core/models/types/browser.dart b/lib/core/models/types/browser.dart
index db2f340a..a764cffa 100644
--- a/lib/core/models/types/browser.dart
+++ b/lib/core/models/types/browser.dart
@@ -1,3 +1,5 @@
+import 'dart:math';
+
import 'package:flutter/material.dart';
import 'package:lunasea/core.dart';
@@ -63,7 +65,7 @@ extension LunaBrowserExtension on LunaBrowser {
}
String formatted(String url) {
- bool isHttps = url.substring(0, 8) == 'https://';
+ bool isHttps = url.substring(0, min(8, url.length)) == 'https://';
switch(this) {
case LunaBrowser.BRAVE_BROWSER: return 'brave://open-url?url=$url';
case LunaBrowser.GOOGLE_CHROME: return isHttps
diff --git a/lib/core/models/types/browser.g.dart b/lib/core/models/types/browser.g.dart
index 3adf715a..2539c471 100644
--- a/lib/core/models/types/browser.g.dart
+++ b/lib/core/models/types/browser.g.dart
@@ -24,7 +24,7 @@ class LunaBrowserAdapter extends TypeAdapter {
case 4:
return LunaBrowser.MOZILLA_FIREFOX;
default:
- return null;
+ return LunaBrowser.APPLE_SAFARI;
}
}
diff --git a/lib/core/models/types/indexer_icon.dart b/lib/core/models/types/indexer_icon.dart
new file mode 100644
index 00000000..48041ccf
--- /dev/null
+++ b/lib/core/models/types/indexer_icon.dart
@@ -0,0 +1,76 @@
+import 'package:flutter/material.dart';
+import 'package:lunasea/core.dart';
+
+part 'indexer_icon.g.dart';
+
+@HiveType(typeId: 22, adapterName: 'LunaIndexerIconAdapter')
+enum LunaIndexerIcon {
+ @HiveField(0)
+ GENERIC,
+ @HiveField(1)
+ DOGNZB,
+ @HiveField(2)
+ DRUNKENSLUG,
+ @HiveField(3)
+ NZBFINDER,
+ @HiveField(4)
+ NZBGEEK,
+ @HiveField(5)
+ NZBHYDRA,
+ @HiveField(6)
+ NZBSU,
+}
+
+extension LunaIndexerIconExtension on LunaIndexerIcon {
+ LunaIndexerIcon fromKey(String key) {
+ switch(key) {
+ case 'generic': return LunaIndexerIcon.GENERIC;
+ case 'dognzb': return LunaIndexerIcon.DOGNZB;
+ case 'drunkenslug': return LunaIndexerIcon.DRUNKENSLUG;
+ case 'nzbfinder': return LunaIndexerIcon.NZBFINDER;
+ case 'nzbgeek': return LunaIndexerIcon.NZBGEEK;
+ case 'nzbhydra': return LunaIndexerIcon.NZBHYDRA;
+ case 'nzbsu': return LunaIndexerIcon.NZBSU;
+ default: return LunaIndexerIcon.GENERIC;
+ }
+ }
+
+ String get key {
+ switch(this) {
+ case LunaIndexerIcon.GENERIC: return 'generic';
+ case LunaIndexerIcon.DOGNZB: return 'dognzb';
+ case LunaIndexerIcon.DRUNKENSLUG: return 'drunkenslug';
+ case LunaIndexerIcon.NZBFINDER: return 'nzbfinder';
+ case LunaIndexerIcon.NZBGEEK: return 'nzbgeek';
+ case LunaIndexerIcon.NZBHYDRA: return 'nzbhydra';
+ case LunaIndexerIcon.NZBSU: return 'nzbsu';
+ }
+ throw Exception('Unknown LunaIndexerIcon');
+ }
+
+ String get name {
+ switch(this) {
+ case LunaIndexerIcon.GENERIC: return 'Generic';
+ case LunaIndexerIcon.DOGNZB: return 'DOGnzb';
+ case LunaIndexerIcon.DRUNKENSLUG: return 'DrunkenSlug';
+ case LunaIndexerIcon.NZBFINDER: return 'NZBFinder';
+ case LunaIndexerIcon.NZBGEEK: return 'NZBGeek';
+ case LunaIndexerIcon.NZBHYDRA: return 'NZBHydra2';
+ case LunaIndexerIcon.NZBSU: return 'NZB.su';
+ }
+ throw Exception('Unknown LunaIndexerIcon');
+ }
+
+ IconData get icon {
+ switch(this) {
+ case LunaIndexerIcon.GENERIC: return Icons.rss_feed_rounded;
+ case LunaIndexerIcon.DOGNZB: return Icons.rss_feed_rounded;
+ case LunaIndexerIcon.DRUNKENSLUG: return Icons.rss_feed_rounded;
+ case LunaIndexerIcon.NZBFINDER: return Icons.rss_feed_rounded;
+ case LunaIndexerIcon.NZBGEEK: return Icons.rss_feed_rounded;
+ case LunaIndexerIcon.NZBHYDRA: return Icons.rss_feed_rounded;
+ case LunaIndexerIcon.NZBSU: return Icons.rss_feed_rounded;
+ }
+ throw Exception('Unknown LunaIndexerIcon');
+ }
+}
\ No newline at end of file
diff --git a/lib/core/models/types/indexer_icon.g.dart b/lib/core/models/types/indexer_icon.g.dart
new file mode 100644
index 00000000..e35dd172
--- /dev/null
+++ b/lib/core/models/types/indexer_icon.g.dart
@@ -0,0 +1,71 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'indexer_icon.dart';
+
+// **************************************************************************
+// TypeAdapterGenerator
+// **************************************************************************
+
+class LunaIndexerIconAdapter extends TypeAdapter {
+ @override
+ final int typeId = 22;
+
+ @override
+ LunaIndexerIcon read(BinaryReader reader) {
+ switch (reader.readByte()) {
+ case 0:
+ return LunaIndexerIcon.GENERIC;
+ case 1:
+ return LunaIndexerIcon.DOGNZB;
+ case 2:
+ return LunaIndexerIcon.DRUNKENSLUG;
+ case 3:
+ return LunaIndexerIcon.NZBFINDER;
+ case 4:
+ return LunaIndexerIcon.NZBGEEK;
+ case 5:
+ return LunaIndexerIcon.NZBHYDRA;
+ case 6:
+ return LunaIndexerIcon.NZBSU;
+ default:
+ return LunaIndexerIcon.GENERIC;
+ }
+ }
+
+ @override
+ void write(BinaryWriter writer, LunaIndexerIcon obj) {
+ switch (obj) {
+ case LunaIndexerIcon.GENERIC:
+ writer.writeByte(0);
+ break;
+ case LunaIndexerIcon.DOGNZB:
+ writer.writeByte(1);
+ break;
+ case LunaIndexerIcon.DRUNKENSLUG:
+ writer.writeByte(2);
+ break;
+ case LunaIndexerIcon.NZBFINDER:
+ writer.writeByte(3);
+ break;
+ case LunaIndexerIcon.NZBGEEK:
+ writer.writeByte(4);
+ break;
+ case LunaIndexerIcon.NZBHYDRA:
+ writer.writeByte(5);
+ break;
+ case LunaIndexerIcon.NZBSU:
+ writer.writeByte(6);
+ break;
+ }
+ }
+
+ @override
+ int get hashCode => typeId.hashCode;
+
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ other is LunaIndexerIconAdapter &&
+ runtimeType == other.runtimeType &&
+ typeId == other.typeId;
+}
diff --git a/lib/core/models/types/log_type.dart b/lib/core/models/types/log_type.dart
new file mode 100644
index 00000000..2e6dc07e
--- /dev/null
+++ b/lib/core/models/types/log_type.dart
@@ -0,0 +1,81 @@
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:lunasea/core.dart';
+
+part 'log_type.g.dart';
+
+@HiveType(typeId: 24, adapterName: 'LunaLogTypeAdapter')
+enum LunaLogType {
+ @HiveField(0)
+ WARNING,
+ @HiveField(1)
+ ERROR,
+ @HiveField(2)
+ CRITICAL,
+ @HiveField(3)
+ DEBUG,
+}
+
+extension LunaLogTypeExtension on LunaLogType {
+ LunaLogType fromKey(String key) {
+ switch(key) {
+ case 'warning': return LunaLogType.WARNING;
+ case 'error': return LunaLogType.ERROR;
+ case 'critical': return LunaLogType.CRITICAL;
+ case 'debug': return LunaLogType.DEBUG;
+ }
+ return null;
+ }
+
+ String get key {
+ switch(this) {
+ case LunaLogType.WARNING: return 'warning';
+ case LunaLogType.ERROR: return 'error';
+ case LunaLogType.CRITICAL: return 'critical';
+ case LunaLogType.DEBUG: return 'debug';
+ }
+ throw Exception('Invalid LunaLogType');
+ }
+
+ bool get enabled {
+ switch(this) {
+ case LunaLogType.WARNING: return true;
+ case LunaLogType.ERROR: return true;
+ case LunaLogType.CRITICAL: return true;
+ case LunaLogType.DEBUG: return kDebugMode;
+ }
+ throw Exception('Invalid LunaLogType');
+ }
+
+ String get name {
+ switch(this) {
+ case LunaLogType.WARNING: return 'Warning';
+ case LunaLogType.ERROR: return 'Error';
+ case LunaLogType.CRITICAL: return 'Critical';
+ case LunaLogType.DEBUG: return 'Debug';
+ }
+ throw Exception('Invalid LunaLogType');
+ }
+
+ String get description => 'View $name Logs';
+
+ IconData get icon {
+ switch(this) {
+ case LunaLogType.WARNING: return Icons.warning;
+ case LunaLogType.ERROR: return Icons.report_rounded;
+ case LunaLogType.CRITICAL: return Icons.new_releases;
+ case LunaLogType.DEBUG: return Icons.bug_report_rounded;
+ }
+ throw Exception('Invalid LunaLogType');
+ }
+
+ Color get color {
+ switch(this) {
+ case LunaLogType.WARNING: return LunaColours.orange;
+ case LunaLogType.ERROR: return LunaColours.red;
+ case LunaLogType.CRITICAL: return LunaColours.accent;
+ case LunaLogType.DEBUG: return LunaColours.blueGrey;
+ }
+ throw Exception('Invalid LunaLogType');
+ }
+}
diff --git a/lib/core/models/types/log_type.g.dart b/lib/core/models/types/log_type.g.dart
new file mode 100644
index 00000000..cedeba23
--- /dev/null
+++ b/lib/core/models/types/log_type.g.dart
@@ -0,0 +1,56 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'log_type.dart';
+
+// **************************************************************************
+// TypeAdapterGenerator
+// **************************************************************************
+
+class LunaLogTypeAdapter extends TypeAdapter {
+ @override
+ final int typeId = 24;
+
+ @override
+ LunaLogType read(BinaryReader reader) {
+ switch (reader.readByte()) {
+ case 0:
+ return LunaLogType.WARNING;
+ case 1:
+ return LunaLogType.ERROR;
+ case 2:
+ return LunaLogType.CRITICAL;
+ case 3:
+ return LunaLogType.DEBUG;
+ default:
+ return LunaLogType.WARNING;
+ }
+ }
+
+ @override
+ void write(BinaryWriter writer, LunaLogType obj) {
+ switch (obj) {
+ case LunaLogType.WARNING:
+ writer.writeByte(0);
+ break;
+ case LunaLogType.ERROR:
+ writer.writeByte(1);
+ break;
+ case LunaLogType.CRITICAL:
+ writer.writeByte(2);
+ break;
+ case LunaLogType.DEBUG:
+ writer.writeByte(3);
+ break;
+ }
+ }
+
+ @override
+ int get hashCode => typeId.hashCode;
+
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ other is LunaLogTypeAdapter &&
+ runtimeType == other.runtimeType &&
+ typeId == other.typeId;
+}
diff --git a/lib/core/pages.dart b/lib/core/pages.dart
new file mode 100644
index 00000000..e41b2e3e
--- /dev/null
+++ b/lib/core/pages.dart
@@ -0,0 +1,2 @@
+export 'pages/invalid_route.dart';
+export 'pages/not_enabled.dart';
diff --git a/lib/core/ui/invalid_route.dart b/lib/core/pages/invalid_route.dart
similarity index 100%
rename from lib/core/ui/invalid_route.dart
rename to lib/core/pages/invalid_route.dart
diff --git a/lib/core/pages/not_enabled.dart b/lib/core/pages/not_enabled.dart
new file mode 100644
index 00000000..fcd17fa2
--- /dev/null
+++ b/lib/core/pages/not_enabled.dart
@@ -0,0 +1,25 @@
+import 'package:flutter/material.dart';
+import 'package:lunasea/core.dart';
+
+class LunaNotEnabledRoute extends StatelessWidget {
+ final String module;
+
+ LunaNotEnabledRoute({
+ Key key,
+ @required this.module,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: LunaAppBar(
+ title: module,
+ scrollControllers: [],
+ ),
+ body: LunaMessage.moduleNotEnabled(
+ context: context,
+ module: module,
+ ),
+ );
+ }
+}
diff --git a/lib/core/system.dart b/lib/core/system.dart
index de5d743c..761a51b6 100644
--- a/lib/core/system.dart
+++ b/lib/core/system.dart
@@ -2,7 +2,6 @@ export 'system/firebase.dart';
export 'system/image_cache.dart';
export 'system/in_app_purchases.dart';
export 'system/localization.dart';
-export 'system/logger.dart';
export 'system/modules.dart';
export 'system/networking.dart';
export 'system/quick_actions.dart';
diff --git a/lib/core/system/firebase.dart b/lib/core/system/firebase.dart
index f736b7a9..8d2203e2 100644
--- a/lib/core/system/firebase.dart
+++ b/lib/core/system/firebase.dart
@@ -1,3 +1,4 @@
+export 'firebase/analytics.dart';
export 'firebase/auth.dart';
export 'firebase/core.dart';
export 'firebase/crashlytics.dart';
diff --git a/lib/core/system/firebase/analytics.dart b/lib/core/system/firebase/analytics.dart
new file mode 100644
index 00000000..cfc3da29
--- /dev/null
+++ b/lib/core/system/firebase/analytics.dart
@@ -0,0 +1,24 @@
+import 'package:firebase_analytics/firebase_analytics.dart';
+import 'package:firebase_analytics/observer.dart';
+import 'package:lunasea/core.dart';
+
+class LunaFirebaseAnalytics {
+ /// Returns an instance of [FirebaseAnalytics].
+ ///
+ /// Throws an error if [LunaFirebase.initialize] has not been called.
+ static FirebaseAnalytics get instance => FirebaseAnalytics();
+
+ /// Returns an instance of [FirebaseAnalyticsObserver].
+ ///
+ /// Throws an error if [LunaFirebase.initialize] has not been called.
+ static FirebaseAnalyticsObserver get observer => FirebaseAnalyticsObserver(analytics: instance);
+
+ /// Set the enabled state of Firebase Analytics.
+ void setEnabledState() {
+ bool state = LunaDatabaseValue.ENABLE_FIREBASE_ANALYTICS.data;
+ instance.setAnalyticsCollectionEnabled(state);
+ }
+
+ /// Log an "app_open" event.
+ Future appOpened() async => instance.logAppOpen();
+}
diff --git a/lib/core/system/firebase/crashlytics.dart b/lib/core/system/firebase/crashlytics.dart
index 5eb69de1..c5956443 100644
--- a/lib/core/system/firebase/crashlytics.dart
+++ b/lib/core/system/firebase/crashlytics.dart
@@ -1,4 +1,5 @@
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
+import 'package:flutter/foundation.dart';
import 'package:lunasea/core.dart';
class LunaFirebaseCrashlytics {
@@ -8,10 +9,8 @@ class LunaFirebaseCrashlytics {
static FirebaseCrashlytics get instance => FirebaseCrashlytics.instance;
/// Set the enabled state of Firebase Crashlytics.
- ///
- /// If `enabled` is supplied, use that value, else use [LunaDatabaseValue.ENABLED_SENTRY].
void setEnabledState() {
- bool state = LunaDatabaseValue.ENABLED_SENTRY.data;
+ bool state = kReleaseMode && LunaDatabaseValue.ENABLE_FIREBASE_CRASHLYTICS.data;
instance.setCrashlyticsCollectionEnabled(state);
}
}
diff --git a/lib/core/system/localization.dart b/lib/core/system/localization.dart
index 3ef6af55..22dcb31a 100644
--- a/lib/core/system/localization.dart
+++ b/lib/core/system/localization.dart
@@ -25,6 +25,7 @@ enum LunaLanguage {
ENGLISH,
FRENCH,
GERMAN,
+ ITALIAN,
NORWEGIAN_BOKMAL,
RUSSIAN,
SWEDISH,
@@ -35,6 +36,7 @@ extension LunaLanguageExtension on LunaLanguage {
if(locale.toLanguageTag() == LunaLanguage.ENGLISH.languageTag) return LunaLanguage.ENGLISH;
if(locale.toLanguageTag() == LunaLanguage.FRENCH.languageTag) return LunaLanguage.FRENCH;
if(locale.toLanguageTag() == LunaLanguage.GERMAN.languageTag) return LunaLanguage.GERMAN;
+ if(locale.toLanguageTag() == LunaLanguage.ITALIAN.languageTag) return LunaLanguage.ITALIAN;
if(locale.toLanguageTag() == LunaLanguage.NORWEGIAN_BOKMAL.languageTag) return LunaLanguage.NORWEGIAN_BOKMAL;
if(locale.toLanguageTag() == LunaLanguage.RUSSIAN.languageTag) return LunaLanguage.RUSSIAN;
if(locale.toLanguageTag() == LunaLanguage.SWEDISH.languageTag) return LunaLanguage.SWEDISH;
@@ -47,6 +49,7 @@ extension LunaLanguageExtension on LunaLanguage {
// Keep all languages enabled in debug mode while LunaSea is being adapted for localization
case LunaLanguage.FRENCH: return kDebugMode;
case LunaLanguage.GERMAN: return kDebugMode;
+ case LunaLanguage.ITALIAN: return kDebugMode;
case LunaLanguage.NORWEGIAN_BOKMAL: return kDebugMode;
case LunaLanguage.RUSSIAN: return kDebugMode;
case LunaLanguage.SWEDISH: return kDebugMode;
@@ -59,6 +62,7 @@ extension LunaLanguageExtension on LunaLanguage {
case LunaLanguage.ENGLISH: return 'English';
case LunaLanguage.FRENCH: return 'French';
case LunaLanguage.GERMAN: return 'German';
+ case LunaLanguage.ITALIAN: return 'Italian';
case LunaLanguage.NORWEGIAN_BOKMAL: return 'Norwegian (BokmĂĄl)';
case LunaLanguage.RUSSIAN: return 'Russian';
case LunaLanguage.SWEDISH: return 'Swedish';
@@ -71,6 +75,7 @@ extension LunaLanguageExtension on LunaLanguage {
case LunaLanguage.ENGLISH: return 'en';
case LunaLanguage.FRENCH: return 'fr';
case LunaLanguage.GERMAN: return 'de';
+ case LunaLanguage.ITALIAN: return 'it';
case LunaLanguage.NORWEGIAN_BOKMAL: return 'nb-NO';
case LunaLanguage.RUSSIAN: return 'ru';
case LunaLanguage.SWEDISH: return 'sv';
@@ -83,6 +88,7 @@ extension LunaLanguageExtension on LunaLanguage {
case LunaLanguage.ENGLISH: return Locale('en');
case LunaLanguage.FRENCH: return Locale('fr');
case LunaLanguage.GERMAN: return Locale('de');
+ case LunaLanguage.ITALIAN: return Locale('it');
case LunaLanguage.NORWEGIAN_BOKMAL: return Locale('nb', 'NO');
case LunaLanguage.RUSSIAN: return Locale('ru');
case LunaLanguage.SWEDISH: return Locale('sv');
diff --git a/lib/core/system/logger.dart b/lib/core/system/logger.dart
deleted file mode 100644
index 1bb7fe17..00000000
--- a/lib/core/system/logger.dart
+++ /dev/null
@@ -1,72 +0,0 @@
-import 'dart:async';
-import 'dart:io';
-import 'package:f_logs/f_logs.dart' show FLog, DataLogType, FormatType, LogsConfig;
-import 'package:flutter/foundation.dart';
-import 'package:flutter/material.dart';
-import 'package:lunasea/core.dart';
-import 'package:stack_trace/stack_trace.dart';
-
-class LunaLogger {
- static String get checkLogsMessage => 'lunasea.CheckLogsMessage'.tr();
-
- /// Initialize the logger by setting the timestamp format and capturing errors on [FlutterError.onError].
- void initialize() {
- LunaFirebaseCrashlytics().setEnabledState();
- LogsConfig config = FLog.getDefaultConfigurations()
- ..formatType = FormatType.FORMAT_SQUARE
- ..timestampFormat = 'MMMM dd, y - hh:mm:ss a';
- FLog.applyConfigurations(config);
- FlutterError.onError = (FlutterErrorDetails details, { bool forceReport = false }) async {
- if (kDebugMode) FlutterError.dumpErrorToConsole(details, forceReport: forceReport);
- LunaFirebaseCrashlytics.instance.recordFlutterError(details);
- Zone.current.handleUncaughtError(details.exception, details.stack);
- };
- }
-
- /// Log a new debug-level log.
- void debug(String text) => FLog.debug(
- text: text,
- dataLogType: DataLogType.DEFAULT.toString(),
- );
-
- /// Log a new warning-level log.
- void warning(String className, String methodName, String text) => FLog.warning(
- className: className,
- methodName: methodName,
- text: text,
- dataLogType: DataLogType.DEFAULT.toString(),
- );
-
- /// Log a new error-level log.
- void error(String text, dynamic error, StackTrace stack) {
- if(LunaFirebaseCrashlytics.instance.isCrashlyticsCollectionEnabled) LunaFirebaseCrashlytics.instance.recordError(error, stack);
- Trace _trace = stack == null ? null : Trace.from(stack);
- FLog.error(
- className: (_trace?.frames?.length ?? 0) >= 1 ? _trace.frames[1].uri.toString() ?? 'Unknown' : 'Unknown',
- methodName: (_trace?.frames?.length ?? 0) >= 1 ? _trace.frames[1].member.toString() ?? 'Unknown' : 'Unknown',
- text: text,
- exception: error,
- stacktrace: stack,
- dataLogType: DataLogType.DEFAULT.toString(),
- );
- }
-
- /// Log a new fatal-level log.
- void fatal(dynamic error, StackTrace stack) {
- if(LunaFirebaseCrashlytics.instance.isCrashlyticsCollectionEnabled) LunaFirebaseCrashlytics.instance.recordError(error, stack);
- Trace _trace = stack == null ? null : Trace.from(stack);
- FLog.fatal(
- className: (_trace?.frames?.length ?? 0) >= 1 ? _trace.frames[1].uri.toString() ?? 'Unknown' : 'Unknown',
- methodName: (_trace?.frames?.length ?? 0) >= 1 ? _trace.frames[1].member.toString() ?? 'Unknown' : 'Unknown',
- text: error.toString(),
- exception: error,
- stacktrace: stack,
- dataLogType: DataLogType.DEFAULT.toString(),
- );
- }
-
- /// Export all logs, and return the [File] object containing the log file.
- Future exportLogs() async => FLog.exportLogs();
- /// Clear all logs currently saved to the database.
- Future clearLogs() async => FLog.clearLogs();
-}
diff --git a/lib/core/system/modules.dart b/lib/core/system/modules.dart
index 9833ab03..246c12c9 100644
--- a/lib/core/system/modules.dart
+++ b/lib/core/system/modules.dart
@@ -91,7 +91,7 @@ extension LunaModuleExtension on LunaModule {
case LunaModule.SEARCH: return SearchHomeRouter().route();
case LunaModule.SETTINGS: return SettingsHomeRouter().route();
case LunaModule.SONARR: return SonarrHomeRouter().route();
- case LunaModule.TAUTULLI: return TautulliHomeRouter.ROUTE_NAME;
+ case LunaModule.TAUTULLI: return TautulliHomeRouter().route();
case LunaModule.WAKE_ON_LAN: return null;
}
throw Exception('Invalid LunaModule');
@@ -219,7 +219,7 @@ extension LunaModuleExtension on LunaModule {
case LunaModule.SEARCH: return;
case LunaModule.SETTINGS: return;
case LunaModule.SONARR: return SonarrWebhooks().handle(data);
- case LunaModule.TAUTULLI: return;
+ case LunaModule.TAUTULLI: return TautulliWebhooks().handle(data);
case LunaModule.WAKE_ON_LAN: return;
}
throw Exception('Invalid LunaModule');
diff --git a/lib/core/ui.dart b/lib/core/ui.dart
index 5c230607..786cd684 100644
--- a/lib/core/ui.dart
+++ b/lib/core/ui.dart
@@ -16,7 +16,6 @@ export 'ui/header.dart';
export 'ui/highlighted_node.dart';
export 'ui/icons.dart';
export 'ui/input_bar.dart';
-export 'ui/invalid_route.dart';
export 'ui/list_tile.dart';
export 'ui/list_view.dart';
export 'ui/loader.dart';
diff --git a/lib/core/ui/bottom_navigation_bar/bottom_navigation_bar.dart b/lib/core/ui/bottom_navigation_bar/bottom_navigation_bar.dart
index ab74611c..cfc46f04 100644
--- a/lib/core/ui/bottom_navigation_bar/bottom_navigation_bar.dart
+++ b/lib/core/ui/bottom_navigation_bar/bottom_navigation_bar.dart
@@ -33,7 +33,7 @@ class _State extends State {
@override
void initState() {
- _index = widget.pageController.initialPage;
+ _index = widget.pageController?.initialPage ?? 0;
widget.pageController?.addListener(_pageControllerListener);
super.initState();
}
@@ -93,7 +93,7 @@ class _State extends State {
if(index == _index) {
if(widget.scrollControllers != null && widget.scrollControllers[index] != null) widget.scrollControllers[index].lunaAnimateToStart();
} else {
- widget.pageController.lunaAnimateToPage(index);
+ widget.pageController.lunaJumpToPage(index);
}
}
}
diff --git a/lib/core/ui/decoration/card_decoration.dart b/lib/core/ui/decoration/card_decoration.dart
index 5dc0cf64..c4ff9f86 100644
--- a/lib/core/ui/decoration/card_decoration.dart
+++ b/lib/core/ui/decoration/card_decoration.dart
@@ -4,6 +4,7 @@ import 'package:lunasea/core.dart';
// ignore: non_constant_identifier_names
Decoration LunaCardDecoration({ @required String uri, @required Map headers }) {
if(LunaDatabaseValue.THEME_IMAGE_BACKGROUND_OPACITY.data == 0) return null;
+ if(uri == null || uri.isEmpty) return BoxDecoration(borderRadius: BorderRadius.circular(LunaUI.BORDER_RADIUS));
return BoxDecoration(
image: DecorationImage(
image: NetworkImage(
@@ -18,6 +19,6 @@ Decoration LunaCardDecoration({ @required String uri, @required Map headers }) {
),
fit: BoxFit.cover,
),
- borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS),
+ borderRadius: BorderRadius.circular(LunaUI.BORDER_RADIUS),
);
}
diff --git a/lib/core/ui/deprecated/dialogs.dart b/lib/core/ui/deprecated/dialogs.dart
index 887674da..78e35ada 100644
--- a/lib/core/ui/deprecated/dialogs.dart
+++ b/lib/core/ui/deprecated/dialogs.dart
@@ -56,7 +56,7 @@ abstract class LSDialog {
static Widget cancel(BuildContext context, { Color textColor = Colors.white, String text }) => TextButton(
child: Text(
- text ?? 'Cancel',
+ text ?? 'lunasea.Cancel'.tr(),
style: TextStyle(
color: textColor,
fontSize: LSDialog.BUTTON_SIZE,
diff --git a/lib/core/ui/deprecated/messages/typewriter.dart b/lib/core/ui/deprecated/messages/typewriter.dart
index 6b0da60f..f6235622 100644
--- a/lib/core/ui/deprecated/messages/typewriter.dart
+++ b/lib/core/ui/deprecated/messages/typewriter.dart
@@ -1,4 +1,5 @@
import 'dart:async';
+import 'dart:math';
import 'package:flutter/material.dart';
import 'package:lunasea/core.dart';
@@ -39,7 +40,7 @@ class _State extends State with TickerProviderStateMixin {
void handleTimer(Timer timer) {
_counter++;
if(_counter > widget.text.length) _counter = 0;
- if(mounted) setState(() => _text = widget.text.substring(0, _counter) + "_");
+ if(mounted) setState(() => _text = widget.text.substring(0, min(_counter, widget.text.length)) + "_");
}
@override
diff --git a/lib/core/ui/deprecated/network_image.dart b/lib/core/ui/deprecated/network_image.dart
index 43534516..e517614d 100644
--- a/lib/core/ui/deprecated/network_image.dart
+++ b/lib/core/ui/deprecated/network_image.dart
@@ -15,7 +15,7 @@ Widget LSNetworkImage({
fit: StackFit.expand,
children: [
Image.asset(placeholder),
- if(url != null) FadeInImage(
+ if(url != null && url.isNotEmpty) FadeInImage(
height: height,
width: width,
fadeInDuration: Duration(milliseconds: LunaUI.ANIMATION_IMAGE_FADE_IN_SPEED),
@@ -26,6 +26,10 @@ Widget LSNetworkImage({
url,
headers: headers,
),
+ imageErrorBuilder: (context, error, stack) => Container(
+ height: height,
+ width: width,
+ ),
),
],
),
diff --git a/lib/core/ui/drawer/drawer_header.dart b/lib/core/ui/drawer/drawer_header.dart
index df6a46f5..b99f40f3 100644
--- a/lib/core/ui/drawer/drawer_header.dart
+++ b/lib/core/ui/drawer/drawer_header.dart
@@ -50,7 +50,7 @@ class LunaDrawerHeader extends UserAccountsDrawerHeader {
decoration: BoxDecoration(
color: LunaColours.accent,
image: DecorationImage(
- image: AssetImage('assets/branding/icon_drawer.png'),
+ image: AssetImage('assets/images/branding/icon_drawer.png'),
colorFilter: ColorFilter.mode(LunaColours.primary.withOpacity(0.15), BlendMode.dstATop),
fit: BoxFit.cover,
),
diff --git a/lib/core/ui/icons/icon_button.dart b/lib/core/ui/icons/icon_button.dart
index e94cdd36..2bdc8fdc 100644
--- a/lib/core/ui/icons/icon_button.dart
+++ b/lib/core/ui/icons/icon_button.dart
@@ -11,6 +11,8 @@ class LunaIconButton extends StatelessWidget {
final Function onPressed;
final Function onLongPress;
final LunaLoadingState loadingState;
+ final AlignmentGeometry alignment;
+ final EdgeInsetsGeometry padding;
LunaIconButton({
Key key,
@@ -18,6 +20,8 @@ class LunaIconButton extends StatelessWidget {
this.textSize = 10.0,
this.icon,
this.iconSize = 24.0,
+ this.alignment = Alignment.center,
+ this.padding = const EdgeInsets.all(8.0),
this.color = Colors.white,
this.onPressed,
this.onLongPress,
@@ -32,6 +36,8 @@ class LunaIconButton extends StatelessWidget {
child: IconButton(
icon: loadingState == LunaLoadingState.ACTIVE ? _loader() : _icon(),
iconSize: iconSize,
+ alignment: alignment,
+ padding: padding,
onPressed: _onPressed(),
),
onLongPress: _onLongPress(),
diff --git a/lib/core/ui/list_view/list_view_modal.dart b/lib/core/ui/list_view/list_view_modal.dart
index d2444fa3..b12b4cae 100644
--- a/lib/core/ui/list_view/list_view_modal.dart
+++ b/lib/core/ui/list_view/list_view_modal.dart
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
+import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
class LunaListViewModal extends StatelessWidget {
final List children;
@@ -11,7 +12,9 @@ class LunaListViewModal extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scrollbar(
+ controller: ModalScrollController.of(context),
child: ListView(
+ controller: ModalScrollController.of(context),
children: children,
padding: EdgeInsets.only(
top: 8.0,
diff --git a/lib/core/ui/message/message.dart b/lib/core/ui/message/message.dart
index 5950bf55..053d2813 100644
--- a/lib/core/ui/message/message.dart
+++ b/lib/core/ui/message/message.dart
@@ -80,6 +80,7 @@ class LunaMessage extends StatelessWidget {
key: key,
text: 'lunasea.ModuleIsNotEnabled'.tr(args: [module]),
buttonText: 'lunasea.ReturnToDashboard'.tr(),
+ //onTap: () async => TautulliActivityDetailsRouter().navigateTo(context, sessionId: '92029620-7B5C-4C02-8769-9EB15626ECFC'),
onTap: () async => LunaModule.DASHBOARD.launch(),
useSafeArea: useSafeArea,
);
diff --git a/lib/core/ui/network_image.dart b/lib/core/ui/network_image.dart
index 634b8612..d61782d7 100644
--- a/lib/core/ui/network_image.dart
+++ b/lib/core/ui/network_image.dart
@@ -27,6 +27,10 @@ class LunaNetworkImage extends ClipRRect {
placeholder: AssetImage(placeholderAsset),
fit: BoxFit.cover,
image: NetworkImage(url, headers: headers?.cast()),
+ imageErrorBuilder: (context, error, stack) => Container(
+ height: height,
+ width: width,
+ ),
),
],
),
diff --git a/lib/core/utilities.dart b/lib/core/utilities.dart
index f190f3de..d9edb836 100644
--- a/lib/core/utilities.dart
+++ b/lib/core/utilities.dart
@@ -2,5 +2,6 @@ export 'utilities/changelog.dart';
export 'utilities/configuration.dart';
export 'utilities/encryption.dart';
export 'utilities/filesystem.dart';
+export 'utilities/logger.dart';
export 'utilities/profile.dart';
export 'utilities/uuid.dart';
diff --git a/lib/core/utilities/filesystem.dart b/lib/core/utilities/filesystem.dart
index 0a1d0079..6701e437 100644
--- a/lib/core/utilities/filesystem.dart
+++ b/lib/core/utilities/filesystem.dart
@@ -1,5 +1,6 @@
import 'dart:io';
-import 'package:share_plus/share_plus.dart';
+import 'package:lunasea/core.dart';
+import 'package:share/share.dart';
import 'package:path_provider/path_provider.dart';
class LunaFileSystem {
@@ -13,7 +14,10 @@ class LunaFileSystem {
String path = '${tempDirectory.path}/$name';
File file = File(path);
await file?.writeAsString(data);
- await Share.shareFiles([path]);
+ await Share.shareFiles([path])
+ .catchError((error, stack) {
+ LunaLogger().error('Failed to share string to sharesheet', error, stack);
+ });
}
/// Given a path to a file, shares the file to the OS-level share sheet with the given name.
@@ -22,6 +26,9 @@ class LunaFileSystem {
/// If the file does not exist, will simply do nothing.
Future exportFileToShareSheet(String path) async {
File file = File(path);
- if(file.existsSync()) await Share.shareFiles([path]);
+ if(file.existsSync()) await Share.shareFiles([path])
+ .catchError((error, stack) {
+ LunaLogger().error('Failed to share string to sharesheet', error, stack);
+ });
}
}
diff --git a/lib/core/utilities/logger.dart b/lib/core/utilities/logger.dart
new file mode 100644
index 00000000..074249ac
--- /dev/null
+++ b/lib/core/utilities/logger.dart
@@ -0,0 +1,129 @@
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+import 'package:dio/dio.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:lunasea/core.dart';
+import 'package:path_provider/path_provider.dart';
+
+class LunaLogger {
+ static String get checkLogsMessage => 'lunasea.CheckLogsMessage'.tr();
+
+ /// Initialize the logger by setting the timestamp format and capturing errors on [FlutterError.onError].
+ void initialize() {
+ LunaFirebaseAnalytics().setEnabledState();
+ LunaFirebaseCrashlytics().setEnabledState();
+ FlutterError.onError = (FlutterErrorDetails details, { bool forceReport = false }) async {
+ if (kDebugMode) FlutterError.dumpErrorToConsole(details, forceReport: forceReport);
+ recordCrashlyticsFlutterError(details);
+ Zone.current.handleUncaughtError(details.exception, details.stack);
+ };
+ _compactDatabase();
+ }
+
+ /// Compact the logs database.
+ ///
+ /// If the logs box has over [count] entries, will only store the latest 100.
+ Future _compactDatabase([int count = 100]) async {
+ if(Database.logsBox.keys.length <= count) return;
+ List logs = Database.logsBox.values.toList();
+ logs.sort((a,b) => (b?.timestamp?.toDouble() ?? double.maxFinite).compareTo(a?.timestamp?.toDouble() ?? double.maxFinite));
+ logs.skip(count).forEach((log) => log.delete());
+ }
+
+ /// Export all logs, and return the [File] object containing the log file.
+ Future exportLogs() async {
+ // Get maps/JSON of all logs
+ List