[Release] v4.2.0+40200001 (#287)

NEW
- [Settings/Modules] Add an information/help button with module descriptions and links

TWEAKS
- [Radarr] Always show the amount of days content will be available instead of limiting it to only content in the next 30 days
- [Settings] Moved "Backup & Restore" and "Logs" to "System" section
- [Settings] Merged "Customization" and "Modules" sections into "Configuration"
- [Settings/Sonarr] Removed the need to enable Sonarr to test the connection
- [Settings/Tautulli] Removed the need to enable Tautulli to test the connection

FIXES
- [IAPs] Ensure all IAPs are marked as "consumed"
- [Flutter] Updated packages
- [Logging] Updated Sentry to v4 framework to improve capturing fatal/crashing bugs
- [Settings/Logs] Hide exception and stack trace buttons when an error is not available
- [Sonarr/History] Fixed history fetching the oldest entries, not the newest
- [State] Correctly clear state when clearing LunaSea's configuration
- [Tautulli/Activity] Fixed consistency of hardware transcoding indicator compared to the web UI
- [UI/Divider] Fix consistency of divider width across regular and AMOLED dark theme
This commit is contained in:
Jagandeep Brar
2020-12-22 08:46:25 -06:00
committed by GitHub
parent b6333b014d
commit f7807ed762
561 changed files with 6995 additions and 8535 deletions

5
.gitignore vendored
View File

@@ -49,3 +49,8 @@ _changelog.txt
# Build Configurations
ExportOptions.plist
key.properties
# Firebase
android/app/google-services.json
ios/GoogleService-Info.plist
output/

View File

@@ -1,5 +1,29 @@
# LunaSea Changelog
## [Beta/TestFlight] v4.2.0 (40200001)
#### NEW
- `[Settings/Modules]` Add an information/help button with module descriptions and links
#### TWEAKS
- `[Radarr]` Always show the amount of days content will be available instead of limiting it to only content in the next 30 days
- `[Settings]` Moved "Backup & Restore" and "Logs" to "System" section
- `[Settings]` Merged "Customization" and "Modules" sections into "Configuration"
- `[Settings/Sonarr]` Removed the need to enable Sonarr to test the connection
- `[Settings/Tautulli]` Removed the need to enable Tautulli to test the connection
#### FIXES
- `[IAPs]` Ensure all IAPs are marked as "consumed"
- `[Flutter]` Updated packages
- `[Logging]` Updated Sentry to v4 framework to improve capturing fatal/crashing bugs
- `[Settings/Logs]` Hide exception and stack trace buttons when an error is not available
- `[Sonarr/History]` Fixed history fetching the oldest entries, not the newest
- `[State]` Correctly clear state when clearing LunaSea's configuration
- `[Tautulli/Activity]` Fixed consistency of hardware transcoding indicator compared to the web UI
- `[UI/Divider]` Fix consistency of divider width across regular and AMOLED dark theme
---
## [Stable/Production] v4.1.1 (40101001)
#### NEW

View File

@@ -2,21 +2,19 @@
[![Play Store](https://img.shields.io/badge/Play%20Store-v4.1.1-red?style=for-the-badge&logo=google-play&color=%234CAF50&logoColor=white)](https://play.google.com/store/apps/details?id=app.lunasea.lunasea)
[![Subreddit](https://img.shields.io/reddit/subreddit-subscribers/LunaSeaApp?label=reddit&logo=reddit&color=%23F44336&logoColor=white&style=for-the-badge)](https://www.reddit.com/r/LunaSeaApp)
![LunaSea](https://uploads-ssl.webflow.com/5fcfe91dceef944b8c203a63/5fd235627cda4b4967b528d8_banner.png)
![LunaSea](https://uploads-ssl.webflow.com/5fcfe91dceef944b8c203a63/5fd4d46b86a0830f10cad146_lunasea_splash_2.png)
![Screenshot](https://uploads-ssl.webflow.com/5fcfe91dceef944b8c203a63/5fd235d3153d39465529f5eb_hero.png)
LunaSea is a fully featured, open source self-hosted software controller! Focused on giving you a seamless experience between all of your self-hosted media software, LunaSea supports:
LunaSea is a fully featured, open source self-hosted controller! Focused on giving you a seamless experience between all of your self-hosted media software, LunaSea supports:
- Lidarr
- Radarr
- Sonarr
- SABnzbd
- NZBGet
- NZBHydra2
- SABnzbd
- Newznab Indexer Searching
- Wake on LAN
- NZBHydra2
- Tautulli
- Wake on LAN
LunaSea even comes with support for multiple instances of applications using profiles, backing up and restoring your configuration to your filesystem, an AMOLED black theme, and more!
@@ -33,6 +31,7 @@ LunaSea even comes with support for multiple instances of applications using pro
3. [Flutter Framework (Stable Channel)](https://flutter.dev/)
4. [XCode](https://apps.apple.com/ca/app/xcode/id497799835?mt=12)
5. [Developer Certificate Configured](https://github.com/LunaSeaApp/LunaSea/wiki/Setup-of-Development-Certificate)
6. [FlutterFire for IOS Configured](https://firebase.flutter.dev/docs/installation/ios/)
#### Developing
@@ -74,6 +73,7 @@ LunaSea even comes with support for multiple instances of applications using pro
1. [Android SDK/Android Studio Installed & Configured](https://developer.android.com/studio)
2. [Flutter Framework (Stable Channel)](https://flutter.dev/)
3. [Keystore Configured](https://github.com/LunaSeaApp/LunaSea/wiki/Configure-Keystore)
4. [FlutterFire for Android Configured](https://firebase.flutter.dev/docs/installation/android/)
#### Developing

View File

@@ -23,6 +23,7 @@ if (flutterVersionName == null) {
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'com.google.gms.google-services'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
def keystoreProperties = new Properties()

View File

@@ -8,6 +8,7 @@ buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:3.6.4'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.3'
}
}

Binary file not shown.

View File

@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'
platform :ios, '11.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
@@ -30,25 +30,12 @@ flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
use_modular_headers!
pod 'FirebaseFirestore', :git => 'https://github.com/invertase/firestore-ios-sdk-frameworks.git', :tag => '6.33.0'
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config|
config.build_settings['ENABLE_BITCODE'] = 'NO'
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
'PERMISSION_SPEECH_RECOGNIZER=0',
'PERMISSION_SENSORS=0',
'PERMISSION_MEDIA_LIBRARY=0',
'PERMISSION_EVENTS=0',
'PERMISSION_CONTACTS=0',
'PERMISSION_REMINDERS=0',
'PERMISSION_MICROPHONE=0'
]
end
end
end

View File

@@ -1,4 +1,9 @@
PODS:
- cloud_firestore (0.14.4):
- Firebase/CoreOnly (~> 6.33.0)
- Firebase/Firestore (~> 6.33.0)
- firebase_core
- Flutter
- DKImagePickerController/Core (4.3.2):
- DKImagePickerController/ImageDataManager
- DKImagePickerController/Resource
@@ -33,20 +38,92 @@ PODS:
- file_picker (0.0.1):
- DKImagePickerController/PhotoGallery
- Flutter
- Firebase/Auth (6.33.0):
- Firebase/CoreOnly
- FirebaseAuth (~> 6.9.2)
- Firebase/CoreOnly (6.33.0):
- FirebaseCore (= 6.10.3)
- Firebase/Firestore (6.33.0):
- Firebase/CoreOnly
- FirebaseFirestore (~> 1.18.0)
- Firebase/Storage (6.33.0):
- Firebase/CoreOnly
- FirebaseStorage (~> 3.9.0)
- firebase_auth (0.18.4-1):
- Firebase/Auth (~> 6.33.0)
- Firebase/CoreOnly (~> 6.33.0)
- firebase_core
- Flutter
- firebase_core (0.5.3):
- Firebase/CoreOnly (~> 6.33.0)
- Flutter
- firebase_storage (5.2.0):
- Firebase/CoreOnly (~> 6.33.0)
- Firebase/Storage (~> 6.33.0)
- firebase_core
- Flutter
- FirebaseAuth (6.9.2):
- FirebaseCore (~> 6.10)
- GoogleUtilities/AppDelegateSwizzler (~> 6.7)
- GoogleUtilities/Environment (~> 6.7)
- GTMSessionFetcher/Core (~> 1.1)
- FirebaseCore (6.10.3):
- FirebaseCoreDiagnostics (~> 1.6)
- GoogleUtilities/Environment (~> 6.7)
- GoogleUtilities/Logger (~> 6.7)
- FirebaseCoreDiagnostics (1.7.0):
- GoogleDataTransport (~> 7.4)
- GoogleUtilities/Environment (~> 6.7)
- GoogleUtilities/Logger (~> 6.7)
- nanopb (~> 1.30906.0)
- FirebaseFirestore (1.18.0)
- FirebaseStorage (3.9.1):
- FirebaseCore (~> 6.10)
- GTMSessionFetcher/Core (~> 1.1)
- Flutter (1.0.0)
- GoogleDataTransport (7.5.1):
- nanopb (~> 1.30906.0)
- GoogleUtilities/AppDelegateSwizzler (6.7.2):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
- GoogleUtilities/Network
- GoogleUtilities/Environment (6.7.2):
- PromisesObjC (~> 1.2)
- GoogleUtilities/Logger (6.7.2):
- GoogleUtilities/Environment
- GoogleUtilities/Network (6.7.2):
- GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib"
- GoogleUtilities/Reachability
- "GoogleUtilities/NSData+zlib (6.7.2)"
- GoogleUtilities/Reachability (6.7.2):
- GoogleUtilities/Logger
- GTMSessionFetcher/Core (1.5.0)
- in_app_purchase (0.0.1):
- Flutter
- nanopb (1.30906.0):
- nanopb/decode (= 1.30906.0)
- nanopb/encode (= 1.30906.0)
- nanopb/decode (1.30906.0)
- nanopb/encode (1.30906.0)
- package_info (0.0.1):
- Flutter
- path_provider (0.0.1):
- Flutter
- "permission_handler (5.0.1+1)":
- Flutter
- PromisesObjC (1.2.11)
- quick_actions (0.0.1):
- Flutter
- SDWebImage (5.10.0):
- SDWebImage/Core (= 5.10.0)
- SDWebImage/Core (5.10.0)
- Sentry (6.0.12):
- Sentry/Core (= 6.0.12)
- Sentry/Core (6.0.12)
- sentry_flutter (0.0.1):
- Flutter
- Sentry (~> 6.0.10)
- share (0.0.1):
- Flutter
- SwiftyGif (5.3.0)
@@ -54,13 +131,19 @@ PODS:
- Flutter
DEPENDENCIES:
- cloud_firestore (from `.symlinks/plugins/cloud_firestore/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- firebase_auth (from `.symlinks/plugins/firebase_auth/ios`)
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
- firebase_storage (from `.symlinks/plugins/firebase_storage/ios`)
- FirebaseFirestore (from `https://github.com/invertase/firestore-ios-sdk-frameworks.git`, tag `6.33.0`)
- Flutter (from `Flutter`)
- in_app_purchase (from `.symlinks/plugins/in_app_purchase/ios`)
- package_info (from `.symlinks/plugins/package_info/ios`)
- path_provider (from `.symlinks/plugins/path_provider/ios`)
- permission_handler (from `.symlinks/plugins/permission_handler/ios`)
- quick_actions (from `.symlinks/plugins/quick_actions/ios`)
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
- share (from `.symlinks/plugins/share/ios`)
- url_launcher (from `.symlinks/plugins/url_launcher/ios`)
@@ -68,12 +151,34 @@ SPEC REPOS:
trunk:
- DKImagePickerController
- DKPhotoGallery
- Firebase
- FirebaseAuth
- FirebaseCore
- FirebaseCoreDiagnostics
- FirebaseStorage
- GoogleDataTransport
- GoogleUtilities
- GTMSessionFetcher
- nanopb
- PromisesObjC
- SDWebImage
- Sentry
- SwiftyGif
EXTERNAL SOURCES:
cloud_firestore:
:path: ".symlinks/plugins/cloud_firestore/ios"
file_picker:
:path: ".symlinks/plugins/file_picker/ios"
firebase_auth:
:path: ".symlinks/plugins/firebase_auth/ios"
firebase_core:
:path: ".symlinks/plugins/firebase_core/ios"
firebase_storage:
:path: ".symlinks/plugins/firebase_storage/ios"
FirebaseFirestore:
:git: https://github.com/invertase/firestore-ios-sdk-frameworks.git
:tag: 6.33.0
Flutter:
:path: Flutter
in_app_purchase:
@@ -86,26 +191,50 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/permission_handler/ios"
quick_actions:
:path: ".symlinks/plugins/quick_actions/ios"
sentry_flutter:
:path: ".symlinks/plugins/sentry_flutter/ios"
share:
:path: ".symlinks/plugins/share/ios"
url_launcher:
:path: ".symlinks/plugins/url_launcher/ios"
CHECKOUT OPTIONS:
FirebaseFirestore:
:git: https://github.com/invertase/firestore-ios-sdk-frameworks.git
:tag: 6.33.0
SPEC CHECKSUMS:
cloud_firestore: b8c0e15fa49dfff87c2817d288b577e5dca2df13
DKImagePickerController: b5eb7f7a388e4643264105d648d01f727110fc3d
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1
Firebase: 8db6f2d1b2c5e2984efba4949a145875a8f65fe5
firebase_auth: d5159db3873478d1ac839af7b10d2f831516136a
firebase_core: 5d6a02f3d85acd5f8321c2d6d62877626a670659
firebase_storage: a023e199edb807d8481c8aa722c8516f462ffab2
FirebaseAuth: c92d49ada7948d1a23466e3db17bc4c2039dddc3
FirebaseCore: d889d9e12535b7f36ac8bfbf1713a0836a3012cd
FirebaseCoreDiagnostics: 770ac5958e1372ce67959ae4b4f31d8e127c3ac1
FirebaseFirestore: 4e00bcfe64c4cb493124b87e249abe6c580403c7
FirebaseStorage: 15e0f15ef3c7fec3d1899d68623e47d4447066b4
Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
GoogleDataTransport: f56af7caa4ed338dc8e138a5d7c5973e66440833
GoogleUtilities: 7f2f5a07f888cdb145101d6042bc4422f57e70b3
GTMSessionFetcher: b3503b20a988c4e20cc189aa798fd18220133f52
in_app_purchase: 3e2155afa9d03d4fa32d9e62d567885080ce97d6
nanopb: 59317e09cf1f1a0af72f12af412d54edf52603fc
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
permission_handler: eac8e15b4a1a3fba55b761d19f3f4e6b005d15b6
PromisesObjC: 8c196f5a328c2cba3e74624585467a557dcb482f
quick_actions: 6cb2390c4dab0e737c94573c27e18d9666710720
SDWebImage: 9169792e9eec3e45bba2a0c02f74bf8bd922d1ee
Sentry: 8ffcfe3416e0a58b90bb1ae319e8e8a13022a03e
sentry_flutter: e57e33f56d5791e4effa48e85e44505367686b27
share: 0b2c3e82132f5888bccca3351c504d0003b3b410
SwiftyGif: e466e86c660d343357ab944a819a101c4127cb40
url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
PODFILE CHECKSUM: d583357f6c19d22241654320250048fb085838b3
PODFILE CHECKSUM: 1b77370c3c4c6c5f98e1d8ea21a13d3289f277f1
COCOAPODS: 1.10.0

View File

@@ -15,6 +15,7 @@
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
9C2991242453FE3100B93AB2 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9C2991232453FE3100B93AB2 /* StoreKit.framework */; };
9C5DBBF2257EB50400836D8D /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9C5DBBF1257EB50400836D8D /* GoogleService-Info.plist */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
@@ -47,6 +48,7 @@
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
9C0A396F23E0F00900EC8127 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
9C2991232453FE3100B93AB2 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; };
9C5DBBF1257EB50400836D8D /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
A046209115F73D5EC348E242 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
D5A8F6AE14D184A58D8A7331 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
E2F8912DE8B8A6F72277E96C /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
@@ -79,6 +81,7 @@
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9C5DBBF1257EB50400836D8D /* GoogleService-Info.plist */,
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
@@ -153,6 +156,7 @@
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
FB187DB152E6AC8A61142056 /* [CP] Embed Pods Frameworks */,
5BA92B8D22335C6E222F251E /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -205,6 +209,7 @@
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
9C5DBBF2257EB50400836D8D /* GoogleService-Info.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
@@ -227,6 +232,24 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
5BA92B8D22335C6E222F251E /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh",
"${PODS_ROOT}/FirebaseFirestore/FirebaseFirestore/Resources/gRPCCertificates-Cpp.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/gRPCCertificates-Cpp.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -273,13 +296,19 @@
"${BUILT_PRODUCTS_DIR}/DKImagePickerController/DKImagePickerController.framework",
"${BUILT_PRODUCTS_DIR}/DKPhotoGallery/DKPhotoGallery.framework",
"${PODS_ROOT}/../Flutter/Flutter.framework",
"${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework",
"${BUILT_PRODUCTS_DIR}/GoogleUtilities/GoogleUtilities.framework",
"${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework",
"${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework",
"${BUILT_PRODUCTS_DIR}/Sentry/Sentry.framework",
"${BUILT_PRODUCTS_DIR}/SwiftyGif/SwiftyGif.framework",
"${BUILT_PRODUCTS_DIR}/file_picker/file_picker.framework",
"${BUILT_PRODUCTS_DIR}/in_app_purchase/in_app_purchase.framework",
"${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework",
"${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}/sentry_flutter/sentry_flutter.framework",
"${BUILT_PRODUCTS_DIR}/share/share.framework",
"${BUILT_PRODUCTS_DIR}/url_launcher/url_launcher.framework",
);
@@ -288,13 +317,19 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKImagePickerController.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKPhotoGallery.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMSessionFetcher.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleUtilities.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBLPromises.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sentry.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyGif.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_picker.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/in_app_purchase.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework",
"${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}/sentry_flutter.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/share.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher.framework",
);

View File

@@ -1,20 +1,22 @@
export 'core/constants.dart';
export 'core/adapters.dart';
export 'core/configuration.dart';
export 'core/constants.dart';
export 'core/database.dart';
export 'core/dialogs.dart';
export 'core/encryption.dart';
export 'core/extensions.dart';
export 'core/filesystem.dart';
export 'core/image_cache.dart';
export 'core/filesystem.dart';
export 'core/firebase.dart';
export 'core/in_app_purchases.dart';
export 'core/logger.dart';
export 'core/luna_ui.dart';
export 'core/module_map.dart';
export 'core/module_metadata.dart';
export 'core/networking.dart';
export 'core/profile.dart';
export 'core/providers.dart';
export 'core/quick_actions.dart';
export 'core/router.dart';
export 'core/state.dart';
export 'core/theme.dart';
export 'core/types.dart';
export 'core/ui.dart';

View File

@@ -1,3 +1,2 @@
export 'adapters/indexer.dart';
export 'adapters/lunasea.dart';
export 'adapters/profile.dart';

View File

@@ -0,0 +1,61 @@
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
part 'indexer.g.dart';
/// Hive database object containing all information on an indexer
@HiveType(typeId: 1, adapterName: 'IndexerHiveObjectAdapter')
class IndexerHiveObject extends HiveObject {
/// Create a new [IndexerHiveObject] object with all fields set to empty values('', false, 0, {}, etc.)
factory IndexerHiveObject.empty() => IndexerHiveObject(
displayName: '',
host: '',
key: '',
headers: {},
);
/// Create a new [IndexerHiveObject] from another [IndexerHiveObject] (deep-copy).
factory IndexerHiveObject.fromIndexerHiveObject(IndexerHiveObject indexer) => IndexerHiveObject(
displayName: indexer.displayName,
host: indexer.host,
key: indexer.key,
headers: indexer.headers,
);
/// Create a new [IndexerHiveObject] from a map where the keys map 1-to-1.
///
/// - Does _not_ do type checking, and will throw an error if the type is invalid.
/// - If the key is null, sets to the "empty" value
factory IndexerHiveObject.fromMap(Map indexer) => IndexerHiveObject(
displayName: indexer['displayName'] ?? '',
host: indexer['host'] ?? '',
key: indexer['key'] ?? '',
headers: indexer['headers'] ?? {},
);
IndexerHiveObject({
@required this.displayName,
@required this.host,
@required this.key,
@required this.headers,
});
@override
String toString() => toMap().toString();
Map<String, dynamic> toMap() => {
"displayName": displayName ?? '',
"host": host ?? '',
"key": key ?? '',
"headers": headers ?? {},
};
@HiveField(0)
String displayName;
@HiveField(1)
String host;
@HiveField(2)
String key;
@HiveField(3)
Map headers;
}

View File

@@ -0,0 +1,415 @@
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:lunasea/core.dart';
import 'package:lunasea/modules.dart';
part 'profile.g.dart';
/// Hive database object containing all profile fields
@HiveType(typeId: 0, adapterName: 'ProfileHiveObjectAdapter')
class ProfileHiveObject extends HiveObject {
/// Create a new [ProfileHiveObject] object with all fields set to empty values('', false, 0, {}, etc.)
factory ProfileHiveObject.empty() => ProfileHiveObject(
//Lidarr
lidarrEnabled: false,
lidarrHost: '',
lidarrKey: '',
lidarrHeaders: {},
//Radarr
radarrEnabled: false,
radarrHost: '',
radarrKey: '',
radarrHeaders: {},
//Sonarr
sonarrEnabled: false,
sonarrHost: '',
sonarrKey: '',
sonarrVersion3: false,
sonarrHeaders: {},
//SABnzbd
sabnzbdEnabled: false,
sabnzbdHost: '',
sabnzbdKey: '',
sabnzbdHeaders: {},
//NZBGet
nzbgetEnabled: false,
nzbgetHost: '',
nzbgetUser: '',
nzbgetPass: '',
nzbgetBasicAuth: false,
nzbgetHeaders: {},
//Wake on LAN
wakeOnLANEnabled: false,
wakeOnLANBroadcastAddress: '',
wakeOnLANMACAddress: '',
//Tautulli
tautulliEnabled: false,
tautulliHost: '',
tautulliKey: '',
tautulliHeaders: {},
//Ombi
ombiEnabled: false,
ombiHost: '',
ombiKey: '',
ombiHeaders: {},
);
/// Create a new [ProfileHiveObject] from another [ProfileHiveObject] (deep-copy).
factory ProfileHiveObject.fromProfileHiveObject(ProfileHiveObject profile) => ProfileHiveObject(
//Lidarr
lidarrEnabled: profile.lidarrEnabled,
lidarrHost: profile.lidarrHost,
lidarrKey: profile.lidarrKey,
lidarrHeaders: profile.lidarrHeaders,
//Radarr
radarrEnabled: profile.radarrEnabled,
radarrHost: profile.radarrHost,
radarrKey: profile.radarrKey,
radarrHeaders: profile.radarrHeaders,
//Sonarr
sonarrEnabled: profile.sonarrEnabled,
sonarrHost: profile.sonarrHost,
sonarrKey: profile.sonarrKey,
sonarrVersion3: profile.sonarrVersion3,
sonarrHeaders: profile.sonarrHeaders,
//SABnzbd
sabnzbdEnabled: profile.sabnzbdEnabled,
sabnzbdHost: profile.sabnzbdHost,
sabnzbdKey: profile.sabnzbdKey,
sabnzbdHeaders: profile.sabnzbdHeaders,
//NZBGet
nzbgetEnabled: profile.nzbgetEnabled,
nzbgetHost: profile.nzbgetHost,
nzbgetUser: profile.nzbgetUser,
nzbgetPass: profile.nzbgetPass,
nzbgetBasicAuth: profile.nzbgetBasicAuth,
nzbgetHeaders: profile.nzbgetHeaders,
//Wake On LAN
wakeOnLANEnabled: profile.wakeOnLANEnabled,
wakeOnLANBroadcastAddress: profile.wakeOnLANBroadcastAddress,
wakeOnLANMACAddress: profile.wakeOnLANMACAddress,
//Tautulli
tautulliEnabled: profile.tautulliEnabled,
tautulliHost: profile.tautulliHost,
tautulliKey: profile.tautulliKey,
tautulliHeaders: profile.tautulliHeaders,
//Ombi
ombiEnabled: profile.ombiEnabled,
ombiHost: profile.ombiHost,
ombiKey: profile.ombiKey,
ombiHeaders: profile.ombiHeaders,
);
/// Create a new [ProfileHiveObject] from a map where the keys map 1-to-1.
///
/// - Does _not_ do type checking, and will throw an error if the type is invalid.
/// - If the key is null, sets to the "empty" value
factory ProfileHiveObject.fromMap(Map profile) => ProfileHiveObject(
//Sonarr
sonarrEnabled: profile['sonarrEnabled'] ?? false,
sonarrHost: profile['sonarrHost'] ?? '',
sonarrKey: profile['sonarrKey'] ?? '',
sonarrVersion3: profile['sonarrVersion3'] ?? false,
sonarrHeaders: profile['sonarrHeaders'] ?? {},
//Radarr
radarrEnabled: profile['radarrEnabled'] ?? false,
radarrHost: profile['radarrHost'] ?? '',
radarrKey: profile['radarrKey'] ?? '',
radarrHeaders: profile['radarrHeaders'] ?? {},
//Lidarr
lidarrEnabled: profile['lidarrEnabled'] ?? false,
lidarrHost: profile['lidarrHost'] ?? '',
lidarrKey: profile['lidarrKey'] ?? '',
lidarrHeaders: profile['lidarrHeaders'] ?? {},
//SABnzbd
sabnzbdEnabled: profile['sabnzbdEnabled'] ?? false,
sabnzbdHost: profile['sabnzbdHost'] ?? '',
sabnzbdKey: profile['sabnzbdKey'] ?? '',
sabnzbdHeaders: profile['sabnzbdHeaders'] ?? {},
//NZBGet
nzbgetEnabled: profile['nzbgetEnabled'] ?? false,
nzbgetHost: profile['nzbgetHost'] ?? '',
nzbgetUser: profile['nzbgetUser'] ?? '',
nzbgetPass: profile['nzbgetPass'] ?? '',
nzbgetBasicAuth: profile['nzbgetBasicAuth'] ?? false,
nzbgetHeaders: profile['nzbgetHeaders'] ?? {},
//Wake on LAN
wakeOnLANEnabled: profile['wakeOnLANEnabled'] ?? false,
wakeOnLANBroadcastAddress: profile['wakeOnLANBroadcastAddress'] ?? '',
wakeOnLANMACAddress: profile['wakeOnLANMACAddress'] ?? '',
//Tautulli
tautulliEnabled: profile['tautulliEnabled'] ?? false,
tautulliHost: profile['tautulliHost'] ?? '',
tautulliKey: profile['tautulliKey'] ?? '',
tautulliHeaders: profile['tautulliHeaders'] ?? {},
//Ombi
ombiEnabled: profile['ombiEnabled'] ?? false,
ombiHost: profile['ombiHost'] ?? '',
ombiKey: profile['ombiKey'] ?? '',
ombiHeaders: profile['ombiHeaders'] ?? {},
);
ProfileHiveObject({
//Lidarr
@required this.lidarrEnabled,
@required this.lidarrHost,
@required this.lidarrKey,
@required this.lidarrHeaders,
//Radarr
@required this.radarrEnabled,
@required this.radarrHost,
@required this.radarrKey,
@required this.radarrHeaders,
//Sonarr
@required this.sonarrEnabled,
@required this.sonarrHost,
@required this.sonarrKey,
@required this.sonarrVersion3,
@required this.sonarrHeaders,
//SABnzbd
@required this.sabnzbdEnabled,
@required this.sabnzbdHost,
@required this.sabnzbdKey,
@required this.sabnzbdHeaders,
//NZBGet
@required this.nzbgetEnabled,
@required this.nzbgetHost,
@required this.nzbgetUser,
@required this.nzbgetPass,
@required this.nzbgetBasicAuth,
@required this.nzbgetHeaders,
//Wake On LAN
@required this.wakeOnLANEnabled,
@required this.wakeOnLANBroadcastAddress,
@required this.wakeOnLANMACAddress,
//Tautulli
@required this.tautulliEnabled,
@required this.tautulliHost,
@required this.tautulliKey,
@required this.tautulliHeaders,
//Ombi
@required this.ombiEnabled,
@required this.ombiHost,
@required this.ombiKey,
@required this.ombiHeaders,
});
@override
String toString() => toMap().toString();
Map<String, dynamic> toMap() => {
"key": key,
//Sonarr
"sonarrEnabled": sonarrEnabled,
"sonarrHost": sonarrHost,
"sonarrKey": sonarrKey,
"sonarrVersion3": sonarrVersion3,
"sonarrHeaders": sonarrHeaders,
//Radarr
"radarrEnabled": radarrEnabled,
"radarrHost": radarrHost,
"radarrKey": radarrKey,
"radarrHeaders": radarrHeaders,
//Lidarr
"lidarrEnabled": lidarrEnabled,
"lidarrHost": lidarrHost,
"lidarrKey": lidarrKey,
"lidarrHeaders": lidarrHeaders,
//SABnzbd
"sabnzbdEnabled": sabnzbdEnabled,
"sabnzbdHost": sabnzbdHost,
"sabnzbdKey": sabnzbdKey,
"sabnzbdHeaders": sabnzbdHeaders,
//NZBGet
"nzbgetEnabled": nzbgetEnabled,
"nzbgetHost": nzbgetHost,
"nzbgetUser": nzbgetUser,
"nzbgetPass": nzbgetPass,
"nzbgetBasicAuth": nzbgetBasicAuth,
"nzbgetHeaders": nzbgetHeaders,
//Wake On LAN
"wakeOnLANEnabled": wakeOnLANEnabled,
"wakeOnLANBroadcastAddress": wakeOnLANBroadcastAddress,
"wakeOnLANMACAddress": wakeOnLANMACAddress,
//Tautulli
"tautulliEnabled": tautulliEnabled,
"tautulliHost": tautulliHost,
"tautulliKey": tautulliKey,
"tautulliHeaders": tautulliHeaders,
//Ombi
"ombiEnabled": ombiEnabled,
"ombiHost": ombiHost,
"ombiKey": ombiKey,
"ombiHeaders": ombiHeaders,
};
//Lidarr
@HiveField(0)
bool lidarrEnabled;
@HiveField(1)
String lidarrHost;
@HiveField(2)
String lidarrKey;
@HiveField(26)
Map lidarrHeaders;
Map<String, dynamic> getLidarr() => {
'enabled': lidarrEnabled ?? false,
'host': lidarrHost ?? '',
'key': lidarrKey ?? '',
'headers': lidarrHeaders ?? {},
};
//Radarr
@HiveField(3)
bool radarrEnabled;
@HiveField(4)
String radarrHost;
@HiveField(5)
String radarrKey;
@HiveField(27)
Map radarrHeaders;
Map<String, dynamic> getRadarr() => {
'enabled': radarrEnabled ?? false,
'host': radarrHost ?? '',
'key': radarrKey ?? '',
'headers': radarrHeaders ?? {},
};
//Sonarr
@HiveField(6)
bool sonarrEnabled;
@HiveField(7)
String sonarrHost;
@HiveField(8)
String sonarrKey;
@HiveField(21)
bool sonarrVersion3;
@HiveField(28)
Map sonarrHeaders;
Map<String, dynamic> getSonarr() => {
'enabled': sonarrEnabled ?? false,
'host': sonarrHost ?? '',
'key': sonarrKey ?? '',
'v3': sonarrVersion3 ?? false,
'headers': sonarrHeaders ?? {},
};
//SABnzbd
@HiveField(9)
bool sabnzbdEnabled;
@HiveField(10)
String sabnzbdHost;
@HiveField(11)
String sabnzbdKey;
@HiveField(29)
Map sabnzbdHeaders;
Map<String, dynamic> getSABnzbd() => {
'enabled': sabnzbdEnabled ?? false,
'host': sabnzbdHost ?? '',
'key': sabnzbdKey ?? '',
'headers': sabnzbdHeaders ?? {},
};
//NZBGet
@HiveField(12)
bool nzbgetEnabled;
@HiveField(13)
String nzbgetHost;
@HiveField(14)
String nzbgetUser;
@HiveField(15)
String nzbgetPass;
@HiveField(22)
bool nzbgetBasicAuth;
@HiveField(30)
Map nzbgetHeaders;
Map<String, dynamic> getNZBGet() => {
'enabled': nzbgetEnabled ?? false,
'host': nzbgetHost ?? '',
'user': nzbgetUser ?? '',
'pass': nzbgetPass ?? '',
'basic_auth': nzbgetBasicAuth ?? false,
'headers': nzbgetHeaders ?? {},
};
//Wake On LAN
@HiveField(23)
bool wakeOnLANEnabled;
@HiveField(24)
String wakeOnLANBroadcastAddress;
@HiveField(25)
String wakeOnLANMACAddress;
Map<String, dynamic> getWakeOnLAN() => {
'enabled': wakeOnLANEnabled ?? false,
'broadcastAddress': wakeOnLANBroadcastAddress ?? '',
'MACAddress': wakeOnLANMACAddress ?? '',
};
//Tautulli
@HiveField(31)
bool tautulliEnabled;
@HiveField(32)
String tautulliHost;
@HiveField(33)
String tautulliKey;
@HiveField(35)
Map tautulliHeaders;
Map<String, dynamic> getTautulli() => {
'enabled': tautulliEnabled ?? false,
'host': tautulliHost ?? '',
'key': tautulliKey ?? '',
'headers': tautulliHeaders ?? {},
};
//Ombi
@HiveField(36)
bool ombiEnabled;
@HiveField(37)
String ombiHost;
@HiveField(38)
String ombiKey;
@HiveField(39)
Map ombiHeaders;
Map<String, dynamic> getOmbi() => {
'enabled': ombiEnabled ?? false,
'host': ombiHost ?? '',
'key': ombiKey ?? '',
'headers': ombiHeaders ?? {},
};
List<String> get enabledModules => [
...enabledAutomationModules,
...enabledClientModules,
...enabledMonitoringModules,
];
List<String> get enabledAutomationModules => [
if(lidarrEnabled ?? false) LidarrConstants.MODULE_KEY,
if(radarrEnabled ?? false) RadarrConstants.MODULE_KEY,
if(sonarrEnabled ?? false) SonarrConstants.MODULE_KEY,
];
List<String> get enabledClientModules => [
if(nzbgetEnabled ?? false) NZBGetConstants.MODULE_KEY,
if(sabnzbdEnabled ?? false) SABnzbdConstants.MODULE_KEY,
];
List<String> get enabledMonitoringModules => [
if(tautulliEnabled ?? false) TautulliConstants.MODULE_KEY,
if(ombiEnabled ?? false) OmbiConstants.MODULE_KEY,
];
bool get anyAutomationEnabled => enabledAutomationModules.isNotEmpty;
bool get anyClientsEnabled => enabledClientModules.isNotEmpty;
bool get anyMonitoringEnabled => enabledMonitoringModules.isNotEmpty;
bool get anythingEnabled => anyAutomationEnabled || anyClientsEnabled || anyMonitoringEnabled;
}

View File

@@ -1,2 +1,103 @@
export 'configuration/export.dart';
export 'configuration/import.dart';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:lunasea/core.dart';
import 'package:lunasea/modules/settings/core.dart' show SettingsDatabase, SettingsConstants;
import 'package:lunasea/modules/home/core.dart' show HomeDatabase, HomeConstants;
import 'package:lunasea/modules/search/core.dart' show SearchDatabase, SearchConstants;
import 'package:lunasea/modules/lidarr/core.dart' show LidarrDatabase, LidarrConstants;
import 'package:lunasea/modules/radarr/core.dart' show RadarrDatabase, RadarrConstants;
import 'package:lunasea/modules/sonarr/core.dart' show SonarrDatabase, SonarrConstants;
import 'package:lunasea/modules/nzbget/core.dart' show NZBGetDatabase, NZBGetConstants;
import 'package:lunasea/modules/sabnzbd/core.dart' show SABnzbdDatabase, SABnzbdConstants;
import 'package:lunasea/modules/ombi/core.dart' show OmbiDatabase, OmbiConstants;
import 'package:lunasea/modules/tautulli/core.dart' show TautulliDatabase, TautulliConstants;
class LunaConfiguration {
/// Returns a list of all profiles converted to a map.
List<Map<String, dynamic>> _getProfiles() {
List<Map<String, dynamic>> _data = [];
for(var key in Database.profilesBox.keys) _data.add(Database.profilesBox.get(key).toMap());
return _data;
}
/// Given a list of map objects, creates or updates profiles for each object.
void _setProfiles(List data) {
Box<dynamic> box = Database.profilesBox;
for(Map profile in data) box.put(profile['key'], ProfileHiveObject.fromMap(profile));
}
/// Returns a list of all indexers converted to a map.
List<Map<String, dynamic>> _getIndexers() {
List<Map<String, dynamic>> _data = [];
for(var key in Database.indexersBox.keys) _data.add(Database.indexersBox.get(key).toMap());
return _data;
}
/// Given a list of map objects, creates or updates indexers for each object.
void _setIndexers(List data) {
Box<dynamic> box = Database.indexersBox;
for(Map indexer in data) box.add(IndexerHiveObject.fromMap(indexer));
}
/// Import the entire configuration from a JSON-encoded string (typically read through a `.lunasea` backup file).
///
/// - Clears all boxes
/// - Calls `_setProfiles()` and `_setIndexers()`
/// - Calls `import()` on all module databases, which implement [LunaDatabase].
/// - Resets the application state
///
/// On a failed import, resets LunaSea back to the default/base state
Future<void> import(BuildContext context, String data) async {
Map config = json.decode(data);
Database.clearAllBoxes();
try {
// Set the profilers, indexer, and global LunaSea boxes
if(config['profiles'] != null) _setProfiles(config['profiles']);
if(config['indexers'] != null) _setIndexers(config['indexers']);
if(config['lunasea'] != null) LunaDatabase().import(config['lunasea']);
// General
if(config[SettingsConstants.MODULE_KEY] != null) SettingsDatabase().import(config[SettingsConstants.MODULE_KEY]);
if(config[HomeConstants.MODULE_KEY] != null) HomeDatabase().import(config[HomeConstants.MODULE_KEY]);
if(config[SearchConstants.MODULE_KEY] != null) SearchDatabase().import(config[SearchConstants.MODULE_KEY]);
// Automation
if(config[LidarrConstants.MODULE_KEY] != null) LidarrDatabase().import(config[LidarrConstants.MODULE_KEY]);
if(config[RadarrConstants.MODULE_KEY] != null) RadarrDatabase().import(config[RadarrConstants.MODULE_KEY]);
if(config[SonarrConstants.MODULE_KEY] != null) SonarrDatabase().import(config[SonarrConstants.MODULE_KEY]);
// Clients
if(config[NZBGetConstants.MODULE_KEY] != null) NZBGetDatabase().import(config[NZBGetConstants.MODULE_KEY]);
if(config[SABnzbdConstants.MODULE_KEY] != null) SABnzbdDatabase().import(config[SABnzbdConstants.MODULE_KEY]);
//Monitoring
if(config[OmbiConstants.MODULE_KEY] != null) OmbiDatabase().import(config[OmbiConstants.MODULE_KEY]);
if(config[TautulliConstants.MODULE_KEY] != null) TautulliDatabase().import(config[TautulliConstants.MODULE_KEY]);
} catch (error, stack) {
LunaLogger().error('Failed to import configuration, resetting to default', error, stack);
Database.setDefaults();
}
// Reset the entire app's state
LunaState.reset(context);
}
/// Converts the entire application configuration/database into a JSON string.
///
/// - Calls `_getProfiles()` and `_getIndexers()`
/// - Calls `export()` on all module databases, which implement [LunaModuleDatabase].
String export() => json.encode({
"profiles": _getProfiles(),
"indexers": _getIndexers(),
"lunasea": LunaDatabase().export(),
// General
SettingsConstants.MODULE_KEY: SettingsDatabase().export(),
HomeConstants.MODULE_KEY: HomeDatabase().export(),
SearchConstants.MODULE_KEY: SearchDatabase().export(),
// Automation
LidarrConstants.MODULE_KEY: LidarrDatabase().export(),
RadarrConstants.MODULE_KEY: RadarrDatabase().export(),
SonarrConstants.MODULE_KEY: SonarrDatabase().export(),
// Clients
NZBGetConstants.MODULE_KEY: NZBGetDatabase().export(),
SABnzbdConstants.MODULE_KEY: SABnzbdDatabase().export(),
// Monitoring
OmbiConstants.MODULE_KEY: OmbiDatabase().export(),
TautulliConstants.MODULE_KEY: TautulliDatabase().export(),
});
}

View File

@@ -1,51 +0,0 @@
import 'dart:convert';
import 'package:lunasea/core/database.dart';
class Export {
Export._();
static bool validateType(dynamic value) {
Type _type = value.runtimeType;
if(
_type == bool ||
_type == String ||
_type == int ||
_type == double
) return true;
return false;
}
static Map get _lunasea {
Map<String, dynamic> _data = {};
_data['profile'] = LunaSeaDatabaseValue.ENABLED_PROFILE.data;
return _data;
}
static List get _profiles {
Box<dynamic> _box = Database.profilesBox;
List _data = [];
for(var key in _box.keys) {
ProfileHiveObject profile = _box.get(key);
_data.add(profile.toMap());
}
return _data;
}
static List get _indexers {
Box<dynamic> _box = Database.indexersBox;
List _data = [];
for(var key in _box.keys) {
IndexerHiveObject indexer = _box.get(key);
_data.add(indexer.toMap());
}
return _data;
}
static String export() {
return json.encode({
"lunasea": _lunasea,
"profiles": _profiles,
"indexers": _indexers,
});
}
}

View File

@@ -1,105 +0,0 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:lunasea/core.dart';
import 'package:lunasea/core/database.dart';
class Import {
Import._();
static void _setLunaSea(BuildContext context, Map data) {
String _name = data['lunasea']['profile'];
Map _profile = (data['profiles'] as List).firstWhere((element) => element['key'] == _name, orElse: () => null);
if(_profile == null) _name = (data['profiles'] as List)[0]['key'];
LunaSeaDatabaseValue.ENABLED_PROFILE.put(_name);
}
static void _setProfiles(List data) {
Box<dynamic> _box = Database.profilesBox;
for(Map profile in data) {
_box.put(profile['key'], ProfileHiveObject(
//Sonarr
sonarrEnabled: profile['sonarrEnabled'] ?? false,
sonarrHost: profile['sonarrHost'] ?? '',
sonarrKey: profile['sonarrKey'] ?? '',
sonarrVersion3: profile['sonarrVersion3'] ?? false,
sonarrHeaders: profile['sonarrHeaders'] ?? {},
//Radarr
radarrEnabled: profile['radarrEnabled'] ?? false,
radarrHost: profile['radarrHost'] ?? '',
radarrKey: profile['radarrKey'] ?? '',
radarrHeaders: profile['radarrHeaders'] ?? {},
//Lidarr
lidarrEnabled: profile['lidarrEnabled'] ?? false,
lidarrHost: profile['lidarrHost'] ?? '',
lidarrKey: profile['lidarrKey'] ?? '',
lidarrHeaders: profile['lidarrHeaders'] ?? {},
//SABnzbd
sabnzbdEnabled: profile['sabnzbdEnabled'] ?? false,
sabnzbdHost: profile['sabnzbdHost'] ?? '',
sabnzbdKey: profile['sabnzbdKey'] ?? '',
sabnzbdHeaders: profile['sabnzbdHeaders'] ?? {},
//NZBGet
nzbgetEnabled: profile['nzbgetEnabled'] ?? false,
nzbgetHost: profile['nzbgetHost'] ?? '',
nzbgetUser: profile['nzbgetUser'] ?? '',
nzbgetPass: profile['nzbgetPass'] ?? '',
nzbgetBasicAuth: profile['nzbgetBasicAuth'] ?? false,
nzbgetHeaders: profile['nzbgetHeaders'] ?? {},
//Wake on LAN
wakeOnLANEnabled: profile['wakeOnLANEnabled'] ?? false,
wakeOnLANBroadcastAddress: profile['wakeOnLANBroadcastAddress'] ?? '',
wakeOnLANMACAddress: profile['wakeOnLANMACAddress'] ?? '',
//Tautulli
tautulliEnabled: profile['tautulliEnabled'] ?? false,
tautulliHost: profile['tautulliHost'] ?? '',
tautulliKey: profile['tautulliKey'] ?? '',
tautulliHeaders: profile['tautulliHeaders'] ?? {},
//Ombi
ombiEnabled: profile['ombiEnabled'] ?? false,
ombiHost: profile['ombiHost'] ?? '',
ombiKey: profile['ombiKey'] ?? '',
ombiHeaders: profile['ombiHeaders'] ?? {},
));
}
}
static void _setIndexers(List data) {
Box<dynamic> _box = Database.indexersBox;
for(Map indexer in data) {
_box.add(IndexerHiveObject(
displayName: indexer['displayName'] ?? '',
host: indexer['host'] ?? '',
key: indexer['key'] ?? '',
headers: indexer['headers'] ?? {},
));
}
}
static bool _validate(Map config) {
if(
config['profiles'] != null &&
config['indexers'] != null &&
config['lunasea'] != null
) return true;
return false;
}
static void _clearBoxes() {
Database.clearIndexersBox();
Database.clearLunaSeaBox();
Database.clearProfilesBox();
}
static Future<bool> import(BuildContext context, String data) async {
Map _config = json.decode(data);
if(_validate(_config)) {
_clearBoxes();
_setProfiles(_config['profiles']);
_setIndexers(_config['indexers']);
_setLunaSea(context, _config);
LunaProvider.reset(context);
return true;
}
return false;
}
}

View File

@@ -1,32 +1,29 @@
import 'package:flutter/material.dart';
import 'package:lunasea/core/module_map.dart';
import 'package:lunasea/modules.dart' show
LidarrConstants,
RadarrConstants,
SonarrConstants,
NZBGetConstants,
SABnzbdConstants,
SearchConstants,
SettingsConstants,
WakeOnLANConstants,
TautulliConstants;
import 'package:lunasea/core.dart';
import 'package:lunasea/modules/lidarr/core.dart' show LidarrConstants;
import 'package:lunasea/modules/radarr/core.dart' show RadarrConstants;
import 'package:lunasea/modules/sonarr/core.dart' show SonarrConstants;
import 'package:lunasea/modules/nzbget/core.dart' show NZBGetConstants;
import 'package:lunasea/modules/sabnzbd/core.dart' show SABnzbdConstants;
import 'package:lunasea/modules/search/core.dart' show SearchConstants;
import 'package:lunasea/modules/settings/core.dart' show SettingsConstants;
import 'package:lunasea/modules/wake_on_lan/core.dart' show WakeOnLANConstants;
import 'package:lunasea/modules/tautulli/core.dart' show TautulliConstants;
class Constants {
Constants._();
static const APPLICATION_NAME = 'LunaSea';
static const SENTRY_DSN = 'https://511f76efcf714ecfb5ed6b26b5819bd6@o426090.ingest.sentry.io/5367513';
static const Map<String, LunaModuleMap> MODULE_MAP = {
LidarrConstants.MODULE_KEY: LidarrConstants.MODULE_MAP,
RadarrConstants.MODULE_KEY: RadarrConstants.MODULE_MAP,
SonarrConstants.MODULE_KEY: SonarrConstants.MODULE_MAP,
NZBGetConstants.MODULE_KEY: NZBGetConstants.MODULE_MAP,
SABnzbdConstants.MODULE_KEY: SABnzbdConstants.MODULE_MAP,
SearchConstants.MODULE_KEY: SearchConstants.MODULE_MAP,
SettingsConstants.MODULE_KEY: SettingsConstants.MODULE_MAP,
WakeOnLANConstants.MODULE_KEY: WakeOnLANConstants.MODULE_MAP,
TautulliConstants.MODULE_KEY: TautulliConstants.MODULE_MAP,
static const Map<String, LunaModuleMetadata> MODULE_METADATA = {
LidarrConstants.MODULE_KEY: LidarrConstants.MODULE_METADATA,
RadarrConstants.MODULE_KEY: RadarrConstants.MODULE_METADATA,
SonarrConstants.MODULE_KEY: SonarrConstants.MODULE_METADATA,
NZBGetConstants.MODULE_KEY: NZBGetConstants.MODULE_METADATA,
SABnzbdConstants.MODULE_KEY: SABnzbdConstants.MODULE_METADATA,
SearchConstants.MODULE_KEY: SearchConstants.MODULE_METADATA,
SettingsConstants.MODULE_KEY: SettingsConstants.MODULE_METADATA,
WakeOnLANConstants.MODULE_KEY: WakeOnLANConstants.MODULE_METADATA,
TautulliConstants.MODULE_KEY: TautulliConstants.MODULE_METADATA,
};
static const EMPTY_MAP = {};
@@ -51,21 +48,14 @@ class Constants {
static const UI_FONT_SIZE_SUBTITLE = 13.0;
static const UI_FONT_SIZE_GRAPH_LEGEND = 10.0;
static const CONFIGURATION_INVALID = '<<INVALID_CONFIGURATION>>';
static const ENCRYPTION_FAILURE = '<<INVALID_ENCRYPTION>>';
static const NO_SERVICES_ENABLED = '<<NO_SERVICES_ENABLED>>';
static const CHECK_LOGS_MESSAGE = 'Check the logs for more details';
static const BIT_SIZES = ['b', 'Kb', 'Mb', 'Gb', 'Tb', 'Pb', 'Eb'];
static const BYTE_SIZES = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'];
static const URL_DOCUMENTATION = 'https://docs.lunasea.app';
static const URL_GITHUB = 'https://github.com/JagandeepBrar/LunaSea';
static const URL_GITHUB = 'https://github.com/CometTools/LunaSea';
static const URL_REDDIT = 'https://www.reddit.com/r/LunaSeaApp';
static const URL_WEBSITE = 'https://www.lunasea.app';
static const URL_FEEDBACK = 'https://feedback.lunasea.app';
static const URL_LICENSES = 'https://docs.lunasea.app/other-resources/licenses';
static const URL_CHANGELOG = 'https://docs.lunasea.app/development/changelog';
static const URL_CHANGELOG = 'https://github.com/CometTools/LunaSea/blob/master/CHANGELOG.md';
static const URL_DISCORD = 'https://discord.com/invite/8MH2N3h';
static const URL_SENTRY = 'https://sentry.io';
static const URL_TESTFLIGHT = 'https://testflight.apple.com/join/WWXaybra';

View File

@@ -1,2 +1,3 @@
export 'database/database.dart';
export 'database/adapters.dart';
export 'database/luna_database.dart';
export 'database/module_database.dart';

View File

@@ -1,55 +0,0 @@
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
part 'indexer.g.dart';
@HiveType(typeId: 1, adapterName: 'IndexerHiveObjectAdapter')
class IndexerHiveObject extends HiveObject {
factory IndexerHiveObject.empty() {
return IndexerHiveObject(
displayName: '',
host: '',
key: '',
headers: {},
);
}
factory IndexerHiveObject.from(IndexerHiveObject obj) {
return IndexerHiveObject(
displayName: obj.displayName,
host: obj.host,
key: obj.key,
headers: obj.headers,
);
}
IndexerHiveObject({
@required this.displayName,
@required this.host,
@required this.key,
@required this.headers,
});
@override
String toString() {
return toMap().toString();
}
Map<String, dynamic> toMap() {
return {
"displayName": displayName ?? '',
"host": host ?? '',
"key": key ?? '',
"headers": headers ?? {},
};
}
@HiveField(0)
String displayName;
@HiveField(1)
String host;
@HiveField(2)
String key;
@HiveField(3)
Map headers;
}

View File

@@ -1,87 +0,0 @@
import 'package:lunasea/core/database/database.dart';
import 'package:lunasea/core/extensions.dart';
class LunaSeaDatabase {
LunaSeaDatabase._();
static void registerAdapters() {
Hive.registerAdapter(LSBrowsersAdapter());
}
}
enum LunaSeaDatabaseValue {
ENABLED_PROFILE,
ENABLED_SENTRY,
CLIENT_IDENTIFIER,
THEME_AMOLED,
THEME_AMOLED_BORDER,
THEME_IMAGE_BACKGROUND_OPACITY,
SELECTED_BROWSER,
DRAWER_GROUP_MODULES,
DRAWER_EXPAND_AUTOMATION,
DRAWER_EXPAND_CLIENTS,
DRAWER_EXPAND_MONITORING,
QUICK_ACTIONS_LIDARR,
QUICK_ACTIONS_RADARR,
QUICK_ACTIONS_SONARR,
QUICK_ACTIONS_NZBGET,
QUICK_ACTIONS_SABNZBD,
QUICK_ACTIONS_TAUTULLI,
QUICK_ACTIONS_SEARCH,
USE_24_HOUR_TIME,
}
extension LunaSeaDatabaseValueExtension on LunaSeaDatabaseValue {
String get key {
switch(this) {
case LunaSeaDatabaseValue.ENABLED_PROFILE: return 'profile';
case LunaSeaDatabaseValue.ENABLED_SENTRY: return 'LUNASEA_ENABLED_SENTRY';
case LunaSeaDatabaseValue.THEME_AMOLED: return 'LUNASEA_THEME_AMOLED';
case LunaSeaDatabaseValue.THEME_AMOLED_BORDER: return 'LUNASEA_THEME_AMOLED_BORDER';
case LunaSeaDatabaseValue.THEME_IMAGE_BACKGROUND_OPACITY: return 'LUNASEA_THEME_IMAGE_BACKGROUND_OPACITY';
case LunaSeaDatabaseValue.SELECTED_BROWSER: return 'LUNASEA_SELECTED_BROWSER';
case LunaSeaDatabaseValue.CLIENT_IDENTIFIER: return 'LUNASEA_CLIENT_IDENTIFIER';
case LunaSeaDatabaseValue.DRAWER_GROUP_MODULES: return 'LUNASEA_DRAWER_GROUP_MODULES';
case LunaSeaDatabaseValue.DRAWER_EXPAND_AUTOMATION: return 'LUNASEA_DRAWER_EXPAND_AUTOMATION';
case LunaSeaDatabaseValue.DRAWER_EXPAND_CLIENTS: return 'LUNASEA_DRAWER_EXPAND_CLIENTS';
case LunaSeaDatabaseValue.DRAWER_EXPAND_MONITORING: return 'LUNASEA_DRAWER_EXPAND_MONITORING';
case LunaSeaDatabaseValue.QUICK_ACTIONS_LIDARR: return 'LUNASEA_QUICK_ACTIONS_LIDARR';
case LunaSeaDatabaseValue.QUICK_ACTIONS_RADARR: return 'LUNASEA_QUICK_ACTIONS_RADARR';
case LunaSeaDatabaseValue.QUICK_ACTIONS_SONARR: return 'LUNASEA_QUICK_ACTIONS_SONARR';
case LunaSeaDatabaseValue.QUICK_ACTIONS_NZBGET: return 'LUNASEA_QUICK_ACTIONS_NZBGET';
case LunaSeaDatabaseValue.QUICK_ACTIONS_SABNZBD: return 'LUNASEA_QUICK_ACTIONS_SABNZBD';
case LunaSeaDatabaseValue.QUICK_ACTIONS_TAUTULLI: return 'LUNASEA_QUICK_ACTIONS_TAUTULLI';
case LunaSeaDatabaseValue.QUICK_ACTIONS_SEARCH: return 'LUNASEA_QUICK_ACTIONS_SEARCH';
case LunaSeaDatabaseValue.USE_24_HOUR_TIME: return 'LUNASEA_USE_24_HOUR_TIME';
}
throw Exception('key not found');
}
dynamic get data {
final _box = Database.lunaSeaBox;
switch(this) {
case LunaSeaDatabaseValue.ENABLED_PROFILE: return _box.get(this.key, defaultValue: 'default');
case LunaSeaDatabaseValue.SELECTED_BROWSER: return _box.get(this.key, defaultValue: LSBrowsers.APPLE_SAFARI);
case LunaSeaDatabaseValue.THEME_AMOLED: return _box.get(this.key, defaultValue: false);
case LunaSeaDatabaseValue.THEME_AMOLED_BORDER: return _box.get(this.key, defaultValue: false);
case LunaSeaDatabaseValue.THEME_IMAGE_BACKGROUND_OPACITY: return _box.get(this.key, defaultValue: 10);
case LunaSeaDatabaseValue.ENABLED_SENTRY: return _box.get(this.key, defaultValue: true);
case LunaSeaDatabaseValue.CLIENT_IDENTIFIER: return _box.get(this.key, defaultValue: null);
case LunaSeaDatabaseValue.DRAWER_GROUP_MODULES: return _box.get(this.key, defaultValue: false);
case LunaSeaDatabaseValue.DRAWER_EXPAND_AUTOMATION: return _box.get(this.key, defaultValue: true);
case LunaSeaDatabaseValue.DRAWER_EXPAND_CLIENTS: return _box.get(this.key, defaultValue: true);
case LunaSeaDatabaseValue.DRAWER_EXPAND_MONITORING: return _box.get(this.key, defaultValue: true);
case LunaSeaDatabaseValue.QUICK_ACTIONS_LIDARR: return _box.get(this.key, defaultValue: false);
case LunaSeaDatabaseValue.QUICK_ACTIONS_RADARR: return _box.get(this.key, defaultValue: false);
case LunaSeaDatabaseValue.QUICK_ACTIONS_SONARR: return _box.get(this.key, defaultValue: false);
case LunaSeaDatabaseValue.QUICK_ACTIONS_NZBGET: return _box.get(this.key, defaultValue: false);
case LunaSeaDatabaseValue.QUICK_ACTIONS_SABNZBD: return _box.get(this.key, defaultValue: false);
case LunaSeaDatabaseValue.QUICK_ACTIONS_TAUTULLI: return _box.get(this.key, defaultValue: false);
case LunaSeaDatabaseValue.QUICK_ACTIONS_SEARCH: return _box.get(this.key, defaultValue: false);
case LunaSeaDatabaseValue.USE_24_HOUR_TIME: return _box.get(this.key, defaultValue: false);
}
throw Exception('data not found');
}
void put(dynamic value) => Database.lunaSeaBox.put(this.key, value);
}

View File

@@ -1,376 +0,0 @@
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:lunasea/core.dart';
import 'package:lunasea/modules.dart';
part 'profile.g.dart';
//Next HiveField ID: 40
/**
* Dead Fields
* 16, 17, 18, 19, 20, 34
*/
/// Hive database object containing all profile fields
@HiveType(typeId: 0, adapterName: 'ProfileHiveObjectAdapter')
class ProfileHiveObject extends HiveObject {
factory ProfileHiveObject.empty() {
return ProfileHiveObject(
//Lidarr
lidarrEnabled: false,
lidarrHost: '',
lidarrKey: '',
lidarrHeaders: {},
//Radarr
radarrEnabled: false,
radarrHost: '',
radarrKey: '',
radarrHeaders: {},
//Sonarr
sonarrEnabled: false,
sonarrHost: '',
sonarrKey: '',
sonarrVersion3: false,
sonarrHeaders: {},
//SABnzbd
sabnzbdEnabled: false,
sabnzbdHost: '',
sabnzbdKey: '',
sabnzbdHeaders: {},
//NZBGet
nzbgetEnabled: false,
nzbgetHost: '',
nzbgetUser: '',
nzbgetPass: '',
nzbgetBasicAuth: false,
nzbgetHeaders: {},
//Wake on LAN
wakeOnLANEnabled: false,
wakeOnLANBroadcastAddress: '',
wakeOnLANMACAddress: '',
//Tautulli
tautulliEnabled: false,
tautulliHost: '',
tautulliKey: '',
tautulliHeaders: {},
//Ombi
ombiEnabled: false,
ombiHost: '',
ombiKey: '',
ombiHeaders: {},
);
}
factory ProfileHiveObject.from(ProfileHiveObject obj) {
return ProfileHiveObject(
//Lidarr
lidarrEnabled: obj.lidarrEnabled,
lidarrHost: obj.lidarrHost,
lidarrKey: obj.lidarrKey,
lidarrHeaders: obj.lidarrHeaders,
//Radarr
radarrEnabled: obj.radarrEnabled,
radarrHost: obj.radarrHost,
radarrKey: obj.radarrKey,
radarrHeaders: obj.radarrHeaders,
//Sonarr
sonarrEnabled: obj.sonarrEnabled,
sonarrHost: obj.sonarrHost,
sonarrKey: obj.sonarrKey,
sonarrVersion3: obj.sonarrVersion3,
sonarrHeaders: obj.sonarrHeaders,
//SABnzbd
sabnzbdEnabled: obj.sabnzbdEnabled,
sabnzbdHost: obj.sabnzbdHost,
sabnzbdKey: obj.sabnzbdKey,
sabnzbdHeaders: obj.sabnzbdHeaders,
//NZBGet
nzbgetEnabled: obj.nzbgetEnabled,
nzbgetHost: obj.nzbgetHost,
nzbgetUser: obj.nzbgetUser,
nzbgetPass: obj.nzbgetPass,
nzbgetBasicAuth: obj.nzbgetBasicAuth,
nzbgetHeaders: obj.nzbgetHeaders,
//Wake On LAN
wakeOnLANEnabled: obj.wakeOnLANEnabled,
wakeOnLANBroadcastAddress: obj.wakeOnLANBroadcastAddress,
wakeOnLANMACAddress: obj.wakeOnLANMACAddress,
//Tautulli
tautulliEnabled: obj.tautulliEnabled,
tautulliHost: obj.tautulliHost,
tautulliKey: obj.tautulliKey,
tautulliHeaders: obj.tautulliHeaders,
//Ombi
ombiEnabled: obj.ombiEnabled,
ombiHost: obj.ombiHost,
ombiKey: obj.ombiKey,
ombiHeaders: obj.ombiHeaders,
);
}
ProfileHiveObject({
//Lidarr
@required this.lidarrEnabled,
@required this.lidarrHost,
@required this.lidarrKey,
@required this.lidarrHeaders,
//Radarr
@required this.radarrEnabled,
@required this.radarrHost,
@required this.radarrKey,
@required this.radarrHeaders,
//Sonarr
@required this.sonarrEnabled,
@required this.sonarrHost,
@required this.sonarrKey,
@required this.sonarrVersion3,
@required this.sonarrHeaders,
//SABnzbd
@required this.sabnzbdEnabled,
@required this.sabnzbdHost,
@required this.sabnzbdKey,
@required this.sabnzbdHeaders,
//NZBGet
@required this.nzbgetEnabled,
@required this.nzbgetHost,
@required this.nzbgetUser,
@required this.nzbgetPass,
@required this.nzbgetBasicAuth,
@required this.nzbgetHeaders,
//Wake On LAN
@required this.wakeOnLANEnabled,
@required this.wakeOnLANBroadcastAddress,
@required this.wakeOnLANMACAddress,
//Tautulli
@required this.tautulliEnabled,
@required this.tautulliHost,
@required this.tautulliKey,
@required this.tautulliHeaders,
//Ombi
@required this.ombiEnabled,
@required this.ombiHost,
@required this.ombiKey,
@required this.ombiHeaders,
});
@override
String toString() => toMap().toString();
Map<String, dynamic> toMap() {
return {
"key": key,
//Sonarr
"sonarrEnabled": sonarrEnabled,
"sonarrHost": sonarrHost,
"sonarrKey": sonarrKey,
"sonarrVersion3": sonarrVersion3,
"sonarrHeaders": sonarrHeaders,
//Radarr
"radarrEnabled": radarrEnabled,
"radarrHost": radarrHost,
"radarrKey": radarrKey,
"radarrHeaders": radarrHeaders,
//Lidarr
"lidarrEnabled": lidarrEnabled,
"lidarrHost": lidarrHost,
"lidarrKey": lidarrKey,
"lidarrHeaders": lidarrHeaders,
//SABnzbd
"sabnzbdEnabled": sabnzbdEnabled,
"sabnzbdHost": sabnzbdHost,
"sabnzbdKey": sabnzbdKey,
"sabnzbdHeaders": sabnzbdHeaders,
//NZBGet
"nzbgetEnabled": nzbgetEnabled,
"nzbgetHost": nzbgetHost,
"nzbgetUser": nzbgetUser,
"nzbgetPass": nzbgetPass,
"nzbgetBasicAuth": nzbgetBasicAuth,
"nzbgetHeaders": nzbgetHeaders,
//Wake On LAN
"wakeOnLANEnabled": wakeOnLANEnabled,
"wakeOnLANBroadcastAddress": wakeOnLANBroadcastAddress,
"wakeOnLANMACAddress": wakeOnLANMACAddress,
//Tautulli
"tautulliEnabled": tautulliEnabled,
"tautulliHost": tautulliHost,
"tautulliKey": tautulliKey,
"tautulliHeaders": tautulliHeaders,
//Ombi
"ombiEnabled": ombiEnabled,
"ombiHost": ombiHost,
"ombiKey": ombiKey,
"ombiHeaders": ombiHeaders,
};
}
//Lidarr
@HiveField(0)
bool lidarrEnabled;
@HiveField(1)
String lidarrHost;
@HiveField(2)
String lidarrKey;
@HiveField(26)
Map lidarrHeaders;
Map<String, dynamic> getLidarr() => {
'enabled': lidarrEnabled ?? false,
'host': lidarrHost ?? '',
'key': lidarrKey ?? '',
'headers': lidarrHeaders ?? {},
};
//Radarr
@HiveField(3)
bool radarrEnabled;
@HiveField(4)
String radarrHost;
@HiveField(5)
String radarrKey;
@HiveField(27)
Map radarrHeaders;
Map<String, dynamic> getRadarr() => {
'enabled': radarrEnabled ?? false,
'host': radarrHost ?? '',
'key': radarrKey ?? '',
'headers': radarrHeaders ?? {},
};
//Sonarr
@HiveField(6)
bool sonarrEnabled;
@HiveField(7)
String sonarrHost;
@HiveField(8)
String sonarrKey;
@HiveField(21)
bool sonarrVersion3;
@HiveField(28)
Map sonarrHeaders;
Map<String, dynamic> getSonarr() => {
'enabled': sonarrEnabled ?? false,
'host': sonarrHost ?? '',
'key': sonarrKey ?? '',
'v3': sonarrVersion3 ?? false,
'headers': sonarrHeaders ?? {},
};
//SABnzbd
@HiveField(9)
bool sabnzbdEnabled;
@HiveField(10)
String sabnzbdHost;
@HiveField(11)
String sabnzbdKey;
@HiveField(29)
Map sabnzbdHeaders;
Map<String, dynamic> getSABnzbd() => {
'enabled': sabnzbdEnabled ?? false,
'host': sabnzbdHost ?? '',
'key': sabnzbdKey ?? '',
'headers': sabnzbdHeaders ?? {},
};
//NZBGet
@HiveField(12)
bool nzbgetEnabled;
@HiveField(13)
String nzbgetHost;
@HiveField(14)
String nzbgetUser;
@HiveField(15)
String nzbgetPass;
@HiveField(22)
bool nzbgetBasicAuth;
@HiveField(30)
Map nzbgetHeaders;
Map<String, dynamic> getNZBGet() => {
'enabled': nzbgetEnabled ?? false,
'host': nzbgetHost ?? '',
'user': nzbgetUser ?? '',
'pass': nzbgetPass ?? '',
'basic_auth': nzbgetBasicAuth ?? false,
'headers': nzbgetHeaders ?? {},
};
//Wake On LAN
@HiveField(23)
bool wakeOnLANEnabled;
@HiveField(24)
String wakeOnLANBroadcastAddress;
@HiveField(25)
String wakeOnLANMACAddress;
Map<String, dynamic> getWakeOnLAN() => {
'enabled': wakeOnLANEnabled ?? false,
'broadcastAddress': wakeOnLANBroadcastAddress ?? '',
'MACAddress': wakeOnLANMACAddress ?? '',
};
//Tautulli
@HiveField(31)
bool tautulliEnabled;
@HiveField(32)
String tautulliHost;
@HiveField(33)
String tautulliKey;
@HiveField(35)
Map tautulliHeaders;
Map<String, dynamic> getTautulli() => {
'enabled': tautulliEnabled ?? false,
'host': tautulliHost ?? '',
'key': tautulliKey ?? '',
'headers': tautulliHeaders ?? {},
};
//Ombi
@HiveField(36)
bool ombiEnabled;
@HiveField(37)
String ombiHost;
@HiveField(38)
String ombiKey;
@HiveField(39)
Map ombiHeaders;
Map<String, dynamic> getOmbi() => {
'enabled': ombiEnabled ?? false,
'host': ombiHost ?? '',
'key': ombiKey ?? '',
'headers': ombiHeaders ?? {},
};
List<String> get enabledModules => [
...enabledAutomationModules,
...enabledClientModules,
...enabledMonitoringModules,
];
List<String> get enabledAutomationModules => [
if(lidarrEnabled ?? false) LidarrConstants.MODULE_KEY,
if(radarrEnabled ?? false) RadarrConstants.MODULE_KEY,
if(sonarrEnabled ?? false) SonarrConstants.MODULE_KEY,
];
List<String> get enabledClientModules => [
if(nzbgetEnabled ?? false) NZBGetConstants.MODULE_KEY,
if(sabnzbdEnabled ?? false) SABnzbdConstants.MODULE_KEY,
];
List<String> get enabledMonitoringModules => [
if(tautulliEnabled ?? false) TautulliConstants.MODULE_KEY,
if(ombiEnabled ?? false) OmbiConstants.MODULE_KEY,
];
bool get anyAutomationEnabled => enabledAutomationModules.isNotEmpty;
bool get anyClientsEnabled => enabledClientModules.isNotEmpty;
bool get anyMonitoringEnabled => enabledMonitoringModules.isNotEmpty;
bool get anythingEnabled => anyAutomationEnabled || anyClientsEnabled || anyMonitoringEnabled;
}

View File

@@ -1,68 +1,76 @@
// Imports
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
import './adapters.dart';
import 'package:lunasea/modules.dart' show
HomeDatabase,
SearchDatabase,
SettingsDatabase,
LidarrDatabase,
RadarrDatabase,
SonarrDatabase,
NZBGetDatabase,
SABnzbdDatabase;
import 'package:lunasea/core.dart';
import 'package:lunasea/modules/home/core.dart' show HomeDatabase;
import 'package:lunasea/modules/search/core.dart' show SearchDatabase;
import 'package:lunasea/modules/settings/core.dart' show SettingsDatabase;
import 'package:lunasea/modules/lidarr/core.dart' show LidarrDatabase;
import 'package:lunasea/modules/radarr/core.dart' show RadarrDatabase;
import 'package:lunasea/modules/sonarr/core.dart' show SonarrDatabase;
import 'package:lunasea/modules/nzbget/core.dart' show NZBGetDatabase;
import 'package:lunasea/modules/sabnzbd/core.dart' show SABnzbdDatabase;
import 'package:lunasea/modules/ombi/core.dart' show OmbiDatabase;
import 'package:lunasea/modules/tautulli/core.dart' show TautulliDatabase;
// Exports
export 'package:hive/hive.dart';
export 'package:hive_flutter/hive_flutter.dart';
/// Next HiveType: 16
///
/// Dead Fields:
class Database {
Database._();
class Database {
static const String _DATABASE_PATH = 'database';
/// Initialize the database.
///
/// - Initializes Hive database path
/// - Register all adapters
/// - Open all boxes
/// - Set default database state of necessary
static Future<void> initialize() async {
await Hive.initFlutter('database');
await Hive.initFlutter(_DATABASE_PATH);
_registerAdapters();
await _openBoxes();
if(profilesBox.keys.length == 0) setDefaults();
}
/// Deinitialize the database by closing all open hive boxes.
static Future<void> deinitialize() async => await Hive.close();
/// Registers all necessary object adapters for Hive.
static void _registerAdapters() {
//Core
Hive.registerAdapter(IndexerHiveObjectAdapter());
Hive.registerAdapter(ProfileHiveObjectAdapter());
//General
LunaSeaDatabase.registerAdapters();
HomeDatabase.registerAdapters();
SearchDatabase.registerAdapters();
SettingsDatabase.registerAdapters();
LunaDatabase().registerAdapters();
HomeDatabase().registerAdapters();
SearchDatabase().registerAdapters();
SettingsDatabase().registerAdapters();
//Automation
LidarrDatabase.registerAdapters();
RadarrDatabase.registerAdapters();
SonarrDatabase.registerAdapters();
LidarrDatabase().registerAdapters();
RadarrDatabase().registerAdapters();
SonarrDatabase().registerAdapters();
//Clients
NZBGetDatabase.registerAdapters();
SABnzbdDatabase.registerAdapters();
NZBGetDatabase().registerAdapters();
SABnzbdDatabase().registerAdapters();
//Monitoring
OmbiDatabase().registerAdapters();
TautulliDatabase().registerAdapters();
}
/// Open all Hive boxes for reading.
static Future<void> _openBoxes() async {
await Hive.openBox('lunasea');
await Hive.openBox<IndexerHiveObject>('indexers');
await Hive.openBox<ProfileHiveObject>('profiles');
}
/// Set the default state for all hive boxes.
static void setDefaults() {
//Clear all the boxes
clearLunaSeaBox();
clearProfilesBox();
clearIndexersBox();
clearAllBoxes();
//Set default profile & enabled profile
profilesBox.put('default', ProfileHiveObject.empty());
lunaSeaBox.put(LunaSeaDatabaseValue.ENABLED_PROFILE.key, 'default');
lunaSeaBox.put(LunaDatabaseValue.ENABLED_PROFILE.key, 'default');
}
//Get boxes
@@ -74,6 +82,11 @@ class Database {
static void clearLunaSeaBox() => lunaSeaBox.deleteAll(lunaSeaBox.keys);
static void clearProfilesBox() => profilesBox.deleteAll(profilesBox.keys);
static void clearIndexersBox() => indexersBox.deleteAll(indexersBox.keys);
static void clearAllBoxes() {
clearLunaSeaBox();
clearProfilesBox();
clearIndexersBox();
}
//Profile values
static String get currentProfile => lunaSeaBox.get('profile');

View File

@@ -0,0 +1,194 @@
import 'package:flutter/material.dart';
import 'package:lunasea/core.dart';
class LunaDatabase extends LunaModuleDatabase {
@override
void registerAdapters() {
Hive.registerAdapter(LunaBrowserAdapter());
}
@override
Map<String, dynamic> export() {
Map<String, dynamic> data = {};
for(LunaDatabaseValue value in LunaDatabaseValue.values) {
switch(value) {
// Non-primitive values
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.THEME_AMOLED:
case LunaDatabaseValue.THEME_AMOLED_BORDER:
case LunaDatabaseValue.THEME_IMAGE_BACKGROUND_OPACITY:
case LunaDatabaseValue.DRAWER_GROUP_MODULES:
case LunaDatabaseValue.DRAWER_EXPAND_AUTOMATION:
case LunaDatabaseValue.DRAWER_EXPAND_CLIENTS:
case LunaDatabaseValue.DRAWER_EXPAND_MONITORING:
case LunaDatabaseValue.QUICK_ACTIONS_LIDARR:
case LunaDatabaseValue.QUICK_ACTIONS_RADARR:
case LunaDatabaseValue.QUICK_ACTIONS_SONARR:
case LunaDatabaseValue.QUICK_ACTIONS_NZBGET:
case LunaDatabaseValue.QUICK_ACTIONS_SABNZBD:
case LunaDatabaseValue.QUICK_ACTIONS_TAUTULLI:
case LunaDatabaseValue.QUICK_ACTIONS_SEARCH:
case LunaDatabaseValue.USE_24_HOUR_TIME: data[value.key] = value.data; break;
}
}
return data;
}
@override
void import(Map<String, dynamic> config) {
for(String key in config.keys) {
LunaDatabaseValue value = valueFromKey(key);
if(value != null) switch(value) {
// Non-primitive values
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.THEME_AMOLED:
case LunaDatabaseValue.THEME_AMOLED_BORDER:
case LunaDatabaseValue.THEME_IMAGE_BACKGROUND_OPACITY:
case LunaDatabaseValue.DRAWER_GROUP_MODULES:
case LunaDatabaseValue.DRAWER_EXPAND_AUTOMATION:
case LunaDatabaseValue.DRAWER_EXPAND_CLIENTS:
case LunaDatabaseValue.DRAWER_EXPAND_MONITORING:
case LunaDatabaseValue.QUICK_ACTIONS_LIDARR:
case LunaDatabaseValue.QUICK_ACTIONS_RADARR:
case LunaDatabaseValue.QUICK_ACTIONS_SONARR:
case LunaDatabaseValue.QUICK_ACTIONS_NZBGET:
case LunaDatabaseValue.QUICK_ACTIONS_SABNZBD:
case LunaDatabaseValue.QUICK_ACTIONS_TAUTULLI:
case LunaDatabaseValue.QUICK_ACTIONS_SEARCH:
case LunaDatabaseValue.USE_24_HOUR_TIME: value.put(config[key]); break;
}
}
}
LunaDatabaseValue valueFromKey(String key) {
switch(key) {
case 'profile': return LunaDatabaseValue.ENABLED_PROFILE;
case 'LUNASEA_ENABLED_SENTRY': return LunaDatabaseValue.ENABLED_SENTRY;
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;
case 'LUNASEA_SELECTED_BROWSER': return LunaDatabaseValue.SELECTED_BROWSER;
case 'LUNASEA_DRAWER_GROUP_MODULES': return LunaDatabaseValue.DRAWER_GROUP_MODULES;
case 'LUNASEA_DRAWER_EXPAND_AUTOMATION': return LunaDatabaseValue.DRAWER_EXPAND_AUTOMATION;
case 'LUNASEA_DRAWER_EXPAND_CLIENTS': return LunaDatabaseValue.DRAWER_EXPAND_CLIENTS;
case 'LUNASEA_DRAWER_EXPAND_MONITORING': return LunaDatabaseValue.DRAWER_EXPAND_MONITORING;
case 'LUNASEA_QUICK_ACTIONS_LIDARR': return LunaDatabaseValue.QUICK_ACTIONS_LIDARR;
case 'LUNASEA_QUICK_ACTIONS_RADARR': return LunaDatabaseValue.QUICK_ACTIONS_RADARR;
case 'LUNASEA_QUICK_ACTIONS_SONARR': return LunaDatabaseValue.QUICK_ACTIONS_SONARR;
case 'LUNASEA_QUICK_ACTIONS_NZBGET': return LunaDatabaseValue.QUICK_ACTIONS_NZBGET;
case 'LUNASEA_QUICK_ACTIONS_SABNZBD': return LunaDatabaseValue.QUICK_ACTIONS_SABNZBD;
case 'LUNASEA_QUICK_ACTIONS_TAUTULLI': return LunaDatabaseValue.QUICK_ACTIONS_TAUTULLI;
case 'LUNASEA_QUICK_ACTIONS_SEARCH': return LunaDatabaseValue.QUICK_ACTIONS_SEARCH;
case 'LUNASEA_USE_24_HOUR_TIME': return LunaDatabaseValue.USE_24_HOUR_TIME;
default: return null;
}
}
}
enum LunaDatabaseValue {
ENABLED_PROFILE,
ENABLED_SENTRY,
THEME_AMOLED,
THEME_AMOLED_BORDER,
THEME_IMAGE_BACKGROUND_OPACITY,
SELECTED_BROWSER,
DRAWER_GROUP_MODULES,
DRAWER_EXPAND_AUTOMATION,
DRAWER_EXPAND_CLIENTS,
DRAWER_EXPAND_MONITORING,
QUICK_ACTIONS_LIDARR,
QUICK_ACTIONS_RADARR,
QUICK_ACTIONS_SONARR,
QUICK_ACTIONS_NZBGET,
QUICK_ACTIONS_SABNZBD,
QUICK_ACTIONS_TAUTULLI,
QUICK_ACTIONS_SEARCH,
USE_24_HOUR_TIME,
}
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.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';
case LunaDatabaseValue.SELECTED_BROWSER: return 'LUNASEA_SELECTED_BROWSER';
case LunaDatabaseValue.DRAWER_GROUP_MODULES: return 'LUNASEA_DRAWER_GROUP_MODULES';
case LunaDatabaseValue.DRAWER_EXPAND_AUTOMATION: return 'LUNASEA_DRAWER_EXPAND_AUTOMATION';
case LunaDatabaseValue.DRAWER_EXPAND_CLIENTS: return 'LUNASEA_DRAWER_EXPAND_CLIENTS';
case LunaDatabaseValue.DRAWER_EXPAND_MONITORING: return 'LUNASEA_DRAWER_EXPAND_MONITORING';
case LunaDatabaseValue.QUICK_ACTIONS_LIDARR: return 'LUNASEA_QUICK_ACTIONS_LIDARR';
case LunaDatabaseValue.QUICK_ACTIONS_RADARR: return 'LUNASEA_QUICK_ACTIONS_RADARR';
case LunaDatabaseValue.QUICK_ACTIONS_SONARR: return 'LUNASEA_QUICK_ACTIONS_SONARR';
case LunaDatabaseValue.QUICK_ACTIONS_NZBGET: return 'LUNASEA_QUICK_ACTIONS_NZBGET';
case LunaDatabaseValue.QUICK_ACTIONS_SABNZBD: return 'LUNASEA_QUICK_ACTIONS_SABNZBD';
case LunaDatabaseValue.QUICK_ACTIONS_TAUTULLI: return 'LUNASEA_QUICK_ACTIONS_TAUTULLI';
case LunaDatabaseValue.QUICK_ACTIONS_SEARCH: return 'LUNASEA_QUICK_ACTIONS_SEARCH';
case LunaDatabaseValue.USE_24_HOUR_TIME: return 'LUNASEA_USE_24_HOUR_TIME';
}
throw Exception('key not found');
}
dynamic get data {
final box = Database.lunaSeaBox;
switch(this) {
case LunaDatabaseValue.ENABLED_PROFILE: return box.get(this.key, defaultValue: 'default');
case LunaDatabaseValue.SELECTED_BROWSER: return box.get(this.key, defaultValue: LunaBrowser.APPLE_SAFARI);
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.DRAWER_GROUP_MODULES: return box.get(this.key, defaultValue: false);
case LunaDatabaseValue.DRAWER_EXPAND_AUTOMATION: return box.get(this.key, defaultValue: true);
case LunaDatabaseValue.DRAWER_EXPAND_CLIENTS: return box.get(this.key, defaultValue: true);
case LunaDatabaseValue.DRAWER_EXPAND_MONITORING: 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);
case LunaDatabaseValue.QUICK_ACTIONS_NZBGET: return box.get(this.key, defaultValue: false);
case LunaDatabaseValue.QUICK_ACTIONS_SABNZBD: return box.get(this.key, defaultValue: false);
case LunaDatabaseValue.QUICK_ACTIONS_TAUTULLI: return box.get(this.key, defaultValue: false);
case LunaDatabaseValue.QUICK_ACTIONS_SEARCH: return box.get(this.key, defaultValue: false);
case LunaDatabaseValue.USE_24_HOUR_TIME: return box.get(this.key, defaultValue: false);
}
throw Exception('data not found');
}
void put(dynamic value) {
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.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;
case LunaDatabaseValue.SELECTED_BROWSER: if(value.runtimeType == LunaBrowser) box.put(this.key, value); return;
case LunaDatabaseValue.DRAWER_GROUP_MODULES: if(value.runtimeType == bool) box.put(this.key, value); return;
case LunaDatabaseValue.DRAWER_EXPAND_AUTOMATION: if(value.runtimeType == bool) box.put(this.key, value); return;
case LunaDatabaseValue.DRAWER_EXPAND_CLIENTS: if(value.runtimeType == bool) box.put(this.key, value); return;
case LunaDatabaseValue.DRAWER_EXPAND_MONITORING: if(value.runtimeType == bool) box.put(this.key, value); return;
case LunaDatabaseValue.QUICK_ACTIONS_LIDARR: if(value.runtimeType == bool) box.put(this.key, value); return;
case LunaDatabaseValue.QUICK_ACTIONS_RADARR: if(value.runtimeType == bool) box.put(this.key, value); return;
case LunaDatabaseValue.QUICK_ACTIONS_SONARR: if(value.runtimeType == bool) box.put(this.key, value); return;
case LunaDatabaseValue.QUICK_ACTIONS_NZBGET: if(value.runtimeType == bool) box.put(this.key, value); return;
case LunaDatabaseValue.QUICK_ACTIONS_SABNZBD: if(value.runtimeType == bool) box.put(this.key, value); return;
case LunaDatabaseValue.QUICK_ACTIONS_TAUTULLI: if(value.runtimeType == bool) box.put(this.key, value); return;
case LunaDatabaseValue.QUICK_ACTIONS_SEARCH: if(value.runtimeType == bool) box.put(this.key, value); return;
case LunaDatabaseValue.USE_24_HOUR_TIME: if(value.runtimeType == bool) box.put(this.key, value); return;
}
LunaLogger().warning('LunaDatabaseValueExtension', 'put', 'Attempted to enter data for invalid LunaDatabaseValue: ${this?.toString() ?? 'null'}');
}
void listen(Widget Function(BuildContext, Box<dynamic>, Widget) builder) => ValueListenableBuilder(
valueListenable: Database.lunaSeaBox.listenable(keys: [this.key]),
builder: builder,
);
}

View File

@@ -0,0 +1,13 @@
abstract class LunaModuleDatabase {
/// Register all adapters necessary for this module
void registerAdapters();
/// Export the current module's database into a [Map] object
Map<String, dynamic> export();
/// Receiving a map object, import a configuration
void import(Map<String, dynamic> config);
/// Return the Database enumerator value given the key
dynamic valueFromKey(String value);
}

View File

@@ -3,9 +3,14 @@ import 'package:flutter/services.dart';
import 'package:lunasea/core.dart';
class LunaDialogs {
LunaDialogs._();
static Future<List<dynamic>> editText(BuildContext context, String title, { String prefill = '', List<TextSpan> extraText }) async {
/// Show an an edit text prompt.
///
/// Can pass in [prefill] String to prefill the [TextFormField]. Can also pass in a list of [TextSpan] tp show text above the field.
///
/// Returns list containing:
/// - 0: Flag (true if they hit save, false if they cancelled the prompt)
/// - 1: Value from the [TextEditingController].
Future<List<dynamic>> editText(BuildContext context, String dialogTitle, { String prefill = '', List<TextSpan> extraText }) async {
bool _flag = false;
final _formKey = GlobalKey<FormState>();
final _textController = TextEditingController()..text = prefill;
@@ -19,7 +24,7 @@ class LunaDialogs {
await LSDialog.dialog(
context: context,
title: title,
title: dialogTitle,
buttons: [
LSDialog.button(
text: 'Save',
@@ -32,7 +37,7 @@ class LunaDialogs {
key: _formKey,
child: LSDialog.textFormInput(
controller: _textController,
title: title,
title: dialogTitle,
onSubmitted: (_) => _setValues(true),
validator: (_) => null,
),
@@ -43,7 +48,10 @@ class LunaDialogs {
return [_flag, _textController.text];
}
static Future<void> textPreview(BuildContext context, String title, String text, { bool alignLeft = false }) async {
/// Show a text preview dialog.
///
/// Can pass in boolean [alignLeft] to left align the text in the dialog (useful for bulleted lists)
Future<void> textPreview(BuildContext context, String dialogTitle, String text, { bool alignLeft = false }) async {
await showDialog(
context: context,
builder: (context) => AlertDialog(
@@ -63,7 +71,7 @@ class LunaDialogs {
},
),
],
title: LSDialog.title(text: title),
title: LSDialog.title(text: dialogTitle),
content: LSDialog.content(
children: [
LSDialog.textContent(
@@ -75,14 +83,17 @@ class LunaDialogs {
],
),
contentPadding: LSDialog.textDialogContentPadding(),
shape: LunaSeaDatabaseValue.THEME_AMOLED.data && LunaSeaDatabaseValue.THEME_AMOLED_BORDER.data
shape: LunaDatabaseValue.THEME_AMOLED.data && LunaDatabaseValue.THEME_AMOLED_BORDER.data
? LSRoundedShapeWithBorder()
: LSRoundedShape(),
),
);
}
static Future<List<dynamic>> deleteCatalogueWithFiles(BuildContext context, String title) async {
/// **Will be removed in future**
///
/// Show a delete catalogue with all files warning dialog.
Future<List<dynamic>> deleteCatalogueWithFiles(BuildContext context, String moduleTitle) async {
bool _flag = false;
void _setValues(bool flag) {
@@ -101,7 +112,7 @@ class LunaDialogs {
),
],
content: [
LSDialog.textContent(text: 'Are you sure you want to delete all the files and folders for $title?'),
LSDialog.textContent(text: 'Are you sure you want to delete all the files and folders for $moduleTitle?'),
],
contentPadding: LSDialog.textDialogContentPadding(),
);

View File

@@ -2,32 +2,42 @@ import 'package:encrypt/encrypt.dart';
import 'package:lunasea/core.dart';
class LunaEncryption {
LunaEncryption._();
static const int _IV_LENGTH = 16;
static const int _KEY_LENGTH = 32;
static const String _PAD_VALUE = '0';
static const String ENCRYPTION_FAILURE = '<<INVALID_ENCRYPTION>>';
static String encrypt(String encryptionKey, String data) {
/// Encrypt a given string using a given encryption key, and return the encrypted string.
///
/// The key is duped, and either trimmed or padded (with _PAD_VALUE) to 32 characters.
String encrypt(String encryptionKey, String data) {
try {
String _key = (encryptionKey+encryptionKey).padRight(32, '0').substring(0, 32);
Key key = Key.fromUtf8(_key);
IV iv = IV.fromLength(16);
String morphedKey = (encryptionKey+encryptionKey).padRight(_KEY_LENGTH, _PAD_VALUE).substring(0, _KEY_LENGTH);
Key key = Key.fromUtf8(morphedKey);
IV iv = IV.fromLength(_IV_LENGTH);
final _encrypter = Encrypter(AES(key));
final _encrypted = _encrypter.encrypt(data, iv: iv).base64;
return _encrypted;
} catch (e) {
LunaLogger.error('package:lunasea/core/configuration/encryption.dart', 'encrypt', 'Encryption error', e, StackTrace.current);
} catch (error, stack) {
LunaLogger().error('Failed to decrypted "$data" with using key "$encryptionKey"', error, stack);
}
return Constants.ENCRYPTION_FAILURE;
return ENCRYPTION_FAILURE;
}
static String decrypt(String decryptionKey, String data) {
/// Decrypt a given string using a given encryption key, and return the decrypted string.
///
/// The key is duped, and either trimmed or padded (with _PAD_VALUE) to 32 characters.
String decrypt(String decryptionKey, String data) {
try {
String _key = (decryptionKey+decryptionKey).padRight(32, '0').substring(0, 32);
Key key = Key.fromUtf8(_key);
IV iv = IV.fromLength(16);
String morphedKey = (decryptionKey+decryptionKey).padRight(_KEY_LENGTH, _PAD_VALUE).substring(0, _KEY_LENGTH);
Key key = Key.fromUtf8(morphedKey);
IV iv = IV.fromLength(_IV_LENGTH);
final _encrypter = Encrypter(AES(key));
return _encrypter.decrypt64(data, iv: iv);
} catch (e) {
LunaLogger.error('package:lunasea/core/configuration/encryption.dart', 'decrypt', 'Decryption error', e, StackTrace.current);
} catch (error) {
/// Do not log as error (to Sentry), since a decrpytion error is very likely a user problem
LunaLogger().warning('LunaEncryption', 'decrypt', 'Failed to decrypted "$data" with using key "$decryptionKey"');
}
return Constants.ENCRYPTION_FAILURE;
return ENCRYPTION_FAILURE;
}
}

View File

@@ -1,7 +1,7 @@
export 'extensions/integer.dart';
export 'extensions/datetime.dart';
export 'extensions/double.dart';
export 'extensions/duration.dart';
export 'extensions/in_app_purchases.dart';
export 'extensions/flog.dart';
export 'extensions/flog_log_level.dart';
export 'extensions/iap_product_details.dart';
export 'extensions/int.dart';
export 'extensions/string.dart';

View File

@@ -2,39 +2,42 @@ import 'package:intl/intl.dart';
import 'package:lunasea/core.dart';
extension DateTimeExtension on DateTime {
//ignore: non_constant_identifier_names
DateTime lsDateTime_floor() => DateTime(this.year, this.month, this.day);
/// Get the floor of a date.
///
/// The floor is a new [DateTime] object with only the year, month, and day copied from the original source.
DateTime get lunaFloor => DateTime(this.year, this.month, this.day);
//ignore: non_constant_identifier_names
String lsDateTime_ageString(DateTime date) {
if(this == null || date == null) return 'Unknown';
Duration diff = this.difference(date);
if(diff.inDays >= 1) {
return '${diff.inDays} ${diff.inDays == 1 ? 'Day' : 'Days'} Ago';
} else if(diff.inHours >= 1) {
return '${diff.inHours} ${diff.inHours == 1 ? 'Hour' : 'Hours'} Ago';
} else if(diff.inMinutes >= 1) {
return '${diff.inMinutes} ${diff.inMinutes == 1 ? 'Minute' : 'Minutes'} Ago';
}
/// Returns a string representation of the "age" of the [DateTime] object.
///
/// Compares to [DateTime.now().toLocal()] to calculate the age.
String get lunaAge {
if(this == null) return 'Unknown';
Duration diff = DateTime.now().toLocal().difference(this);
if(diff.inDays >= 1) return '${diff.inDays} ${diff.inDays == 1 ? 'Day' : 'Days'} Ago';
if(diff.inHours >= 1) return '${diff.inHours} ${diff.inHours == 1 ? 'Hour' : 'Hours'} Ago';
if(diff.inMinutes >= 1) return '${diff.inMinutes} ${diff.inMinutes == 1 ? 'Minute' : 'Minutes'} Ago';
return '${diff.inSeconds} ${diff.inSeconds == 1 ? 'Second' : 'Seconds'} Ago';
}
//ignore: non_constant_identifier_names
String lsDateTime_upcomingString(DateTime date, { bool shouldLimit = true, int limit = 30 }) {
if(this == null || date == null) return 'Unknown';
Duration diff = date.difference(this);
/// Returns a string representation of how far in the future the day is.
///
/// Compares to [DateTime.now().toLocal()] to calculate the upcoming time.
String get lunaUpcomingDays {
if(this == null) return 'Unknown';
Duration diff = this.difference(DateTime.now().toLocal());
if(diff.inDays == 0) return 'Today';
if(shouldLimit && diff.inDays > limit) return '';
return 'In ${diff.inDays} ${diff.inDays == 1 ? "Day" : "Days"}';
}
/// Returns just the time as a String.
/// 3 PM will return either as 15:00 (24 hour style) or 3:00 PM depending on the configured database option
//ignore: non_constant_identifier_names
String get lsDateTime_time => LunaSeaDatabaseValue.USE_24_HOUR_TIME.data
? DateFormat.Hm().format(this)
: DateFormat.jm().format(this);
/// Returns just the time as a string.
///
/// 3 PM will return either 15:00 (24 hour style) or 3:00 PM depending on the configured database option.
String get lunaTime => LunaDatabaseValue.USE_24_HOUR_TIME.data ? DateFormat.Hm().format(this) : DateFormat.jm().format(this);
/// Returns just the date as a string.
///
/// Formatted as YYYY-MM-DD
String get lunaDate => '${this.year.toString().padLeft(4, '0')}-${this.month.toString().padLeft(2, '0')}-${this.day.toString().padLeft(2, '0')}';
//ignore: non_constant_identifier_names
String get lsDateTime_date => '${this.year.toString().padLeft(4, '0')}-${this.month.toString().padLeft(2, '0')}-${this.day.toString().padLeft(2, '0')}';
}

View File

@@ -1 +1,13 @@
export 'double/time.dart';
import 'package:lunasea/core.dart';
extension DoubleExtension on double {
/// Given a double (of hours), return a string signifying how long ago it occurred.
///
/// Time greater than 48 hours => "x Days Ago" else "x Hours Ago"
String lunaHoursToAge() {
if(this == null) return Constants.TEXT_EMDASH;
int days = (this/24).floor();
if(this > 48) return '$days Days Ago';
return this == 1 ? '1 Hour Ago' : '${this.toStringAsFixed(1)} Hours Ago';
}
}

View File

@@ -1,9 +0,0 @@
extension DoubleTimeExtension on double {
// ignore: non_constant_identifier_names
String lsTime_releaseAgeString() {
if(this == null) return '';
int days = (this/24).floor();
if(this > 48) return days == 1 ? '1 Day Ago' : '$days Days Ago';
return this == 1 ? '1 Hour Ago' : '${this.toStringAsFixed(1)} Hours Ago';
}
}

View File

@@ -1,35 +1,31 @@
import 'package:lunasea/core.dart';
extension DurationExtension on Duration {
/// Creates a HH:MM:SS timestamp
//ignore: non_constant_identifier_names
String lsDuration_timestamp() {
String hours = this.inHours.toString().padLeft(2, '0');
String minutes = (this.inMinutes%60).toString().padLeft(2, '0');
String seconds = (this.inSeconds%60).toString().padLeft(2, '0');
return [
if(hours != '00') '$hours:',
'$minutes:',
'$seconds',
].join();
}
/// Return a string representation of the timestamp.
///
/// Format example: HH:MM:SS or MM:SS if the duration is less than one hour
String get lunaTimestamp => [
if(this.inHours != 0) this.inHours.toString().padLeft(2, '0'),
(this.inMinutes%60).toString().padLeft(2, '0'),
(this.inSeconds%60).toString().padLeft(2, '0'),
].join(':');
/// Creates a full timestamp, akin to 1 Day, 23 Hours, 10 Minutes
//ignore: non_constant_identifier_names
String lsDuration_fullTimestamp() {
/// Returns a string representation of the timestamp as words.
///
/// Format example: 1 Day, 23 Hours, 10 Minutes
String get lunaTimestampWords {
String days, hours, minutes;
if(this.inDays == 1) days = '1 Day ';
if(this.inDays > 1) days = '${this.inDays.toString()} Days ';
if(this.inHours == 1) hours = '1 Hour ';
if(this.inHours > 1) hours = '${(this.inHours%24).toString()} Hours ';
if(this.inMinutes == 1) minutes = '1 Minute ';
if(this.inMinutes > 1) minutes = '${(this.inMinutes%60).toString()} Minutes ';
if(this.inDays == 1) days = '1 Day';
if(this.inDays > 1) days = '${this.inDays.toString()} Days';
if(this.inHours == 1) hours = '1 Hour';
if(this.inHours > 1) hours = '${(this.inHours%24).toString()} Hours';
if(this.inMinutes == 1) minutes = '1 Minute';
if(this.inMinutes > 1) minutes = '${(this.inMinutes%60).toString()} Minutes';
return [
if(days != null) days,
if(hours != null) hours,
if(minutes != null) minutes,
if(minutes == null) Constants.TEXT_EMDASH,
].join();
].join(' ');
}
}

View File

@@ -1 +0,0 @@
export 'flog/log_level.dart';

View File

@@ -1,18 +0,0 @@
import 'package:flutter/material.dart';
import 'package:lunasea/core.dart';
import 'package:f_logs/f_logs.dart' as FLog;
extension FLogLogLevelExtension on FLog.LogLevel {
//ignore: non_constant_identifier_names
String lsFLogLogLevel_name() => this.toString().substring(9);
//ignore: non_constant_identifier_names
Color lsFLogLogLevel_color() {
switch(this.toString()) {
case 'LogLevel.WARNING': return Colors.orange;
case 'LogLevel.ERROR': return Colors.red;
case 'LogLevel.FATAL': return LunaColours.accent;
default: return Colors.blueGrey;
}
}
}

View File

@@ -0,0 +1,18 @@
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;
}
}
}

View File

@@ -3,28 +3,27 @@ import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:lunasea/core.dart';
extension ProductDetailsExtension on ProductDetails {
//ignore: non_constant_identifier_names
IconData get ls_Icon {
/// Returns [IconData] icon corresponding to the IAP
IconData get lunaIcon {
switch(this.id) {
case LunaInAppPurchases.IAP_ID_DONATION_01: return Icons.local_drink;
case LunaInAppPurchases.IAP_ID_DONATION_03: return Icons.local_cafe;
case LunaInAppPurchases.IAP_ID_DONATION_05: return Icons.local_bar;
case LunaInAppPurchases.IAP_ID_DONATION_10: return Icons.fastfood;
case LunaInAppPurchases.IAP_UPGRADE_PRO: return Icons.upgrade;
default: return Icons.attach_money;
}
}
//ignore: non_constant_identifier_names
String get ls_Name {
/// Returns a readable name for the corresponding IAP
String get lunaName {
switch(this.id) {
case LunaInAppPurchases.IAP_ID_DONATION_01: return 'Buy Me A Soda';
case LunaInAppPurchases.IAP_ID_DONATION_03: return 'Buy Me A Coffee';
case LunaInAppPurchases.IAP_ID_DONATION_05: return 'Buy Me A Beer';
case LunaInAppPurchases.IAP_ID_DONATION_10: return 'Buy Me A Burger';
case LunaInAppPurchases.IAP_UPGRADE_PRO: return 'Upgrade to LunaSea Pro';
default: return 'Unknown In-App Purchase';
}
}
//ignore: non_constant_identifier_names
String get ls_Description => '${this.price}';
}
}

View File

@@ -0,0 +1,76 @@
extension IntegerBytesExtension on int {
static const _BIT_SIZES = ['b', 'Kb', 'Mb', 'Gb', 'Tb', 'Pb', 'Eb'];
static const _BYTE_SIZES = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'];
String _bytesToString({ int decimals = 2, position = 0, bool bytes = true }) {
if(this == null || this <= 0) return '${0.toStringAsFixed(decimals)} B';
int chunk = bytes ? 1024 : 1000;
double size = this.toDouble();
while(size > chunk) {
size /= chunk;
position++;
}
return '${size.toStringAsFixed(decimals)} ${bytes ? _BYTE_SIZES[position] : _BIT_SIZES[position]}';
}
/// Given an integer of the number of bytes/bits, return a string representation.
String lunaBytesToString({ int decimals = 2, bool bytes = true }) => _bytesToString(decimals: decimals, position: 0, bytes: bytes);
/// Given an integer of the number of kilobytes/kilobits, return a string representation.
String lunaKilobytesToString({ int decimals = 2, bool bytes = true }) => _bytesToString(decimals: decimals, position: 1, bytes: bytes);
/// Given an integer of the number of megabytes/megabits, return a string representation.
String lunaMegabytesToString({ int decimals = 2, bool bytes = true }) => _bytesToString(decimals: decimals, position: 2, bytes: bytes);
/// Given an integer of the number of gigabytes/gigabits, return a string representation.
String lunaGigabytesToString({ int decimals = 2, bool bytes = true }) => _bytesToString(decimals: decimals, position: 3, bytes: bytes);
}
extension IntegerTimeExtension on int {
static const int _MINUTE_IN_SECONDS = 60;
static const int _HOUR_IN_SECONDS = 60*60;
static const int _DAY_IN_SECONDS = 60*60*24;
/// Given an integer duration, converts it to a readable string. Assumes the duration is in seconds, use the `divisor` and `multipler` variables to easily modify the input.
///
/// Only contains the highest factor (2.5 Days would return 2 Days).
String lunaDuration({ int divisor = 1, int multiplier = 1 }) {
if(this == null) return '';
int duration = ((this*multiplier)/divisor).floor();
int days = (duration/_DAY_IN_SECONDS).floor();
if(days > 0) return '$days ${days == 1 ? 'Day' : 'Days'}';
int hours = (duration/_HOUR_IN_SECONDS).floor();
if(hours > 0) return '$hours ${hours == 1 ? 'Hour' : 'Hours'}';
int minutes = (duration/_MINUTE_IN_SECONDS).floor();
if(minutes > 0) return '$minutes ${minutes == 1 ? 'Minute' : 'Minutes'}';
return '$duration ${duration == 1 ? 'Second' : 'Seconds'}';
}
/// Given an integer duration, converts it to a readable runtime string. Assumes the duration is in seconds, use the `divisor` and `multipler` variables to easily modify the input.
///
/// Format example: 1h 23m
String lunaRuntime({ int divisor = 1, int multiplier = 1 }) {
if(this == null || this == 0) return '';
double runtime = ((this*multiplier)/divisor);
if(runtime < 60) return '${runtime.floor()}m';
return '${(runtime/60).floor()}h ${(runtime-((runtime/60).floor()*60)).floor()}m';
}
/// Given an integer duration, converts it to a readable timestamp string. Assumes the duration is in seconds, use the `divisor` and `multiplier` variables to easily modify the input.
///
/// Format example: 1:23:45 (1 hour, 23 minutes, and 45 seconds)
String lunaTimestamp({ int divisor = 1, int multiplier = 1 }) {
if(this == null) return '';
int duration = ((this*multiplier)/divisor).floor();
int hours = 0, minutes = 0;
while(duration >= _HOUR_IN_SECONDS) {
duration -= _HOUR_IN_SECONDS;
hours++;
}
while(duration >= _MINUTE_IN_SECONDS) {
duration -= _MINUTE_IN_SECONDS;
minutes++;
}
return '${hours.toString().padLeft(1, '0')}:${minutes.toString().padLeft(2, '0')}:${duration.toString().padLeft(2, '0')}';
}
}

View File

@@ -1,2 +0,0 @@
export 'integer/bytes.dart';
export 'integer/time.dart';

View File

@@ -1,23 +0,0 @@
import 'package:lunasea/core/constants.dart';
extension IntegerBytesExtension on int {
String _bytesToString({ int decimals = 2, position = 0, bool bytes = true }) {
if(this == null || this <= 0) return '${0.toStringAsFixed(decimals)} B';
int chunk = bytes ? 1024 : 1000;
double size = this.toDouble();
while(size > chunk) {
size /= chunk;
position++;
}
return '${size.toStringAsFixed(decimals)} ${bytes ? Constants.BYTE_SIZES[position] : Constants.BIT_SIZES[position]}';
}
// ignore: non_constant_identifier_names
String lsBytes_BytesToString({ int decimals = 2, bool bytes = true }) => _bytesToString(decimals: decimals, position: 0, bytes: bytes);
// ignore: non_constant_identifier_names
String lsBytes_KilobytesToString({ int decimals = 2, bool bytes = true }) => _bytesToString(decimals: decimals, position: 1, bytes: bytes);
// ignore: non_constant_identifier_names
String lsBytes_MegabytesToString({ int decimals = 2, bool bytes = true }) => _bytesToString(decimals: decimals, position: 2, bytes: bytes);
// ignore: non_constant_identifier_names
String lsBytes_GigabytesToString({ int decimals = 2, bool bytes = true }) => _bytesToString(decimals: decimals, position: 3, bytes: bytes);
}

View File

@@ -1,47 +0,0 @@
const DAY_IN_SECONDS = 60*60*24;
const HOUR_IN_SECONDS = 60*60;
const MINUTE_IN_SECONDS = 60;
extension IntegerTimeExtension on int {
// ignore: non_constant_identifier_names
String lsTime_runtimeString({ bool dot = false, int divisor = 1, int multipler = 1 }) {
if(this == null || this == 0) return '';
double runtime = ((this*multipler)/divisor);
if(runtime < 60) return dot
? '\t\t${runtime.floor()}m'
: '${runtime.floor()}m';
return dot
? '\t\t${(runtime/60).floor()}h ${(runtime-((runtime/60).floor()*60)).floor()}m'
: '${(runtime/60).floor()}h ${(runtime-((runtime/60).floor()*60)).floor()}m';
}
// ignore: non_constant_identifier_names
String lsTime_timestampString({ int divisor = 1, int multipler = 1 }) {
if(this == null) return '';
int seconds = ((this*multipler)/divisor).floor();
int hours = 0;
int minutes = 0;
while(seconds >= 3600) {
seconds -= 3600;
hours++;
}
while(seconds >= 60) {
seconds -= 60;
minutes++;
}
return '${hours.toString().padLeft(1, '0')}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
}
// ignore: non_constant_identifier_names
String lsTime_durationString({ int divisor = 1, int multiplier = 1 }) {
if(this == null) return '';
int duration = ((this*multiplier)/divisor).floor();
int days = (duration/DAY_IN_SECONDS).floor();
if(days > 0) return days == 1 ? '1 Day' : '$days Days';
int hours = (duration/HOUR_IN_SECONDS).floor();
if(hours > 0) return hours == 1 ? '1 Hour' : '$hours Hours';
int minutes = (duration/MINUTE_IN_SECONDS).floor();
if(minutes > 0) return minutes == 1 ? '1 Minute' : '$minutes Minutes';
return duration == 1 ? '1 Second' : '$duration Seconds';
}
}

View File

@@ -1,3 +1,58 @@
export 'string/language.dart';
export 'string/links.dart';
export 'string/slugs.dart';
import 'package:lunasea/core.dart';
import 'package:url_launcher/url_launcher.dart';
extension StringExtension on String {
/// Returns a string with all first letters of each word capitalized
///
/// Default word splitting pattern is by a space.
String lunaCapitalizeFirstLetters({ Pattern pattern = ' ' }) {
List<String> split = this.split(pattern);
for(var i=0; i<split.length; i++) {
split[i] = split[i].substring(0, 1).toUpperCase()+split[i].substring(1);
}
return split.join(pattern);
}
/// Convert a string into a slug format.
///
/// Example "LunaSea Flutter" => 'lunasea-flutter';
String lunaConvertToSlug() => this.toLowerCase().replaceAll(RegExp(r'[\ \.]'), '-').replaceAll(RegExp(r'[^a-zA-Z0-9\-]'), '').trim();
}
extension StringLinksExtension on String {
Future<void> _openLink(String url) async {
try {
//Attempt to launch the link forced as a universal link
if(await _launchUniversalLink(url)) return;
//If that fails and we're using a custom browser, format the link for the correct browser and attempt to launch
LunaBrowser _browser = LunaDatabaseValue.SELECTED_BROWSER.data;
if(_browser != LunaBrowser.APPLE_SAFARI && await _launchCustomBrowser(_browser.formatted(url))) return;
//If all else fails, just launch it in Safari/stock browser
await _launch(url);
} catch (error, stack) {
LunaLogger().error('Unable to open link: $url', error, stack);
}
}
Future<bool> _launchUniversalLink(String url) async => await launch(url, forceSafariVC: false, universalLinksOnly: true);
Future<bool> _launchCustomBrowser(String url) async => await launch(url, forceSafariVC: false);
Future<bool> _launch(String url) async => await launch(url);
/// Attempt to open this string as a URL in a browser.
Future<void> lunaOpenGenericLink() async => await _openLink(this);
/// Attach this string as an ID/title to IMDB and attempt to launch it as a URL.
Future<void> lunaOpenIMDB() async => await _openLink('https://www.imdb.com/title/$this');
/// Attach this string as a video ID to YouTube and attempt to launch it as a URL.
Future<void> lunaOpenYouTube() async => await _openLink('https://www.youtube.com/watch?v=$this');
/// Attach this string as a movie ID to TheMovieDB and attempt to launch it as a URL.
Future<void> lunaOpenTheMovieDB() async => await _openLink('https://www.themoviedb.org/movie/$this');
/// Attach this string as a series ID to TheTVDB and attempt to launch it as a URL.
Future<void> lunaOpenTheTVDB() async => await _openLink('https://www.thetvdb.com/?id=$this&tab=series');
/// Attach this string as a series ID to TVMaze and attempt to launch it as a URL.
Future<void> lunaOpenTVMaze() async => await _openLink('https://www.tvmaze.com/shows/$this');
}

View File

@@ -1,10 +0,0 @@
extension StringLanguageExtension on String {
// ignore: non_constant_identifier_names
lsLanguage_Capitalize() {
List<String> split = this.split(' ');
for(var i=0; i<split.length; i++) {
split[i] = split[i].substring(0, 1).toUpperCase()+split[i].substring(1);
}
return split.join(' ');
}
}

View File

@@ -1,114 +0,0 @@
import 'package:flutter/material.dart';
import 'package:lunasea/core/database.dart';
import 'package:lunasea/core/ui.dart';
import 'package:url_launcher/url_launcher.dart';
part 'links.g.dart';
@HiveType(typeId: 11, adapterName: 'LSBrowsersAdapter')
enum LSBrowsers {
@HiveField(0)
APPLE_SAFARI,
@HiveField(1)
BRAVE_BROWSER,
@HiveField(2)
GOOGLE_CHROME,
@HiveField(3)
MICROSOFT_EDGE,
@HiveField(4)
MOZILLA_FIREFOX,
}
extension LSBrowsersExtension on LSBrowsers {
String get name {
switch(this) {
case LSBrowsers.APPLE_SAFARI: return 'Apple Safari';
case LSBrowsers.BRAVE_BROWSER: return 'Brave Browser';
case LSBrowsers.GOOGLE_CHROME: return 'Google Chrome';
case LSBrowsers.MICROSOFT_EDGE: return 'Microsoft Edge';
case LSBrowsers.MOZILLA_FIREFOX: return 'Mozilla Firefox';
default: return 'Unknown Browser';
}
}
String get key {
switch(this) {
case LSBrowsers.APPLE_SAFARI: return 'applesafari';
case LSBrowsers.BRAVE_BROWSER: return 'bravebrowser';
case LSBrowsers.GOOGLE_CHROME: return 'googlechrome';
case LSBrowsers.MICROSOFT_EDGE: return 'microsoftedge';
case LSBrowsers.MOZILLA_FIREFOX: return 'mozillafirefox';
default: return 'unknown';
}
}
IconData get icon {
switch(this) {
case LSBrowsers.APPLE_SAFARI: return CustomIcons.safari;
case LSBrowsers.BRAVE_BROWSER: return CustomIcons.bravebrowser;
case LSBrowsers.GOOGLE_CHROME: return CustomIcons.chrome;
case LSBrowsers.MICROSOFT_EDGE: return CustomIcons.microsoftedge;
case LSBrowsers.MOZILLA_FIREFOX: return CustomIcons.firefox;
default: return null;
}
}
String formatted(String url) {
bool isHttps = url.substring(0, 8) == 'https://';
switch(this) {
case LSBrowsers.BRAVE_BROWSER: return 'brave://open-url?url=$url';
case LSBrowsers.GOOGLE_CHROME: return isHttps
? url.replaceFirst('https://', 'googlechromes://')
: url.replaceFirst('http://', 'googlechrome://');
case LSBrowsers.MICROSOFT_EDGE: return isHttps
? url.replaceFirst('https://', 'microsoft-edge-https://')
: url.replaceFirst('http://', 'microsoft-edge-http://');
case LSBrowsers.MOZILLA_FIREFOX: return 'firefox://open-url?url=$url';
case LSBrowsers.APPLE_SAFARI:
default: return url;
}
}
}
extension StringLinksExtension on String {
Future<void> _openLink(String url) async {
try {
//Attempt to launch the link forced as a universal link
if(await _launchUniversalLink(url)) return;
//If that fails and we're using a custom browser, format the link for the correct browser and attempt to launch
LSBrowsers _browser = LunaSeaDatabaseValue.SELECTED_BROWSER.data;
if(
_browser != LSBrowsers.APPLE_SAFARI &&
await _launchCustomBrowser(_browser.formatted(url))
) return;
//If all else fails, just launch it in Safari
await _launch(url);
} catch (e) {}
}
Future<bool> _launchUniversalLink(String url) async => await launch(
url,
forceSafariVC: false,
universalLinksOnly: true,
);
Future<bool> _launchCustomBrowser(String url) async => await launch(
url,
forceSafariVC: false,
);
Future<bool> _launch(String url) async => await launch(url);
// ignore: non_constant_identifier_names
Future<void> lsLinks_OpenLink() async => await _openLink(this);
// ignore: non_constant_identifier_names
Future<void> lsLinks_OpenIMDB() async => await _openLink('https://www.imdb.com/title/$this');
// ignore: non_constant_identifier_names
Future<void> lsLinks_OpenYoutube() async => await _openLink('https://www.youtube.com/watch?v=$this');
// ignore: non_constant_identifier_names
Future<void> lsLinks_OpenMovieDB() async => await _openLink('https://www.themoviedb.org/movie/$this');
// ignore: non_constant_identifier_names
Future<void> lsLinks_OpenTVDB() async => await _openLink('https://www.thetvdb.com/?id=$this&tab=series');
// ignore: non_constant_identifier_names
Future<void> lsLinks_OpenTVMaze() async => await _openLink('https://www.tvmaze.com/shows/$this');
}

View File

@@ -1,10 +0,0 @@
extension StringSlugsExtension on String {
// ignore: non_constant_identifier_names
String lsSlugs_ConvertToSlug() {
return this
.toLowerCase()
.replaceAll(RegExp(r'[\ \.]'), '-')
.replaceAll(RegExp(r'[^a-zA-Z0-9\-]'), '')
.trim();
}
}

View File

@@ -3,18 +3,17 @@ import 'package:share/share.dart';
import 'package:path_provider/path_provider.dart';
class LunaFileSystem {
LunaFileSystem._();
static Future<String> _getTempFilePath(String filename) async {
Directory tempDir = await getTemporaryDirectory();
return '${tempDir.path}/$filename';
}
static Future<void> exportFileToTemporaryStorage(String name, String data) async {
String _file = await _getTempFilePath(name);
/// Export a given string to the OS-level share sheet with the given name.
///
/// Temporarily writes the string as a [File] to the temporary storage directory on the OS.
///
/// Temporary storage is eventually cleared by the OS, but is more than enough time to save/send via the share sheet.
Future<void> exportStringToShareSheet(String name, String data) async {
Directory tempDirectory = await getTemporaryDirectory();
String path = '${tempDirectory.path}/$name';
//Write the NZB to the filesystem
File file = File(_file);
File file = File(path);
await file?.writeAsString(data);
await Share.shareFiles([_file]);
await Share.shareFiles([path]);
}
}

13
lib/core/firebase.dart Normal file
View File

@@ -0,0 +1,13 @@
import 'package:firebase_core/firebase_core.dart';
class LunaFirebase {
/// Return an instance of [FirebaseApp].
///
/// Throws an error if [LunaFirebase.initialize] has not been called.
FirebaseApp get app => Firebase.app();
/// Initialize Firebase: Calls [Firebase.initializeApp()].
///
/// This must be called before anything accesses Firebase services, or an exception will be thrown.
static Future<FirebaseApp> initialize({ String name }) async => Firebase.initializeApp();
}

View File

@@ -1,8 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/painting.dart';
class LunaImageCache {
LunaImageCache._();
/// Initialize the image cache by setting [ImageCache]'s maximumSize and maximumSizeBytes.
static void initialize() {
ImageCache().maximumSize = 1000;
ImageCache().maximumSizeBytes = 128 << 20;

View File

@@ -2,40 +2,75 @@ import 'dart:async';
import 'package:in_app_purchase/in_app_purchase.dart';
class LunaInAppPurchases {
// Donation IAP IDs
static const IAP_ID_DONATION_01 = 'donation_01';
static const IAP_ID_DONATION_03 = 'donation_03';
static const IAP_ID_DONATION_05 = 'donation_05';
static const IAP_ID_DONATION_10 = 'donation_10';
static const IAP_IDS = [
static const IAP_DONATION_IDS = [
IAP_ID_DONATION_01,
IAP_ID_DONATION_03,
IAP_ID_DONATION_05,
IAP_ID_DONATION_10,
];
static final InAppPurchaseConnection connection = InAppPurchaseConnection.instance;
static StreamSubscription<List<PurchaseDetails>> purchaseStream;
static List<ProductDetails> products = [];
static bool available = true;
// Upgrade IAP IDs
static const IAP_UPGRADE_PRO = 'upgrade_01';
static const IAP_UPGRADE_IDS = [
IAP_UPGRADE_PRO,
];
static InAppPurchaseConnection get connection => InAppPurchaseConnection.instance;
static StreamSubscription<List<PurchaseDetails>> _purchaseStream;
static List<ProductDetails> donationIAPs = [];
static List<ProductDetails> upgradeIAPs = [];
static bool isAvailable = false;
/// Initialize the in-app purchases connection.
///
/// - Enables pending purchase
/// - Attach [InAppPurchaseConnection.instance.purchaseUpdateStream] listener for automatically completing IAP purchases
/// - Fetch and store donation IAPs
static Future<void> initialize() async {
InAppPurchaseConnection.enablePendingPurchases();
purchaseStream = connection.purchaseUpdatedStream.listen((data) => LunaInAppPurchases._purchasedCallback(data));
available = await connection.isAvailable();
ProductDetailsResponse _resp = await connection.queryProductDetails(Set.from(IAP_IDS));
products = _resp.productDetails;
products.sort((a, b) => a.id.compareTo(b.id));
isAvailable = await connection.isAvailable();
if(isAvailable) {
// Open listener
_purchaseStream = connection.purchaseUpdatedStream.listen((data) => LunaInAppPurchases._purchasedCallback(data));
// Load donation IAPs
ProductDetailsResponse donation = await connection.queryProductDetails(Set.from(IAP_DONATION_IDS));
donationIAPs = donation?.productDetails ?? [];
donationIAPs.sort((a, b) => a?.id?.compareTo(b?.id) ?? 0);
// Load upgrade IAPs
ProductDetailsResponse upgrade = await connection.queryProductDetails(Set.from(IAP_UPGRADE_IDS));
upgradeIAPs = upgrade?.productDetails ?? [];
upgradeIAPs.sort((a, b) => a?.id?.compareTo(b?.id) ?? 0);
}
}
static Future<void> deinitialize() async {
purchaseStream?.cancel();
}
/// Deinitialize the in-app purchases connection.
///
/// - Cleanly cancel/close the [InAppPurchaseConnection.instance.purchaseUpdateStream] listener
static Future<void> deinitialize() async => _purchaseStream?.cancel();
/// Callback function for [purchaseUpdatedStream].
///
/// **Donations**:
/// - Automatically completes the purchase, as there is no actual product to deliver
///
/// **Account Upgrade**:
/// - Create a listener to the user's account in Firestore
/// - Upgrade their account
/// - Validate upgrade
/// - Mark as completed, close listener
static Future<void> _purchasedCallback(List<PurchaseDetails> purchases) async {
for(var purchase in purchases) {
if(purchase.pendingCompletePurchase) await connection.completePurchase(purchase);
/// Handle donations by always completing the purchase
if(IAP_DONATION_IDS.contains(purchase.productID)) {
if(purchase.pendingCompletePurchase) await connection.completePurchase(purchase);
}
/// Handle account upgrades
if(IAP_UPGRADE_IDS.contains(purchase.productID)) {}
}
}
}

View File

@@ -1,88 +1,79 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:lunasea/core/constants.dart';
import 'package:lunasea/core/database.dart';
import 'package:sentry/sentry.dart';
import 'package:stack_trace/stack_trace.dart';
import 'package:dio/dio.dart';
import 'package:f_logs/f_logs.dart' show FLog, DataLogType, FormatType, LogsConfig;
export 'package:dio/dio.dart' show DioError;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:lunasea/core.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:stack_trace/stack_trace.dart';
class LunaLogger {
LunaLogger._();
static final SentryClient _sentry = SentryClient(dsn: Constants.SENTRY_DSN);
static const SENTRY_DSN = 'https://511f76efcf714ecfb5ed6b26b5819bd6@o426090.ingest.sentry.io/5367513';
static const CHECK_LOGS_MESSAGE = 'Check the logs for more details';
/// Initialize the logger by setting the timestamp format and capturing errors on [FlutterError.onError].
static void initialize() {
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 {
bool inDebugMode = false;
assert(inDebugMode = true);
if (inDebugMode) {
FlutterError.dumpErrorToConsole(details, forceReport: forceReport);
}
if (kDebugMode) FlutterError.dumpErrorToConsole(details, forceReport: forceReport);
Zone.current.handleUncaughtError(details.exception, details.stack);
};
}
static void debug(String className, String methodName, String text, {DataLogType type = DataLogType.DEFAULT}) {
FLog.debug(
className: className,
methodName: methodName,
text: text,
dataLogType: type.toString(),
);
}
/// Log a new debug-level log.
void debug(String text) => FLog.debug(
text: text,
dataLogType: DataLogType.DEFAULT.toString(),
);
static void warning(String className, String methodName, String text, {DataLogType type = DataLogType.DEFAULT}) {
FLog.warning(
className: className,
methodName: methodName,
text: text,
dataLogType: type.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(),
);
static void error(String className, String methodName, String text, dynamic error, StackTrace stack, {
DataLogType type = DataLogType.DEFAULT,
bool uploadToSentry = true,
}) {
/// Log a new error-level log.
void error(String text, dynamic error, StackTrace stack) {
Trace _trace = stack == null ? null : Trace.from(stack);
FLog.error(
className: className,
methodName: methodName,
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: type.toString(),
dataLogType: DataLogType.DEFAULT.toString(),
);
if(uploadToSentry && LunaSeaDatabaseValue.ENABLED_SENTRY.data) _sentry.captureException(
exception: error,
if(!(error is DioError) && LunaDatabaseValue.ENABLED_SENTRY.data) Sentry.captureException(
error,
stackTrace: stack,
);
}
static void fatal(dynamic error, StackTrace stack, {
DataLogType type = DataLogType.DEFAULT,
bool uploadToSentry = true,
}) {
Trace _trace = Trace.from(stack);
/// Log a new fatal-level log.
void fatal(dynamic error, StackTrace stack) {
Trace _trace = stack == null ? null : Trace.from(stack);
FLog.fatal(
className: _trace.frames.length >= 1 ? _trace.frames[1].uri.toString() ?? 'Unknown' : 'Unknown',
methodName: _trace.frames.length >= 1 ? _trace.frames[1].member.toString() ?? 'Unknown' : 'Unknown',
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: type.toString(),
dataLogType: DataLogType.DEFAULT.toString(),
);
if(LunaSeaDatabaseValue.ENABLED_SENTRY.data) _sentry.captureException(
exception: error,
if(!(error is DioError) && LunaDatabaseValue.ENABLED_SENTRY.data) Sentry.captureException(
error,
stackTrace: stack,
);
}
static Future<File> exportLogs() async => FLog.exportLogs();
static Future<void> clearLogs() async => FLog.clearLogs();
/// Export all logs, and return the [File] object containing the log file.
Future<File> exportLogs() async => FLog.exportLogs();
/// Clear all logs currently saved to the database.
Future<void> clearLogs() async => FLog.clearLogs();
}

View File

@@ -3,20 +3,19 @@ import 'package:lunasea/core.dart';
// ignore: non_constant_identifier_names
Decoration LunaCardDecoration({ @required String uri, @required Map headers }) {
if(LunaSeaDatabaseValue.THEME_IMAGE_BACKGROUND_OPACITY.data == 0) return null;
double _opacity = (LunaSeaDatabaseValue.THEME_IMAGE_BACKGROUND_OPACITY.data as int)/100;
if(LunaDatabaseValue.THEME_IMAGE_BACKGROUND_OPACITY.data == 0) return null;
return BoxDecoration(
image: DecorationImage(
image: NetworkImage(
uri,
headers: Map<String, String>.from(headers),
),
onError: (error, stack) => LunaLogger.warning(
'LunaCardDecoration',
'DecorationImage',
'Failed to fetch background image: $uri',
// To prevent excessive logs, just handle an error loading the image with an empty callback
onError: (error, stack) {},
colorFilter: ColorFilter.mode(
Colors.black.withOpacity((LunaDatabaseValue.THEME_IMAGE_BACKGROUND_OPACITY.data as int)/100),
BlendMode.dstATop,
),
colorFilter: ColorFilter.mode(Colors.black.withOpacity(_opacity), BlendMode.dstATop),
fit: BoxFit.cover,
),
borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS),

View File

@@ -52,7 +52,7 @@ Future<void> showLunaSnackBar({
horizontalDismissDirection: HorizontalDismissDirection.horizontal,
margin: EdgeInsets.all(_PADDING),
borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS),
borderColor: LunaSeaDatabaseValue.THEME_AMOLED.data && LunaSeaDatabaseValue.THEME_AMOLED_BORDER.data
borderColor: LunaDatabaseValue.THEME_AMOLED.data && LunaDatabaseValue.THEME_AMOLED_BORDER.data
? Colors.white12
: Colors.transparent,
child: FlashBar(

View File

@@ -6,12 +6,12 @@ Future<void> showLunaErrorSnackBar({
@required BuildContext context,
@required String title,
dynamic error,
String message = Constants.CHECK_LOGS_MESSAGE,
String message = LunaLogger.CHECK_LOGS_MESSAGE,
}) async => showLunaSnackBar(
context: context,
title: title,
message: message,
type: LunaSnackbarType.ERROR,
showButton: error != null,
buttonOnPressed: () async => LunaDialogs.textPreview(context, 'Error', error.toString()),
buttonOnPressed: () async => LunaDialogs().textPreview(context, 'Error', error.toString()),
);

View File

@@ -1,19 +0,0 @@
import 'package:flutter/material.dart';
class LunaModuleMap {
final String name;
final String description;
final String settingsDescription;
final String route;
final IconData icon;
final Color color;
const LunaModuleMap({
@required this.name,
@required this.description,
@required this.settingsDescription,
@required this.route,
@required this.icon,
@required this.color,
});
}

View File

@@ -0,0 +1,33 @@
import 'package:flutter/material.dart';
/// [LunaModuleMetadata] stores general information about a module, including:
/// - Name
/// - Descrption
/// - Base/home route
/// - Links
/// - Icon
/// - Brand colour
/// - etc.
class LunaModuleMetadata {
final String name;
final String description;
final String settingsDescription;
final String helpMessage;
final String route;
final String website;
final String github;
final IconData icon;
final Color color;
const LunaModuleMetadata({
@required this.name,
@required this.description,
@required this.settingsDescription,
@required this.helpMessage,
@required this.route,
@required this.website,
@required this.github,
@required this.icon,
@required this.color,
});
}

View File

@@ -1,8 +1,14 @@
import 'dart:io';
class LunaNetworking extends HttpOverrides {
static void initialize() => HttpOverrides.global = LunaNetworking();
/// Initialize the default HTTP client by setting [HttpOverrides]'s global HTTP client to [LunaNetworking].
static void initialize() {
HttpOverrides.global = LunaNetworking();
}
/// Overrides the default [createHttpClient] function and ensures all HTTPS connections allow bad certificate connections.
///
/// Returns a new [HttpClient] using the given [context].
@override
HttpClient createHttpClient(SecurityContext context) {
final HttpClient client = super.createHttpClient(context);

View File

@@ -2,17 +2,22 @@ import 'package:flutter/material.dart';
import 'package:lunasea/core.dart';
class LunaProfile {
LunaProfile._();
LunaProfile();
static Future<bool> changeProfile(BuildContext context, String profile) async {
if(LunaSeaDatabaseValue.ENABLED_PROFILE.data != profile) {
/// Safely change profiles.
///
/// Does this safely by:
/// - Ensures that the passed in profile isn't already enabled
/// - Ensures that the profile exists
Future<bool> safelyChangeProfiles(BuildContext context, String profile) async {
if(LunaDatabaseValue.ENABLED_PROFILE.data != profile) {
if(Database.profilesBox.containsKey(profile)) {
LunaSeaDatabaseValue.ENABLED_PROFILE.put(profile);
LunaProvider.reset(context);
LunaDatabaseValue.ENABLED_PROFILE.put(profile);
LunaState.reset(context);
LSSnackBar(context: context, title: 'Changed Profile', message: profile);
return true;
} else {
LunaLogger.warning('LunaProfile', 'changeProfile', 'Attempted to change profile to unknown profile: $profile');
LunaLogger().warning('LunaProfile', 'changeProfile', 'Attempted to change profile to unknown profile: $profile');
}
}
return false;

View File

@@ -6,48 +6,63 @@ import 'package:lunasea/main.dart';
export 'package:quick_actions/quick_actions.dart' show ShortcutItem;
class LunaQuickActions {
LunaQuickActions._();
static final QuickActions _quickActions = QuickActions();
LunaQuickActions._();
/// Initialize the quick actions by setting the action handler.
///
/// Required before the handler can actually receive any intents.
static void initialize(BuildContext context) {
_quickActions.initialize((action) => _handler(context, action));
setShortcutItems();
}
/// Sets the shortcut items by checking the database and enabling the respective action if enabled.
static void setShortcutItems() {
_quickActions.setShortcutItems(<ShortcutItem>[
if(LunaSeaDatabaseValue.QUICK_ACTIONS_SEARCH.data) SearchConstants.MODULE_QUICK_ACTION,
if(LunaSeaDatabaseValue.QUICK_ACTIONS_LIDARR.data) LidarrConstants.MODULE_QUICK_ACTION,
if(LunaSeaDatabaseValue.QUICK_ACTIONS_RADARR.data) RadarrConstants.MODULE_QUICK_ACTION,
if(LunaSeaDatabaseValue.QUICK_ACTIONS_SONARR.data) SonarrConstants.MODULE_QUICK_ACTION,
if(LunaSeaDatabaseValue.QUICK_ACTIONS_NZBGET.data) NZBGetConstants.MODULE_QUICK_ACTION,
if(LunaSeaDatabaseValue.QUICK_ACTIONS_SABNZBD.data) SABnzbdConstants.MODULE_QUICK_ACTION,
if(LunaSeaDatabaseValue.QUICK_ACTIONS_TAUTULLI.data) TautulliConstants.MODULE_QUICK_ACTION,
// General
if(LunaDatabaseValue.QUICK_ACTIONS_SEARCH.data) SearchConstants.MODULE_QUICK_ACTION,
// Automation
if(LunaDatabaseValue.QUICK_ACTIONS_LIDARR.data) LidarrConstants.MODULE_QUICK_ACTION,
if(LunaDatabaseValue.QUICK_ACTIONS_RADARR.data) RadarrConstants.MODULE_QUICK_ACTION,
if(LunaDatabaseValue.QUICK_ACTIONS_SONARR.data) SonarrConstants.MODULE_QUICK_ACTION,
// Clients
if(LunaDatabaseValue.QUICK_ACTIONS_NZBGET.data) NZBGetConstants.MODULE_QUICK_ACTION,
if(LunaDatabaseValue.QUICK_ACTIONS_SABNZBD.data) SABnzbdConstants.MODULE_QUICK_ACTION,
// Monitoring
if(LunaDatabaseValue.QUICK_ACTIONS_TAUTULLI.data) TautulliConstants.MODULE_QUICK_ACTION,
]);
}
/// The actual shortcut command handler
static void _handler(BuildContext context, String action) {
if(action != null) {
switch(action) {
// General
case SearchConstants.MODULE_KEY: _pushSearch(); break;
// Automation
case LidarrConstants.MODULE_KEY: _pushLidarr(); break;
case RadarrConstants.MODULE_KEY: _pushRadarr(); break;
case SonarrConstants.MODULE_KEY: _pushSonarr(); break;
// Clients
case NZBGetConstants.MODULE_KEY: _pushNZBGet(); break;
case SABnzbdConstants.MODULE_KEY: _pushSABnzbd(); break;
// Monitoring
case TautulliConstants.MODULE_KEY: _pushTautulli(); break;
case OmbiConstants.MODULE_KEY: _pushOmbi(); break;
}
}
}
// General
static void _pushSearch() => LunaBIOS.navigatorKey.currentState.pushNamedAndRemoveUntil(Search.ROUTE_NAME, (Route<dynamic> route) => false);
// Automation
static void _pushLidarr() => LunaBIOS.navigatorKey.currentState.pushNamedAndRemoveUntil(Lidarr.ROUTE_NAME, (Route<dynamic> route) => false);
static void _pushRadarr() => LunaBIOS.navigatorKey.currentState.pushNamedAndRemoveUntil(Radarr.ROUTE_NAME, (Route<dynamic> route) => false);
static void _pushSonarr() => LunaBIOS.navigatorKey.currentState.pushNamedAndRemoveUntil(SonarrHomeRouter.route(), (Route<dynamic> route) => false);
// Clients
static void _pushNZBGet() => LunaBIOS.navigatorKey.currentState.pushNamedAndRemoveUntil(NZBGet.ROUTE_NAME, (Route<dynamic> route) => false);
static void _pushSABnzbd() => LunaBIOS.navigatorKey.currentState.pushNamedAndRemoveUntil(SABnzbd.ROUTE_NAME, (Route<dynamic> route) => false);
static void _pushOmbi() => LunaBIOS.navigatorKey.currentState.pushNamedAndRemoveUntil(OmbiHomeRouter.route(), (Route<dynamic> route) => false);
// Monitoring
static void _pushTautulli() => LunaBIOS.navigatorKey.currentState.pushNamedAndRemoveUntil(TautulliHomeRouter.route(), (Route<dynamic> route) => false);
}

View File

@@ -1,110 +1,3 @@
import 'package:flutter/material.dart';
import 'package:fluro/fluro.dart';
import 'package:lunasea/modules/home/routes.dart';
import 'package:lunasea/modules/search/routes.dart';
import 'package:lunasea/modules/lidarr/routes.dart';
import 'package:lunasea/modules/radarr/routes.dart';
import 'package:lunasea/modules/nzbget/routes.dart';
import 'package:lunasea/modules/sabnzbd/routes.dart';
import 'package:lunasea/modules/settings.dart' show SettingsRouter;
import 'package:lunasea/modules/sonarr.dart' show SonarrRouter;
import 'package:lunasea/modules/ombi.dart' show OmbiRouter;
import 'package:lunasea/modules/tautulli.dart' show TautulliRouter;
class LunaRouter {
static FluroRouter router = FluroRouter();
LunaRouter._();
static void intialize() {
SettingsRouter.initialize(router);
SonarrRouter.initialize(router);
OmbiRouter.initialize(router);
TautulliRouter.initialize(router);
}
static TransitionType get transitionType => TransitionType.native;
static Map<String, WidgetBuilder> get routes => <String, WidgetBuilder> {
..._home,
..._search,
..._lidarr,
..._radarr,
..._sabnzbd,
..._nzbget,
};
static Map<String, WidgetBuilder> get _home => <String, WidgetBuilder> {
// /
Home.ROUTE_NAME: (context) => Home(),
// /home/*
HomeCalendar.ROUTE_NAME: (context) => HomeCalendar(refreshIndicatorKey: null),
HomeQuickAccess.ROUTE_NAME: (context) => HomeQuickAccess(),
};
static Map<String, WidgetBuilder> get _search => <String, WidgetBuilder> {
// /search
Search.ROUTE_NAME: (context) => Search(),
// /search/*
SearchSearch.ROUTE_NAME: (context) => SearchSearch(),
SearchCategories.ROUTE_NAME: (context) => SearchCategories(),
SearchSubcategories.ROUTE_NAME: (context) => SearchSubcategories(),
SearchResults.ROUTE_NAME: (context) => SearchResults(),
};
static Map<String, WidgetBuilder> get _lidarr => <String, WidgetBuilder> {
// /lidarr
Lidarr.ROUTE_NAME: (context) => Lidarr(),
// /lidarr/*
LidarrCatalogue.ROUTE_NAME: (context) => LidarrCatalogue(refreshIndicatorKey: null, refreshAllPages: null),
LidarrMissing.ROUTE_NAME: (context) => LidarrMissing(refreshIndicatorKey: null, refreshAllPages: null),
LidarrHistory.ROUTE_NAME: (context) => LidarrHistory(refreshIndicatorKey: null, refreshAllPages: null),
// /lidarr/add/*
LidarrAddSearch.ROUTE_NAME: (context) => LidarrAddSearch(),
LidarrAddDetails.ROUTE_NAME: (context) => LidarrAddDetails(),
// /lidarr/edit/*
LidarrEditArtist.ROUTE_NAME: (context) => LidarrEditArtist(),
// /lidarr/details/*
LidarrDetailsAlbum.ROUTE_NAME: (context) => LidarrDetailsAlbum(),
LidarrDetailsArtist.ROUTE_NAME: (context) => LidarrDetailsArtist(),
// /lidarr/search/*
LidarrSearchResults.ROUTE_NAME: (context) => LidarrSearchResults(),
};
static Map<String, WidgetBuilder> get _radarr => <String, WidgetBuilder> {
// /radarr
Radarr.ROUTE_NAME: (context) => Radarr(),
// /radarr/*
RadarrCatalogue.ROUTE_NAME: (context) => RadarrCatalogue(refreshIndicatorKey: null, refreshAllPages: null),
RadarrMissing.ROUTE_NAME: (context) => RadarrMissing(refreshIndicatorKey: null, refreshAllPages: null),
RadarrUpcoming.ROUTE_NAME: (context) => RadarrUpcoming(refreshIndicatorKey: null, refreshAllPages: null),
RadarrHistory.ROUTE_NAME: (context) => RadarrHistory(refreshIndicatorKey: null, refreshAllPages: null),
// /radarr/add/*
RadarrAddSearch.ROUTE_NAME: (context) => RadarrAddSearch(),
RadarrAddDetails.ROUTE_NAME: (context) => RadarrAddDetails(),
// /radarr/details/*
RadarrDetailsMovie.ROUTE_NAME: (context) => RadarrDetailsMovie(),
RadarrEditMovie.ROUTE_NAME: (context) => RadarrEditMovie(),
RadarrSearchResults.ROUTE_NAME: (context) => RadarrSearchResults(),
};
static Map<String, WidgetBuilder> get _nzbget => <String, WidgetBuilder> {
// /nzbget
NZBGet.ROUTE_NAME: (context) => NZBGet(),
// /nzbget/*
NZBGetHistory.ROUTE_NAME: (context) => NZBGetHistory(refreshIndicatorKey: null),
NZBGetQueue.ROUTE_NAME: (context) => NZBGetQueue(refreshIndicatorKey: null),
NZBGetStatistics.ROUTE_NAME: (context) => NZBGetStatistics(),
};
static Map<String, WidgetBuilder> get _sabnzbd => <String, WidgetBuilder> {
// /sabnzbd
SABnzbd.ROUTE_NAME: (context) => SABnzbd(),
// /sabnzbd/*
SABnzbdHistory.ROUTE_NAME: (context) => SABnzbdHistory(refreshIndicatorKey: null),
SABnzbdQueue.ROUTE_NAME: (context) => SABnzbdQueue(refreshIndicatorKey: null),
SABnzbdStatistics.ROUTE_NAME: (context) => SABnzbdStatistics(),
// /sabnzbd/history/*
SABnzbdHistoryStages.ROUTE_NAME: (context) => SABnzbdHistoryStages(),
};
}
export 'router/module_router.dart';
export 'router/page_router.dart';
export 'router/router.dart';

View File

@@ -0,0 +1,6 @@
import 'package:fluro/fluro.dart';
abstract class LunaModuleRouter {
/// Given the [FluroRouter], define all routes and handlers required for the module.
void defineAllRoutes(FluroRouter router);
}

View File

@@ -0,0 +1,33 @@
import 'package:fluro/fluro.dart';
import 'package:flutter/material.dart';
import 'package:lunasea/core.dart';
abstract class LunaPageRouter {
/// The full string representation of the route without the named parameters being swapped out
final String fullRoute;
/// Use [FluroRouter] to navigate to the page
Future<void> navigateTo(BuildContext context) => LunaRouter.router.navigateTo(context, fullRoute);
/// Get a string representation of the route with any named parameters inserted
String route() => fullRoute;
/// Used to define the route in the passed in router.
///
/// If the route requires no parameters, call [noParameterRouteDefinition] with the route as the [Widget] parameter.
void defineRoute(FluroRouter router);
/// Define the route in the passed in router without any parameters required.
///
/// Already has a definition that:
/// - Calls [FluroRouter.define] with the [fullRoute]
/// - Creates a basic [Handler]
/// - Sets the transition type to [LunaRouter.transitionType]
void noParameterRouteDefinition(FluroRouter router, StatefulWidget widget) => router.define(
fullRoute,
handler: Handler(handlerFunc: (context, params) => widget),
transitionType: LunaRouter.transitionType,
);
LunaPageRouter(this.fullRoute);
}

View File

@@ -0,0 +1,82 @@
import 'package:flutter/material.dart';
import 'package:fluro/fluro.dart';
import 'package:lunasea/modules/home/routes.dart';
import 'package:lunasea/modules/search/routes.dart';
import 'package:lunasea/modules/lidarr/routes.dart';
import 'package:lunasea/modules/radarr/routes.dart';
import 'package:lunasea/modules/nzbget/routes.dart';
import 'package:lunasea/modules/sabnzbd/routes.dart';
import 'package:lunasea/modules.dart' show OmbiRouter, SettingsRouter, SonarrRouter, TautulliRouter;
class LunaRouter {
static FluroRouter router = FluroRouter();
static TransitionType get transitionType => TransitionType.native;
/// Calls `defineAllRoutes()` on all module routers that implement [LunaModuleRouter].
static void intialize() {
SettingsRouter().defineAllRoutes(router);
SonarrRouter().defineAllRoutes(router);
OmbiRouter().defineAllRoutes(router);
TautulliRouter().defineAllRoutes(router);
}
/// **Will be removed when all module routers are integrated.**
///
/// Returns a map of all module routes.
static Map<String, WidgetBuilder> get routes => <String, WidgetBuilder> {
Home.ROUTE_NAME: (context) => Home(),
..._search,
..._lidarr,
..._radarr,
..._sabnzbd,
..._nzbget,
};
static Map<String, WidgetBuilder> get _search => <String, WidgetBuilder> {
// /search
Search.ROUTE_NAME: (context) => Search(),
// /search/*
SearchSearch.ROUTE_NAME: (context) => SearchSearch(),
SearchCategories.ROUTE_NAME: (context) => SearchCategories(),
SearchSubcategories.ROUTE_NAME: (context) => SearchSubcategories(),
SearchResults.ROUTE_NAME: (context) => SearchResults(),
};
static Map<String, WidgetBuilder> get _lidarr => <String, WidgetBuilder> {
// /lidarr
Lidarr.ROUTE_NAME: (context) => Lidarr(),
// /lidarr/add/*
LidarrAddSearch.ROUTE_NAME: (context) => LidarrAddSearch(),
LidarrAddDetails.ROUTE_NAME: (context) => LidarrAddDetails(),
// /lidarr/edit/*
LidarrEditArtist.ROUTE_NAME: (context) => LidarrEditArtist(),
// /lidarr/details/*
LidarrDetailsAlbum.ROUTE_NAME: (context) => LidarrDetailsAlbum(),
LidarrDetailsArtist.ROUTE_NAME: (context) => LidarrDetailsArtist(),
// /lidarr/search/*
LidarrSearchResults.ROUTE_NAME: (context) => LidarrSearchResults(),
};
static Map<String, WidgetBuilder> get _radarr => <String, WidgetBuilder> {
// /radarr
Radarr.ROUTE_NAME: (context) => Radarr(),
// /radarr/add/*
RadarrAddSearch.ROUTE_NAME: (context) => RadarrAddSearch(),
RadarrAddDetails.ROUTE_NAME: (context) => RadarrAddDetails(),
// /radarr/details/*
RadarrDetailsMovie.ROUTE_NAME: (context) => RadarrDetailsMovie(),
RadarrEditMovie.ROUTE_NAME: (context) => RadarrEditMovie(),
RadarrSearchResults.ROUTE_NAME: (context) => RadarrSearchResults(),
};
static Map<String, WidgetBuilder> get _nzbget => <String, WidgetBuilder> {
NZBGet.ROUTE_NAME: (context) => NZBGet(),
NZBGetStatistics.ROUTE_NAME: (context) => NZBGetStatistics(),
};
static Map<String, WidgetBuilder> get _sabnzbd => <String, WidgetBuilder> {
SABnzbd.ROUTE_NAME: (context) => SABnzbd(),
SABnzbdStatistics.ROUTE_NAME: (context) => SABnzbdStatistics(),
SABnzbdHistoryStages.ROUTE_NAME: (context) => SABnzbdHistoryStages(),
};
}

2
lib/core/state.dart Normal file
View File

@@ -0,0 +1,2 @@
export 'state/module_state.dart';
export 'state/state.dart';

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
abstract class LunaGlobalState extends ChangeNotifier {
abstract class LunaModuleState extends ChangeNotifier {
/// Reset the state back to the default
void reset();

View File

@@ -1,24 +1,21 @@
//Exports
export 'package:provider/provider.dart';
//Imports
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:lunasea/core.dart';
import 'package:lunasea/modules.dart' show
HomeState,
SearchState,
SettingsState,
LidarrState,
RadarrState,
SonarrState,
NZBGetState,
SABnzbdState,
OmbiState,
TautulliState;
import 'package:lunasea/modules/home/core.dart' show HomeState;
import 'package:lunasea/modules/search/core.dart' show SearchState;
import 'package:lunasea/modules/settings/core.dart' show SettingsState;
import 'package:lunasea/modules/lidarr/core.dart' show LidarrState;
import 'package:lunasea/modules/radarr/core.dart' show RadarrState;
import 'package:lunasea/modules/sonarr/core.dart' show SonarrState;
import 'package:lunasea/modules/nzbget/core.dart' show NZBGetState;
import 'package:lunasea/modules/sabnzbd/core.dart' show SABnzbdState;
import 'package:lunasea/modules/ombi/core.dart' show OmbiState;
import 'package:lunasea/modules/tautulli/core.dart' show TautulliState;
export 'package:provider/provider.dart';
class LunaProvider {
LunaProvider._();
class LunaState {
LunaState._();
/// Calls `.reset()` on all states which extend [LunaModuleState].
static void reset(BuildContext context) {
// General
Provider.of<HomeState>(context, listen: false)?.reset();
@@ -36,6 +33,9 @@ class LunaProvider {
Provider.of<TautulliState>(context, listen: false)?.reset();
}
/// Returns a [MultiProvider] with the provided child.
///
/// The [MultiProvider] has a [ChangeNotifierProvider] provider added for each module global state object.
static MultiProvider providers({ @required Widget child }) => MultiProvider(
providers: [
// General

View File

@@ -3,16 +3,14 @@ import 'package:flutter/services.dart';
import 'package:lunasea/core.dart';
class LunaTheme {
LunaTheme._();
static ThemeData get darkTheme => LunaSeaDatabaseValue.THEME_AMOLED.data
? _pureBlackTheme()
: _midnightTheme();
/// Returns the active [ThemeData] by checking the theme database value.
static ThemeData activeTheme() {
return LunaDatabaseValue.THEME_AMOLED.data ? _pureBlackTheme() : _midnightTheme();
}
/// Midnight theme (Default)
static ThemeData _midnightTheme() {
const _textStyle = TextStyle(
color: Colors.white,
);
const _textStyle = TextStyle(color: Colors.white);
return ThemeData(
brightness: Brightness.dark,
canvasColor: LunaColours.primary,
@@ -22,13 +20,7 @@ class LunaTheme {
cardColor: LunaColours.secondary,
splashColor: LunaColours.splash,
dialogBackgroundColor: LunaColours.secondary,
dividerColor: LunaColours.accent.withAlpha(0),
toggleableActiveColor: LunaColours.accent,
dividerTheme: DividerThemeData(
color: LunaColours.accent,
indent: 100.0,
endIndent: 100.0,
),
iconTheme: IconThemeData(
color: Colors.white,
),
@@ -52,10 +44,9 @@ class LunaTheme {
);
}
/// AMOLED/Pure black theme
static ThemeData _pureBlackTheme() {
const _textStyle = TextStyle(
color: Colors.white,
);
const _textStyle = TextStyle(color: Colors.white);
return ThemeData(
brightness: Brightness.dark,
canvasColor: Colors.black,
@@ -65,13 +56,7 @@ class LunaTheme {
cardColor: Colors.black,
splashColor: LunaColours.splash,
dialogBackgroundColor: Colors.black,
dividerColor: LunaColours.accent.withAlpha(0),
toggleableActiveColor: LunaColours.accent,
dividerTheme: DividerThemeData(
color: LunaColours.accent,
indent: 72.0,
endIndent: 72.0,
),
iconTheme: IconThemeData(
color: Colors.white,
),

View File

@@ -1,4 +1,3 @@
export 'types/api.dart';
export 'types/loading.dart';
export 'types/browser.dart';
export 'types/loading_state.dart';
export 'types/sorter.dart';
export 'types/state_global.dart';

View File

@@ -1,5 +0,0 @@
abstract class API {
void logWarning(String methodName, String text);
void logError(String methodName, String text, Object error, StackTrace trace, { bool uploadToSentry = true });
Future<dynamic> testConnection();
}

View File

@@ -0,0 +1,80 @@
import 'package:flutter/material.dart';
import 'package:lunasea/core.dart';
part 'browser.g.dart';
@HiveType(typeId: 11, adapterName: 'LunaBrowserAdapter')
enum LunaBrowser {
@HiveField(0)
APPLE_SAFARI,
@HiveField(1)
BRAVE_BROWSER,
@HiveField(2)
GOOGLE_CHROME,
@HiveField(3)
MICROSOFT_EDGE,
@HiveField(4)
MOZILLA_FIREFOX,
}
extension LunaBrowserExtension on LunaBrowser {
String get name {
switch(this) {
case LunaBrowser.APPLE_SAFARI: return 'Apple Safari';
case LunaBrowser.BRAVE_BROWSER: return 'Brave Browser';
case LunaBrowser.GOOGLE_CHROME: return 'Google Chrome';
case LunaBrowser.MICROSOFT_EDGE: return 'Microsoft Edge';
case LunaBrowser.MOZILLA_FIREFOX: return 'Mozilla Firefox';
default: return null;
}
}
String get key {
switch(this) {
case LunaBrowser.APPLE_SAFARI: return 'applesafari';
case LunaBrowser.BRAVE_BROWSER: return 'bravebrowser';
case LunaBrowser.GOOGLE_CHROME: return 'googlechrome';
case LunaBrowser.MICROSOFT_EDGE: return 'microsoftedge';
case LunaBrowser.MOZILLA_FIREFOX: return 'mozillafirefox';
default: return null;
}
}
LunaBrowser fromKey(String key) {
switch(key) {
case 'applesafari': return LunaBrowser.APPLE_SAFARI;
case 'bravebrowser': return LunaBrowser.BRAVE_BROWSER;
case 'googlechrome': return LunaBrowser.GOOGLE_CHROME;
case 'microsoftedge': return LunaBrowser.MICROSOFT_EDGE;
case 'mozillafirefox': return LunaBrowser.MOZILLA_FIREFOX;
default: return null;
}
}
IconData get icon {
switch(this) {
case LunaBrowser.APPLE_SAFARI: return CustomIcons.safari;
case LunaBrowser.BRAVE_BROWSER: return CustomIcons.bravebrowser;
case LunaBrowser.GOOGLE_CHROME: return CustomIcons.chrome;
case LunaBrowser.MICROSOFT_EDGE: return CustomIcons.microsoftedge;
case LunaBrowser.MOZILLA_FIREFOX: return CustomIcons.firefox;
default: return null;
}
}
String formatted(String url) {
bool isHttps = url.substring(0, 8) == 'https://';
switch(this) {
case LunaBrowser.BRAVE_BROWSER: return 'brave://open-url?url=$url';
case LunaBrowser.GOOGLE_CHROME: return isHttps
? url.replaceFirst('https://', 'googlechromes://')
: url.replaceFirst('http://', 'googlechrome://');
case LunaBrowser.MICROSOFT_EDGE: return isHttps
? url.replaceFirst('https://', 'microsoft-edge-https://')
: url.replaceFirst('http://', 'microsoft-edge-http://');
case LunaBrowser.MOZILLA_FIREFOX: return 'firefox://open-url?url=$url';
case LunaBrowser.APPLE_SAFARI:
default: return url;
}
}
}

View File

@@ -1,49 +1,49 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'links.dart';
part of 'browser.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class LSBrowsersAdapter extends TypeAdapter<LSBrowsers> {
class LunaBrowserAdapter extends TypeAdapter<LunaBrowser> {
@override
final int typeId = 11;
@override
LSBrowsers read(BinaryReader reader) {
LunaBrowser read(BinaryReader reader) {
switch (reader.readByte()) {
case 0:
return LSBrowsers.APPLE_SAFARI;
return LunaBrowser.APPLE_SAFARI;
case 1:
return LSBrowsers.BRAVE_BROWSER;
return LunaBrowser.BRAVE_BROWSER;
case 2:
return LSBrowsers.GOOGLE_CHROME;
return LunaBrowser.GOOGLE_CHROME;
case 3:
return LSBrowsers.MICROSOFT_EDGE;
return LunaBrowser.MICROSOFT_EDGE;
case 4:
return LSBrowsers.MOZILLA_FIREFOX;
return LunaBrowser.MOZILLA_FIREFOX;
default:
return null;
}
}
@override
void write(BinaryWriter writer, LSBrowsers obj) {
void write(BinaryWriter writer, LunaBrowser obj) {
switch (obj) {
case LSBrowsers.APPLE_SAFARI:
case LunaBrowser.APPLE_SAFARI:
writer.writeByte(0);
break;
case LSBrowsers.BRAVE_BROWSER:
case LunaBrowser.BRAVE_BROWSER:
writer.writeByte(1);
break;
case LSBrowsers.GOOGLE_CHROME:
case LunaBrowser.GOOGLE_CHROME:
writer.writeByte(2);
break;
case LSBrowsers.MICROSOFT_EDGE:
case LunaBrowser.MICROSOFT_EDGE:
writer.writeByte(3);
break;
case LSBrowsers.MOZILLA_FIREFOX:
case LunaBrowser.MOZILLA_FIREFOX:
writer.writeByte(4);
break;
}
@@ -55,7 +55,7 @@ class LSBrowsersAdapter extends TypeAdapter<LSBrowsers> {
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is LSBrowsersAdapter &&
other is LunaBrowserAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

View File

@@ -17,7 +17,6 @@ export 'ui/messages.dart';
export 'ui/navigation_bar.dart';
export 'ui/network_image.dart';
export 'ui/refresh_indicator.dart';
export 'ui/rive.dart';
export 'ui/shape.dart';
export 'ui/snackbar.dart';
export 'ui/sticky_header.dart';

View File

@@ -11,7 +11,7 @@ Widget LSAppBarDropdown({
? LunaAppBar(context: context, title: title, actions: actions, hideLeading: true)
: AppBar(
title: PopupMenuButton<String>(
shape: LunaSeaDatabaseValue.THEME_AMOLED.data && LunaSeaDatabaseValue.THEME_AMOLED_BORDER.data
shape: LunaDatabaseValue.THEME_AMOLED.data && LunaDatabaseValue.THEME_AMOLED_BORDER.data
? LSRoundedShapeWithBorder()
: LSRoundedShape(),
child: Wrap(
@@ -28,7 +28,7 @@ Widget LSAppBarDropdown({
),
],
),
onSelected: (result) => LunaProfile.changeProfile(context, result),
onSelected: (result) => LunaProfile().safelyChangeProfiles(context, result),
itemBuilder: (context) {
return <PopupMenuEntry<String>>[for(String profile in profiles) PopupMenuItem<String>(
value: profile,

View File

@@ -19,8 +19,8 @@ class LSCard extends StatelessWidget {
@override
Widget build(BuildContext context) => ValueListenableBuilder(
valueListenable: Database.lunaSeaBox.listenable(keys: [
LunaSeaDatabaseValue.THEME_AMOLED.key,
LunaSeaDatabaseValue.THEME_AMOLED_BORDER.key,
LunaDatabaseValue.THEME_AMOLED.key,
LunaDatabaseValue.THEME_AMOLED_BORDER.key,
]),
builder: (context, box, widget) => Card(
child: Container(
@@ -34,7 +34,7 @@ class LSCard extends StatelessWidget {
color: color == null
? Theme.of(context).primaryColor
: color,
shape: LunaSeaDatabaseValue.THEME_AMOLED.data && LunaSeaDatabaseValue.THEME_AMOLED_BORDER.data
shape: LunaDatabaseValue.THEME_AMOLED.data && LunaDatabaseValue.THEME_AMOLED_BORDER.data
? LSRoundedShapeWithBorder()
: LSRoundedShape(),
),

View File

@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:lunasea/core.dart';
import 'package:lunasea/core/constants.dart';
class LSCardTile extends StatelessWidget {
final Widget title;
@@ -34,8 +33,8 @@ class LSCardTile extends StatelessWidget {
@override
Widget build(BuildContext context) => ValueListenableBuilder(
valueListenable: Database.lunaSeaBox.listenable(keys: [
LunaSeaDatabaseValue.THEME_AMOLED.key,
LunaSeaDatabaseValue.THEME_AMOLED_BORDER.key,
LunaDatabaseValue.THEME_AMOLED.key,
LunaDatabaseValue.THEME_AMOLED_BORDER.key,
]),
builder: (context, box, widget) => Card(
child: Container(
@@ -63,7 +62,7 @@ class LSCardTile extends StatelessWidget {
: Constants.UI_CARD_MARGIN
: customMargin,
elevation: Constants.UI_ELEVATION,
shape: LunaSeaDatabaseValue.THEME_AMOLED.data && LunaSeaDatabaseValue.THEME_AMOLED_BORDER.data
shape: LunaDatabaseValue.THEME_AMOLED.data && LunaDatabaseValue.THEME_AMOLED_BORDER.data
? LSRoundedShapeWithBorder()
: LSRoundedShape(),
color: color == null

View File

@@ -62,7 +62,7 @@ class _State extends State<LSDescriptionBlock> {
],
),
borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS),
onTap: () => LunaDialogs.textPreview(context, widget.title, widget.description.trim() ?? 'No summary is available.'),
onTap: () => LunaDialogs().textPreview(context, widget.title, widget.description.trim() ?? 'No summary is available.'),
),
);
}

View File

@@ -210,7 +210,7 @@ abstract class LSDialog {
title: LSDialog.title(text: title),
content: LSDialog.content(children: content),
contentPadding: contentPadding,
shape: LunaSeaDatabaseValue.THEME_AMOLED.data && LunaSeaDatabaseValue.THEME_AMOLED_BORDER.data
shape: LunaDatabaseValue.THEME_AMOLED.data && LunaDatabaseValue.THEME_AMOLED_BORDER.data
? LSRoundedShapeWithBorder()
: LSRoundedShape(),
),

View File

@@ -6,5 +6,7 @@ class LSDivider extends StatelessWidget {
Widget build(BuildContext context) => Divider(
thickness: 1.0,
color: LunaColours.splash,
indent: 60.0,
endIndent: 60.0,
);
}

View File

@@ -4,6 +4,6 @@ import 'package:lunasea/core.dart';
// ignore: non_constant_identifier_names
Widget LSDrawer({
@required String page,
}) => LunaSeaDatabaseValue.DRAWER_GROUP_MODULES.data
}) => LunaDatabaseValue.DRAWER_GROUP_MODULES.data
? LSDrawerCategories(page: page)
: LSDrawerNoCategories(page: page);

View File

@@ -12,12 +12,12 @@ class LSDrawerCategories extends StatelessWidget {
@override
Widget build(BuildContext context) => ValueListenableBuilder(
valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.ENABLED_PROFILE.key]),
valueListenable: Database.lunaSeaBox.listenable(keys: [LunaDatabaseValue.ENABLED_PROFILE.key]),
builder: (context, lunaBox, widget) {
return ValueListenableBuilder(
valueListenable: Database.indexersBox.listenable(),
builder: (context, indexerBox, widget) {
ProfileHiveObject profile = Database.profilesBox.get(lunaBox.get(LunaSeaDatabaseValue.ENABLED_PROFILE.key));
ProfileHiveObject profile = Database.profilesBox.get(lunaBox.get(LunaDatabaseValue.ENABLED_PROFILE.key));
return Drawer(
child: ListView(
children: _getDrawerEntries(context, profile, (indexerBox as Box).length > 0),
@@ -61,14 +61,14 @@ class LSDrawerCategories extends StatelessWidget {
fontSize: Constants.UI_FONT_SIZE_SUBTITLE,
),
),
initiallyExpanded: LunaSeaDatabaseValue.DRAWER_EXPAND_AUTOMATION.data,
initiallyExpanded: LunaDatabaseValue.DRAWER_EXPAND_AUTOMATION.data,
children: List.generate(
Database.currentProfileObject.enabledAutomationModules.length,
(index) => _buildEntry(
context: context,
route: Constants.MODULE_MAP[Database.currentProfileObject.enabledAutomationModules[index]].route,
icon: Constants.MODULE_MAP[Database.currentProfileObject.enabledAutomationModules[index]].icon,
title: Constants.MODULE_MAP[Database.currentProfileObject.enabledAutomationModules[index]].name,
route: Constants.MODULE_METADATA[Database.currentProfileObject.enabledAutomationModules[index]].route,
icon: Constants.MODULE_METADATA[Database.currentProfileObject.enabledAutomationModules[index]].icon,
title: Constants.MODULE_METADATA[Database.currentProfileObject.enabledAutomationModules[index]].name,
padLeft: true,
),
),
@@ -81,14 +81,14 @@ class LSDrawerCategories extends StatelessWidget {
fontSize: Constants.UI_FONT_SIZE_SUBTITLE,
),
),
initiallyExpanded: LunaSeaDatabaseValue.DRAWER_EXPAND_CLIENTS.data,
initiallyExpanded: LunaDatabaseValue.DRAWER_EXPAND_CLIENTS.data,
children: List.generate(
Database.currentProfileObject.enabledClientModules.length,
(index) => _buildEntry(
context: context,
route: Constants.MODULE_MAP[Database.currentProfileObject.enabledClientModules[index]].route,
icon: Constants.MODULE_MAP[Database.currentProfileObject.enabledClientModules[index]].icon,
title: Constants.MODULE_MAP[Database.currentProfileObject.enabledClientModules[index]].name,
route: Constants.MODULE_METADATA[Database.currentProfileObject.enabledClientModules[index]].route,
icon: Constants.MODULE_METADATA[Database.currentProfileObject.enabledClientModules[index]].icon,
title: Constants.MODULE_METADATA[Database.currentProfileObject.enabledClientModules[index]].name,
padLeft: true,
),
),
@@ -101,14 +101,14 @@ class LSDrawerCategories extends StatelessWidget {
fontSize: Constants.UI_FONT_SIZE_SUBTITLE,
),
),
initiallyExpanded: LunaSeaDatabaseValue.DRAWER_EXPAND_MONITORING.data,
initiallyExpanded: LunaDatabaseValue.DRAWER_EXPAND_MONITORING.data,
children: List.generate(
Database.currentProfileObject.enabledMonitoringModules.length,
(index) => _buildEntry(
context: context,
route: Constants.MODULE_MAP[Database.currentProfileObject.enabledMonitoringModules[index]].route,
icon: Constants.MODULE_MAP[Database.currentProfileObject.enabledMonitoringModules[index]].icon,
title: Constants.MODULE_MAP[Database.currentProfileObject.enabledMonitoringModules[index]].name,
route: Constants.MODULE_METADATA[Database.currentProfileObject.enabledMonitoringModules[index]].route,
icon: Constants.MODULE_METADATA[Database.currentProfileObject.enabledMonitoringModules[index]].icon,
title: Constants.MODULE_METADATA[Database.currentProfileObject.enabledMonitoringModules[index]].name,
padLeft: true,
),
),
@@ -152,9 +152,9 @@ class LSDrawerCategories extends StatelessWidget {
@required BuildContext context,
}) {
return ListTile(
leading: LSIcon(icon: Constants.MODULE_MAP[WakeOnLANConstants.MODULE_KEY].icon),
leading: LSIcon(icon: Constants.MODULE_METADATA[WakeOnLANConstants.MODULE_KEY].icon),
title: Text(
Constants.MODULE_MAP[WakeOnLANConstants.MODULE_KEY].name,
Constants.MODULE_METADATA[WakeOnLANConstants.MODULE_KEY].name,
style: TextStyle(
color: Colors.white,
fontSize: Constants.UI_FONT_SIZE_SUBTITLE,

View File

@@ -5,19 +5,19 @@ import 'package:lunasea/core.dart';
Widget LSDrawerHeader() => UserAccountsDrawerHeader(
accountName: LSTitle(text: Constants.APPLICATION_NAME),
accountEmail: ValueListenableBuilder(
valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.ENABLED_PROFILE.key]),
valueListenable: Database.lunaSeaBox.listenable(keys: [LunaDatabaseValue.ENABLED_PROFILE.key]),
builder: (context, lunaBox, widget) => ValueListenableBuilder(
valueListenable: Database.profilesBox.listenable(),
builder: (context, profilesBox, widget) => Padding(
child: PopupMenuButton<String>(
shape: LunaSeaDatabaseValue.THEME_AMOLED.data && LunaSeaDatabaseValue.THEME_AMOLED_BORDER.data
shape: LunaDatabaseValue.THEME_AMOLED.data && LunaDatabaseValue.THEME_AMOLED_BORDER.data
? LSRoundedShapeWithBorder()
: LSRoundedShape(),
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
LSSubtitle(
text: LunaSeaDatabaseValue.ENABLED_PROFILE.data,
text: LunaDatabaseValue.ENABLED_PROFILE.data,
),
LSIcon(
icon: Icons.arrow_drop_down,
@@ -26,7 +26,7 @@ Widget LSDrawerHeader() => UserAccountsDrawerHeader(
),
],
),
onSelected: (result) => LunaProfile.changeProfile(context, result),
onSelected: (result) => LunaProfile().safelyChangeProfiles(context, result),
itemBuilder: (context) {
return <PopupMenuEntry<String>>[for(String profile in (profilesBox as Box).keys) PopupMenuItem<String>(
value: profile,

View File

@@ -12,12 +12,12 @@ class LSDrawerNoCategories extends StatelessWidget {
@override
Widget build(BuildContext context) => ValueListenableBuilder(
valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.ENABLED_PROFILE.key]),
valueListenable: Database.lunaSeaBox.listenable(keys: [LunaDatabaseValue.ENABLED_PROFILE.key]),
builder: (context, lunaBox, widget) {
return ValueListenableBuilder(
valueListenable: Database.indexersBox.listenable(),
builder: (context, indexerBox, widget) {
ProfileHiveObject profile = Database.profilesBox.get(lunaBox.get(LunaSeaDatabaseValue.ENABLED_PROFILE.key));
ProfileHiveObject profile = Database.profilesBox.get(lunaBox.get(LunaDatabaseValue.ENABLED_PROFILE.key));
return Drawer(
child: ListView(
children: _getDrawerEntries(context, profile, (indexerBox as Box).length > 0),
@@ -57,27 +57,27 @@ class LSDrawerNoCategories extends StatelessWidget {
Database.currentProfileObject.enabledAutomationModules.length,
(index) => _buildEntry(
context: context,
route: Constants.MODULE_MAP[Database.currentProfileObject.enabledAutomationModules[index]].route,
icon: Constants.MODULE_MAP[Database.currentProfileObject.enabledAutomationModules[index]].icon,
title: Constants.MODULE_MAP[Database.currentProfileObject.enabledAutomationModules[index]].name,
route: Constants.MODULE_METADATA[Database.currentProfileObject.enabledAutomationModules[index]].route,
icon: Constants.MODULE_METADATA[Database.currentProfileObject.enabledAutomationModules[index]].icon,
title: Constants.MODULE_METADATA[Database.currentProfileObject.enabledAutomationModules[index]].name,
),
),
...List.generate(
Database.currentProfileObject.enabledClientModules.length,
(index) => _buildEntry(
context: context,
route: Constants.MODULE_MAP[Database.currentProfileObject.enabledClientModules[index]].route,
icon: Constants.MODULE_MAP[Database.currentProfileObject.enabledClientModules[index]].icon,
title: Constants.MODULE_MAP[Database.currentProfileObject.enabledClientModules[index]].name,
route: Constants.MODULE_METADATA[Database.currentProfileObject.enabledClientModules[index]].route,
icon: Constants.MODULE_METADATA[Database.currentProfileObject.enabledClientModules[index]].icon,
title: Constants.MODULE_METADATA[Database.currentProfileObject.enabledClientModules[index]].name,
),
),
...List.generate(
Database.currentProfileObject.enabledMonitoringModules.length,
(index) => _buildEntry(
context: context,
route: Constants.MODULE_MAP[Database.currentProfileObject.enabledMonitoringModules[index]].route,
icon: Constants.MODULE_MAP[Database.currentProfileObject.enabledMonitoringModules[index]].icon,
title: Constants.MODULE_MAP[Database.currentProfileObject.enabledMonitoringModules[index]].name,
route: Constants.MODULE_METADATA[Database.currentProfileObject.enabledMonitoringModules[index]].route,
icon: Constants.MODULE_METADATA[Database.currentProfileObject.enabledMonitoringModules[index]].icon,
title: Constants.MODULE_METADATA[Database.currentProfileObject.enabledMonitoringModules[index]].name,
),
),
];
@@ -116,9 +116,9 @@ class LSDrawerNoCategories extends StatelessWidget {
@required BuildContext context,
}) {
return ListTile(
leading: LSIcon(icon: Constants.MODULE_MAP[WakeOnLANConstants.MODULE_KEY].icon),
leading: LSIcon(icon: Constants.MODULE_METADATA[WakeOnLANConstants.MODULE_KEY].icon),
title: Text(
Constants.MODULE_MAP[WakeOnLANConstants.MODULE_KEY].name,
Constants.MODULE_METADATA[WakeOnLANConstants.MODULE_KEY].name,
style: TextStyle(
color: Colors.white,
fontSize: Constants.UI_FONT_SIZE_SUBTITLE,

View File

@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:lunasea/core/constants.dart';
import 'package:lunasea/core.dart';
class LSGenericMessage extends StatelessWidget {
@@ -19,8 +18,8 @@ class LSGenericMessage extends StatelessWidget {
@override
Widget build(BuildContext context) => ValueListenableBuilder(
valueListenable: Database.lunaSeaBox.listenable(keys: [
LunaSeaDatabaseValue.THEME_AMOLED.key,
LunaSeaDatabaseValue.THEME_AMOLED_BORDER.key,
LunaDatabaseValue.THEME_AMOLED.key,
LunaDatabaseValue.THEME_AMOLED_BORDER.key,
]),
builder: (context, box, child) => Column(
mainAxisAlignment: MainAxisAlignment.center,
@@ -30,7 +29,7 @@ class LSGenericMessage extends StatelessWidget {
child: child,
margin: EdgeInsets.symmetric(horizontal: 12.0, vertical: 6.0),
elevation: Constants.UI_ELEVATION,
shape: LunaSeaDatabaseValue.THEME_AMOLED.data && LunaSeaDatabaseValue.THEME_AMOLED_BORDER.data
shape: LunaDatabaseValue.THEME_AMOLED.data && LunaDatabaseValue.THEME_AMOLED_BORDER.data
? LSRoundedShapeWithBorder()
: LSRoundedShape(),
),

View File

@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:lunasea/core/constants.dart';
import './generic.dart';
import 'package:lunasea/core.dart';
class LSNotEnabled extends StatelessWidget {
final String service;

View File

@@ -45,8 +45,8 @@ class _State extends State<LSTypewriterMessage> with TickerProviderStateMixin {
@override
Widget build(BuildContext context) => ValueListenableBuilder(
valueListenable: Database.lunaSeaBox.listenable(keys: [
LunaSeaDatabaseValue.THEME_AMOLED.key,
LunaSeaDatabaseValue.THEME_AMOLED_BORDER.key,
LunaDatabaseValue.THEME_AMOLED.key,
LunaDatabaseValue.THEME_AMOLED_BORDER.key,
]),
builder: (context, box, child) => Column(
mainAxisAlignment: MainAxisAlignment.center,
@@ -56,7 +56,7 @@ class _State extends State<LSTypewriterMessage> with TickerProviderStateMixin {
child: child,
margin: EdgeInsets.symmetric(horizontal: 12.0, vertical: 6.0),
elevation: Constants.UI_ELEVATION,
shape: LunaSeaDatabaseValue.THEME_AMOLED.data && LunaSeaDatabaseValue.THEME_AMOLED_BORDER.data
shape: LunaDatabaseValue.THEME_AMOLED.data && LunaDatabaseValue.THEME_AMOLED_BORDER.data
? LSRoundedShapeWithBorder()
: LSRoundedShape(),
),

View File

@@ -1,8 +0,0 @@
class RiveAnimations {
RiveAnimations._();
static const CODER = {
'path': 'assets/rive/coding.flr',
'animation': 'coding',
};
}

View File

@@ -14,7 +14,7 @@ const _LEFT_PADDING = 8.0;
Future<void> LSSnackBar({
@required BuildContext context,
@required String title,
String message = Constants.CHECK_LOGS_MESSAGE,
String message = LunaLogger.CHECK_LOGS_MESSAGE,
Duration duration,
SNACKBAR_TYPE type = SNACKBAR_TYPE.info,
bool showButton = false,
@@ -44,7 +44,7 @@ Future<void> LSSnackBar({
horizontalDismissDirection: HorizontalDismissDirection.horizontal,
margin: EdgeInsets.all(8.0),
borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS),
borderColor: LunaSeaDatabaseValue.THEME_AMOLED.data && LunaSeaDatabaseValue.THEME_AMOLED_BORDER.data
borderColor: LunaDatabaseValue.THEME_AMOLED.data && LunaDatabaseValue.THEME_AMOLED_BORDER.data
? Colors.white12
: Colors.transparent,
child: FlashBar(

View File

@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:lunasea/core.dart';
import 'package:lunasea/core/ui/colors.dart';
class LSHeader extends StatelessWidget {
final String text;

View File

@@ -1,6 +1,11 @@
import 'package:uuid/uuid.dart';
class LunaUUID {
static final _generator = Uuid();
static String get uuid => _generator.v4();
static final Uuid _generator = Uuid();
/// Generate a new v4 UUID with the base configuration.
String get uuid => _generator.v4();
/// Return the [Uuid] instance.
Uuid get generator => _generator;
}

View File

@@ -2,30 +2,47 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:lunasea/core.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
/// LunaSea Entry Point: Initialize & Run Application
/// Runs app in guarded zone to attempt to capture fatal (crashing) errors
void main() async {
await _init();
runZonedGuarded<void>(
() => runApp(LunaBIOS()),
(Object error, StackTrace stack) => LunaLogger.fatal(error, stack),
);
}
///
/// Runs app in Sentry guarded zone to attempt to capture fatal (crashing) errors
Future<void> main() async => await SentryFlutter.init(
(options) => options
..dsn = LunaLogger.SENTRY_DSN,
appRunner: () async {
await _init();
runApp(LunaBIOS());
}
);
/// Initializes LunaSea before running the BIOS Widget.
///
/// Sets up (in order):
/// - System UI Overlay Styling
/// - Logger
/// - Network
/// - Image Cache
/// - Router
/// - Firebase
/// - IAPs
/// - Database
///
/// Does not call [WidgetsFlutterBinding.ensureInitialized()] as that is called during Sentry's initialization.
/// If Sentry is removed, you should call that method to prevent black-screen flashing on launch.
Future<void> _init() async {
WidgetsFlutterBinding.ensureInitialized();
//Set system UI style (navbar, statusbar)
//Set system UI overlay style (navbar, statusbar)
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
systemNavigationBarColor: Colors.black,
systemNavigationBarDividerColor: Colors.black,
statusBarColor: Colors.transparent,
));
//LunaSea initialization
LunaNetworking.initialize();
LunaLogger.initialize();
LunaNetworking.initialize();
LunaImageCache.initialize();
LunaRouter.intialize();
await LunaFirebase.initialize();
await LunaInAppPurchases.initialize();
await Database.initialize();
}
@@ -39,17 +56,20 @@ class LunaBIOS extends StatefulWidget {
class _State extends State<LunaBIOS> {
@override
Widget build(BuildContext context) => LunaProvider.providers(
Widget build(BuildContext context) => LunaState.providers(
child: ValueListenableBuilder(
valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.THEME_AMOLED.key]),
valueListenable: Database.lunaSeaBox.listenable(keys: [LunaDatabaseValue.THEME_AMOLED.key]),
builder: (context, box, _) {
return MaterialApp(
navigatorKey: LunaBIOS.navigatorKey,
routes: LunaRouter.routes,
onGenerateRoute: LunaRouter.router.generator,
darkTheme: LunaTheme.darkTheme,
theme: LunaTheme.darkTheme,
darkTheme: LunaTheme.activeTheme(),
theme: LunaTheme.activeTheme(),
title: Constants.APPLICATION_NAME,
navigatorKey: LunaBIOS.navigatorKey,
navigatorObservers: [
SentryNavigatorObserver(),
],
);
}
),

View File

@@ -1,4 +1,4 @@
import 'package:lunasea/core/database/database.dart';
import 'package:lunasea/core.dart';
import 'package:table_calendar/table_calendar.dart';
part 'calendar_starting_day.g.dart';
@@ -31,7 +31,7 @@ extension CalendarStartingDayExtension on CalendarStartingDay {
case CalendarStartingDay.FRIDAY: return StartingDayOfWeek.friday;
case CalendarStartingDay.SATURDAY: return StartingDayOfWeek.saturday;
case CalendarStartingDay.SUNDAY: return StartingDayOfWeek.sunday;
default: return StartingDayOfWeek.monday;
default: return null;
}
}
@@ -44,7 +44,33 @@ extension CalendarStartingDayExtension on CalendarStartingDay {
case CalendarStartingDay.FRIDAY: return 'Friday';
case CalendarStartingDay.SATURDAY: return 'Saturday';
case CalendarStartingDay.SUNDAY: return 'Sunday';
default: return 'Unknown Starting Day';
default: return null;
}
}
String get key {
switch(this) {
case CalendarStartingDay.MONDAY: return 'mon';
case CalendarStartingDay.TUESDAY: return 'tue';
case CalendarStartingDay.WEDNESDAY: return 'wed';
case CalendarStartingDay.THURSDAY: return 'thu';
case CalendarStartingDay.FRIDAY: return 'fri';
case CalendarStartingDay.SATURDAY: return 'sat';
case CalendarStartingDay.SUNDAY: return 'sun';
default: return null;
}
}
CalendarStartingDay fromKey(String key) {
switch(key) {
case 'mon': return CalendarStartingDay.MONDAY;
case 'tue': return CalendarStartingDay.TUESDAY;
case 'wed': return CalendarStartingDay.WEDNESDAY;
case 'thu': return CalendarStartingDay.THURSDAY;
case 'fri': return CalendarStartingDay.FRIDAY;
case 'sat': return CalendarStartingDay.SATURDAY;
case 'sun': return CalendarStartingDay.SUNDAY;
default: return null;
}
}
}

View File

@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:lunasea/core/database/database.dart';
import 'package:lunasea/core.dart';
import 'package:table_calendar/table_calendar.dart';
part 'calendar_starting_size.g.dart';
@@ -17,28 +17,46 @@ enum CalendarStartingSize {
extension CalendarStartingSizeExtension on CalendarStartingSize {
CalendarFormat get data {
switch(this) {
case CalendarStartingSize.ONE_WEEK: return CalendarFormat.week;
case CalendarStartingSize.TWO_WEEKS: return CalendarFormat.twoWeeks;
case CalendarStartingSize.ONE_MONTH: return CalendarFormat.month;
case CalendarStartingSize.ONE_WEEK:
default: return CalendarFormat.week;
default: return null;
}
}
String get name {
switch(this) {
case CalendarStartingSize.ONE_WEEK: return 'One Week';
case CalendarStartingSize.TWO_WEEKS: return 'Two Weeks';
case CalendarStartingSize.ONE_MONTH: return 'One Month';
case CalendarStartingSize.ONE_WEEK:
default: return 'One Week';
default: return null;
}
}
String get key {
switch(this) {
case CalendarStartingSize.ONE_WEEK: return 'oneweek';
case CalendarStartingSize.TWO_WEEKS: return 'twoweeks';
case CalendarStartingSize.ONE_MONTH: return 'onemonth';
default: return null;
}
}
IconData get icon {
switch(this) {
case CalendarStartingSize.ONE_WEEK: return Icons.photo_size_select_small;
case CalendarStartingSize.TWO_WEEKS: return Icons.photo_size_select_large;
case CalendarStartingSize.ONE_MONTH: return Icons.photo_size_select_actual;
case CalendarStartingSize.ONE_WEEK:
default: return Icons.photo_size_select_small;
default: return null;
}
}
CalendarStartingSize fromKey(String key) {
switch(key) {
case 'oneweek': return CalendarStartingSize.ONE_WEEK;
case 'twoweeks': return CalendarStartingSize.TWO_WEEKS;
case 'onemonth': return CalendarStartingSize.ONE_MONTH;
default: return null;
}
}
}

View File

@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:lunasea/core.dart';
import 'package:lunasea/core/database/database.dart';
part 'calendar_starting_type.g.dart';
@@ -13,27 +12,35 @@ enum CalendarStartingType {
}
extension CalendarStartingTypeExtension on CalendarStartingType {
String get data {
switch(this) {
case CalendarStartingType.SCHEDULE: return 'schedule';
case CalendarStartingType.CALENDAR:
default: return 'calendar';
}
}
String get name {
switch(this) {
case CalendarStartingType.SCHEDULE: return 'Schedule';
case CalendarStartingType.CALENDAR:
default: return 'Calendar';
case CalendarStartingType.CALENDAR: return 'Calendar';
default: return null;
}
}
String get key {
switch(this) {
case CalendarStartingType.SCHEDULE: return 'schedule';
case CalendarStartingType.CALENDAR: return 'calendar';
default: return null;
}
}
IconData get icon {
switch(this) {
case CalendarStartingType.SCHEDULE: return Icons.calendar_view_day;
case CalendarStartingType.CALENDAR:
default: return CustomIcons.calendar;
case CalendarStartingType.CALENDAR: return CustomIcons.calendar;
default: return null;
}
}
CalendarStartingType fromKey(String key) {
switch(key) {
case 'schedule': return CalendarStartingType.SCHEDULE;
case 'calendar': return CalendarStartingType.CALENDAR;
default: return null;
}
}
}

View File

@@ -4,7 +4,7 @@ import 'package:intl/intl.dart';
import 'package:lunasea/core.dart';
import 'package:lunasea/modules/home.dart';
class CalendarAPI extends API {
class CalendarAPI {
final Map<String, dynamic> lidarr;
final Map<String, dynamic> radarr;
final Map<String, dynamic> sonarr;
@@ -23,22 +23,7 @@ class CalendarAPI extends API {
);
}
void logError(String methodName, String text, Object error, StackTrace trace, {
bool uploadToSentry = true,
}) => LunaLogger.error(
'package:lunasea/core/api/calendar/api.dart',
methodName,
'Home: $text',
error,
trace,
uploadToSentry: uploadToSentry,
);
void logWarning(String methodName, String text) => LunaLogger.warning(
'package:lunasea/core/api/calendar/api.dart',
methodName,
'Home: $text',
);
void logError(String text, Object error, StackTrace trace) => LunaLogger().error('Home: $text', error, trace);
Future<bool> testConnection() async => true;
@@ -80,7 +65,7 @@ class CalendarAPI extends API {
Response response = await _client.get('calendar');
if(response.data.length > 0) {
for(var entry in response.data) {
DateTime date = DateTime.tryParse(entry['releaseDate'] ?? '')?.toLocal()?.lsDateTime_floor();
DateTime date = DateTime.tryParse(entry['releaseDate'] ?? '')?.toLocal()?.lunaFloor;
if(date != null) {
List day = map[date] ?? [];
day.add(CalendarLidarrData(
@@ -94,10 +79,8 @@ class CalendarAPI extends API {
}
}
}
} on DioError catch (error, stack) {
logError('_getLidarrUpcoming', 'Failed to fetch Lidarr upcoming content', error, stack, uploadToSentry: false);
} catch (error, stack) {
logError('_getLidarrUpcoming', 'Failed to fetch Lidarr upcoming content', error, stack);
logError('Failed to fetch Lidarr upcoming content', error, stack);
}
return;
}
@@ -121,7 +104,7 @@ class CalendarAPI extends API {
Response response = await _client.get('calendar');
if(response.data.length > 0) {
for(var entry in response.data) {
DateTime date = DateTime.tryParse(entry['physicalRelease'] ?? '')?.toLocal()?.lsDateTime_floor();
DateTime date = DateTime.tryParse(entry['physicalRelease'] ?? '')?.toLocal()?.lunaFloor;
if(date != null) {
List day = map[date] ?? [];
day.add(CalendarRadarrData(
@@ -136,10 +119,8 @@ class CalendarAPI extends API {
}
}
}
} on DioError catch (error, stack) {
logError('_getRadarrUpcoming', 'Failed to fetch Radarr upcoming content', error, stack, uploadToSentry: false);
} catch (error, stack) {
logError('_getRadarrUpcoming', 'Failed to fetch Radarr upcoming content', error, stack);
logError('Failed to fetch Radarr upcoming content', error, stack);
}
return;
}
@@ -163,7 +144,7 @@ class CalendarAPI extends API {
Response response = await _client.get('calendar');
if(response.data.length > 0) {
for(var entry in response.data) {
DateTime date = DateTime.tryParse(entry['airDateUtc'] ?? '')?.toLocal()?.lsDateTime_floor();
DateTime date = DateTime.tryParse(entry['airDateUtc'] ?? '')?.toLocal()?.lunaFloor;
if(date != null) {
List day = map[date] ?? [];
day.add(CalendarSonarrData(
@@ -181,10 +162,8 @@ class CalendarAPI extends API {
}
}
}
} on DioError catch (error, stack) {
logError('_getSonarrUpcoming', 'Failed to fetch Sonarr upcoming content', error, stack, uploadToSentry: false);
} catch (error, stack) {
logError('_getSonarrUpcoming', 'Failed to fetch Sonarr upcoming content', error, stack);
logError('Failed to fetch Sonarr upcoming content', error, stack);
}
return;
}

View File

@@ -72,7 +72,7 @@ class CalendarLidarrData extends CalendarData {
Future<void> trailingOnPress(BuildContext context) async {
await LidarrAPI.from(Database.currentProfileObject).searchAlbums([id])
.then((_) => LSSnackBar(context: context, title: 'Searching...', message: albumTitle))
.catchError((_) => LSSnackBar(context: context, title: 'Failed to Search', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure));
.catchError((_) => LSSnackBar(context: context, title: 'Failed to Search', message: LunaLogger.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure));
}
@override

View File

@@ -19,9 +19,7 @@ class CalendarRadarrData extends CalendarData {
@required this.runtime,
}): super(id, title);
String get runtimeString {
return runtime.lsTime_runtimeString(dot: true);
}
String get runtimeString => '\t${Constants.TEXT_BULLET}\t${runtime.lunaRuntime()}';
TextSpan get subtitle => TextSpan(
style: TextStyle(
@@ -75,7 +73,7 @@ class CalendarRadarrData extends CalendarData {
Future<void> trailingOnPress(BuildContext context) async {
await RadarrAPI.from(Database.currentProfileObject).automaticSearchMovie(id)
.then((_) => LSSnackBar(context: context, title: 'Searching...', message: title))
.catchError((_) => LSSnackBar(context: context, title: 'Failed to Search', message: Constants.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure));
.catchError((_) => LSSnackBar(context: context, title: 'Failed to Search', message: LunaLogger.CHECK_LOGS_MESSAGE, type: SNACKBAR_TYPE.failure));
}
@override

View File

@@ -93,7 +93,7 @@ class CalendarSonarrData extends CalendarData {
String get airTimeString {
if(airTimeObject != null) {
return LunaSeaDatabaseValue.USE_24_HOUR_TIME.data
return LunaDatabaseValue.USE_24_HOUR_TIME.data
? DateFormat.Hm().format(airTimeObject)
: DateFormat('hh:mm\na').format(airTimeObject);
}
@@ -110,13 +110,10 @@ class CalendarSonarrData extends CalendarData {
type: SNACKBAR_TYPE.success,
))
.catchError((error, stack) {
LunaLogger.error(
'CalendarSonarrData',
'trailingOnPress',
LunaLogger().error(
'Failed to search for episode: $id',
error,
stack,
uploadToSentry: !(error is DioError),
);
LSSnackBar(
context: context,

View File

@@ -6,18 +6,21 @@ class HomeConstants {
static const MODULE_KEY = 'home';
static const LunaModuleMap MODULE_MAP = LunaModuleMap(
static const LunaModuleMetadata MODULE_METADATA = LunaModuleMetadata(
name: 'Home',
description: 'Home',
settingsDescription: 'Configure the Home Screen',
helpMessage: '',
icon: CustomIcons.home,
route: '/',
color: Color(LunaColours.ACCENT_COLOR),
website: '',
github: '',
);
//ignore: non_constant_identifier_names
static final ShortcutItem MODULE_QUICK_ACTION = ShortcutItem(
type: MODULE_KEY,
localizedTitle: MODULE_MAP.name,
localizedTitle: MODULE_METADATA.name,
);
}

View File

@@ -1,15 +1,75 @@
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:lunasea/core/database/database.dart';
import './adapters.dart';
import 'package:lunasea/core.dart';
import 'package:lunasea/modules/home/core.dart';
class HomeDatabase {
HomeDatabase._();
static void registerAdapters() {
class HomeDatabase extends LunaModuleDatabase {
@override
void registerAdapters() {
Hive.registerAdapter(CalendarStartingDayAdapter());
Hive.registerAdapter(CalendarStartingSizeAdapter());
Hive.registerAdapter(CalendarStartingTypeAdapter());
}
@override
Map<String, dynamic> export() {
Map<String, dynamic> data = {};
for(HomeDatabaseValue value in HomeDatabaseValue.values) {
switch(value) {
// Non-primitive values
case HomeDatabaseValue.CALENDAR_STARTING_DAY: data[value.key] = (HomeDatabaseValue.CALENDAR_STARTING_DAY.data as CalendarStartingDay).key; break;
case HomeDatabaseValue.CALENDAR_STARTING_SIZE: data[value.key] = (HomeDatabaseValue.CALENDAR_STARTING_SIZE.data as CalendarStartingSize).key; break;
case HomeDatabaseValue.CALENDAR_STARTING_TYPE: data[value.key] = (HomeDatabaseValue.CALENDAR_STARTING_TYPE.data as CalendarStartingType).key; break;
// Primitive values
case HomeDatabaseValue.NAVIGATION_INDEX:
case HomeDatabaseValue.CALENDAR_ENABLE_LIDARR:
case HomeDatabaseValue.CALENDAR_ENABLE_RADARR:
case HomeDatabaseValue.CALENDAR_ENABLE_SONARR:
case HomeDatabaseValue.MODULES_BRAND_COLOURS:
case HomeDatabaseValue.CALENDAR_DAYS_PAST:
case HomeDatabaseValue.CALENDAR_DAYS_FUTURE: data[value.key] = value.data; break;
}
}
return data;
}
@override
void import(Map<String, dynamic> config) {
for(String key in config.keys) {
HomeDatabaseValue value = valueFromKey(key);
if(value != null) switch(value) {
// Non-primitive values
case HomeDatabaseValue.CALENDAR_STARTING_DAY: value.put(CalendarStartingDay.MONDAY.fromKey(config[key])); break;
case HomeDatabaseValue.CALENDAR_STARTING_SIZE: value.put(CalendarStartingSize.ONE_WEEK.fromKey(config[key])); break;
case HomeDatabaseValue.CALENDAR_STARTING_TYPE: value.put(CalendarStartingType.CALENDAR.fromKey(config[key])); break;
// Primitive values
case HomeDatabaseValue.NAVIGATION_INDEX:
case HomeDatabaseValue.CALENDAR_ENABLE_LIDARR:
case HomeDatabaseValue.CALENDAR_ENABLE_RADARR:
case HomeDatabaseValue.CALENDAR_ENABLE_SONARR:
case HomeDatabaseValue.MODULES_BRAND_COLOURS:
case HomeDatabaseValue.CALENDAR_DAYS_PAST:
case HomeDatabaseValue.CALENDAR_DAYS_FUTURE: value.put(config[key]); break;
}
}
}
@override
HomeDatabaseValue valueFromKey(String key) {
switch(key) {
case 'HOME_NAVIGATION_INDEX': return HomeDatabaseValue.NAVIGATION_INDEX;
case 'HOME_CALENDAR_STARTING_DAY': return HomeDatabaseValue.CALENDAR_STARTING_DAY;
case 'HOME_CALENDAR_STARTING_SIZE': return HomeDatabaseValue.CALENDAR_STARTING_SIZE;
case 'HOME_CALENDAR_STARTING_TYPE': return HomeDatabaseValue.CALENDAR_STARTING_TYPE;
case 'HOME_CALENDAR_ENABLE_LIDARR': return HomeDatabaseValue.CALENDAR_ENABLE_LIDARR;
case 'HOME_CALENDAR_ENABLE_RADARR': return HomeDatabaseValue.CALENDAR_ENABLE_RADARR;
case 'HOME_CALENDAR_ENABLE_SONARR': return HomeDatabaseValue.CALENDAR_ENABLE_SONARR;
case 'HOME_MODULES_BRAND_COLOURS': return HomeDatabaseValue.MODULES_BRAND_COLOURS;
case 'HOME_CALENDAR_DAYS_PAST': return HomeDatabaseValue.CALENDAR_DAYS_PAST;
case 'HOME_CALENDAR_DAYS_FUTURE': return HomeDatabaseValue.CALENDAR_DAYS_FUTURE;
default: return null;
}
}
}
enum HomeDatabaseValue {
@@ -43,21 +103,41 @@ extension HomeDatabaseValueExtension on HomeDatabaseValue {
}
dynamic get data {
final _box = Database.lunaSeaBox;
final box = Database.lunaSeaBox;
switch(this) {
case HomeDatabaseValue.NAVIGATION_INDEX: return _box.get(this.key, defaultValue: 0);
case HomeDatabaseValue.CALENDAR_STARTING_DAY: return _box.get(this.key, defaultValue: CalendarStartingDay.MONDAY);
case HomeDatabaseValue.CALENDAR_STARTING_SIZE: return _box.get(this.key, defaultValue: CalendarStartingSize.ONE_WEEK);
case HomeDatabaseValue.CALENDAR_STARTING_TYPE: return _box.get(this.key, defaultValue: CalendarStartingType.CALENDAR);
case HomeDatabaseValue.CALENDAR_ENABLE_LIDARR: return _box.get(this.key, defaultValue: true);
case HomeDatabaseValue.CALENDAR_ENABLE_RADARR: return _box.get(this.key, defaultValue: true);
case HomeDatabaseValue.CALENDAR_ENABLE_SONARR: return _box.get(this.key, defaultValue: true);
case HomeDatabaseValue.MODULES_BRAND_COLOURS: return _box.get(this.key, defaultValue: false);
case HomeDatabaseValue.CALENDAR_DAYS_PAST: return _box.get(this.key, defaultValue: 14);
case HomeDatabaseValue.CALENDAR_DAYS_FUTURE: return _box.get(this.key, defaultValue: 14);
case HomeDatabaseValue.NAVIGATION_INDEX: return box.get(this.key, defaultValue: 0);
case HomeDatabaseValue.CALENDAR_STARTING_DAY: return box.get(this.key, defaultValue: CalendarStartingDay.MONDAY);
case HomeDatabaseValue.CALENDAR_STARTING_SIZE: return box.get(this.key, defaultValue: CalendarStartingSize.ONE_WEEK);
case HomeDatabaseValue.CALENDAR_STARTING_TYPE: return box.get(this.key, defaultValue: CalendarStartingType.CALENDAR);
case HomeDatabaseValue.CALENDAR_ENABLE_LIDARR: return box.get(this.key, defaultValue: true);
case HomeDatabaseValue.CALENDAR_ENABLE_RADARR: return box.get(this.key, defaultValue: true);
case HomeDatabaseValue.CALENDAR_ENABLE_SONARR: return box.get(this.key, defaultValue: true);
case HomeDatabaseValue.MODULES_BRAND_COLOURS: return box.get(this.key, defaultValue: false);
case HomeDatabaseValue.CALENDAR_DAYS_PAST: return box.get(this.key, defaultValue: 14);
case HomeDatabaseValue.CALENDAR_DAYS_FUTURE: return box.get(this.key, defaultValue: 14);
}
throw Exception('data not found');
}
void put(dynamic value) => Database.lunaSeaBox.put(this.key, value);
void put(dynamic value) {
final box = Database.lunaSeaBox;
switch(this) {
case HomeDatabaseValue.NAVIGATION_INDEX: if(value.runtimeType == int) box.put(this.key, value); return;
case HomeDatabaseValue.CALENDAR_STARTING_DAY: if(value.runtimeType == CalendarStartingDay) box.put(this.key, value); return;
case HomeDatabaseValue.CALENDAR_STARTING_SIZE: if(value.runtimeType == CalendarStartingSize) box.put(this.key, value); return;
case HomeDatabaseValue.CALENDAR_STARTING_TYPE: if(value.runtimeType == CalendarStartingType) box.put(this.key, value); return;
case HomeDatabaseValue.CALENDAR_ENABLE_LIDARR: if(value.runtimeType == bool) box.put(this.key, value); return;
case HomeDatabaseValue.CALENDAR_ENABLE_RADARR: if(value.runtimeType == bool) box.put(this.key, value); return;
case HomeDatabaseValue.CALENDAR_ENABLE_SONARR: if(value.runtimeType == bool) box.put(this.key, value); return;
case HomeDatabaseValue.MODULES_BRAND_COLOURS: if(value.runtimeType == bool) box.put(this.key, value); return;
case HomeDatabaseValue.CALENDAR_DAYS_PAST: if(value.runtimeType == int) box.put(this.key, value); return;
case HomeDatabaseValue.CALENDAR_DAYS_FUTURE: if(value.runtimeType == int) box.put(this.key, value); return;
}
LunaLogger().warning('HomeDatabaseValueExtension', 'put', 'Attempted to enter data for invalid HomeDatabaseValue: ${this?.toString() ?? 'null'}');
}
void listen(Widget Function(BuildContext, Box<dynamic>, Widget) builder) => ValueListenableBuilder(
valueListenable: Database.lunaSeaBox.listenable(keys: [this.key]),
builder: builder,
);
}

View File

@@ -1,7 +1,7 @@
import 'package:lunasea/core.dart';
import 'package:lunasea/modules/home.dart';
class HomeState extends LunaGlobalState {
class HomeState extends LunaModuleState {
@override
void reset() {}

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