[Release] v2.1.0 (66) (#200)
* [Icon] Removed alpha channel from icon * [Sonarr] Fixed toast for season monitoring toggle * [Settings] Completely redesign, prepping for user configurable options * [Browsers] Ability to set which browser * [Settings/Modules] Long pressing 'Open Links In...' would open LunaSea website * [Navigation Bar] Fixed title regression * [Sliver AppBar] Defaults to collapsed, pull down to view * [Sliver AppBar] Left align title * [Settings/Calendar] Ability to configure starting day and starting size * [Settings] Added headers * [Settings] Cleaned folder structure of widgets * [Calendar] Added option to disable specific modules * [Theme] added AMOLED black theme * [Android] Build Configuration for Android (#198) * [Android] Implemented WIP android build configuration * [Android] Added splash, icons, dynamic platform-specific text * [Android] Added signing configuration, updated minimum version to Android 7.0 * [README] Updated README to reflect Android building * [README] Added stub Google Play shield * [README] Reordered shields
1
.gitignore
vendored
@@ -43,3 +43,4 @@ ios/build/
|
||||
ExportOptions.plist
|
||||
api_responses/
|
||||
pre_archive
|
||||
key.properties
|
||||
|
||||
54
README.md
@@ -1,10 +1,14 @@
|
||||
[](https://www.lunasea.app) [-red?style=for-the-badge&logo=app-store&color=%230D96F6&logoColor=white)](https://apps.apple.com/us/app/lunasea/id1496797802?ls=1) [](https://www.reddit.com/r/LunaSeaApp) [](https://github.com/LunaSeaApp/LunaSea/blob/master/LICENSE)
|
||||
[](https://www.lunasea.app)
|
||||
[](https://www.reddit.com/r/LunaSeaApp)
|
||||
[-red?style=for-the-badge&logo=app-store&color=%230D96F6&logoColor=white)](https://apps.apple.com/us/app/lunasea/id1496797802?ls=1)
|
||||
[-red?style=for-the-badge&logo=google-play&color=%230F9D58&logoColor=white)](https://github.com/LunaSeaApp/LunaSea/wiki/Play-Store-Release)
|
||||
[](https://github.com/LunaSeaApp/LunaSea/blob/master/LICENSE)
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
LunaSea is a fully-featured, open-source usenet manager built using the Flutter framework for iOS! LunaSea is focused on giving you a seamless experience between all of your usenet software.
|
||||
LunaSea is a fully-featured, open-source usenet manager built using the Flutter framework for mobile devices! LunaSea is focused on giving you a seamless experience between all of your usenet software.
|
||||
|
||||
Support for services include:
|
||||
- Lidarr
|
||||
@@ -21,7 +25,7 @@ LunaSea also comes with support for:
|
||||
|
||||
---
|
||||
|
||||
## Developing, Installing, & Building
|
||||
## Developing, Installing, & Building (iOS)
|
||||
|
||||
#### Requirements
|
||||
|
||||
@@ -61,3 +65,47 @@ LunaSea also comes with support for:
|
||||
- `flutter pub get`
|
||||
3. Run `build_ipa` inside of the `scripts` folder
|
||||
4. The IPA will be placed in the root of the project directory
|
||||
|
||||
---
|
||||
|
||||
## Developing, Installing, & Building (Android)
|
||||
|
||||
#### Requirements
|
||||
|
||||
1. Android SDK/Android Studio Installed & Configured
|
||||
2. [Flutter Framework (Beta Channel)](https://flutter.dev/)
|
||||
3. [Keystore Configured](https://github.com/LunaSeaApp/LunaSea/wiki/Configure-Keystore)
|
||||
|
||||
#### Developing
|
||||
|
||||
1. Clone the repository
|
||||
- `git clone git@github.com:LunaSeaApp/LunaSea.git`
|
||||
2. Install the Flutter packages
|
||||
- `flutter pub get`
|
||||
3. Start your simulator or plug in your device and ensure you have enabled USB Debugging
|
||||
4. Install LunaSea in development mode on your device or simulator
|
||||
- `flutter run`
|
||||
|
||||
#### Installing
|
||||
|
||||
> Release builds can only be installed on physical devices
|
||||
|
||||
1. Clone the repository
|
||||
- `git clone git@github.com:LunaSeaApp/LunaSea.git`
|
||||
2. Install the Flutter packages
|
||||
- `flutter pub get`
|
||||
3. Plug in your device and ensure you have enabled USB Debugging
|
||||
4. Install a production version of the application on your device
|
||||
- `flutter run --release`
|
||||
|
||||
#### Building (.apk)
|
||||
|
||||
1. Clone the repository
|
||||
- `git clone git@github.com:LunaSeaApp/LunaSea.git`
|
||||
2. Install the Flutter packages
|
||||
- `flutter pub get`
|
||||
3. Run `flutter build apk --split-per-abi`
|
||||
4. The APKs are located in:
|
||||
- `./build/app/outputs/apk/release/app-armeabi-v7a-release.apk`
|
||||
- `./build/app/outputs/apk/release/app-arm64-v8a-release.apk`
|
||||
- `./build/app/outputs/apk/release/app-x86_64-release.apk`
|
||||
|
||||
7
android/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
gradle-wrapper.jar
|
||||
/.gradle
|
||||
/captures/
|
||||
/gradlew
|
||||
/gradlew.bat
|
||||
/local.properties
|
||||
GeneratedPluginRegistrant.java
|
||||
75
android/app/build.gradle
Normal file
@@ -0,0 +1,75 @@
|
||||
def localProperties = new Properties()
|
||||
def localPropertiesFile = rootProject.file('local.properties')
|
||||
if (localPropertiesFile.exists()) {
|
||||
localPropertiesFile.withReader('UTF-8') { reader ->
|
||||
localProperties.load(reader)
|
||||
}
|
||||
}
|
||||
|
||||
def flutterRoot = localProperties.getProperty('flutter.sdk')
|
||||
if (flutterRoot == null) {
|
||||
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
|
||||
}
|
||||
|
||||
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||
if (flutterVersionCode == null) {
|
||||
flutterVersionCode = '1'
|
||||
}
|
||||
|
||||
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
||||
if (flutterVersionName == null) {
|
||||
flutterVersionName = '1.0'
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||
|
||||
def keystoreProperties = new Properties()
|
||||
def keystorePropertiesFile = rootProject.file('key.properties')
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
disable 'InvalidPackage'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "app.lunasea.lunasea"
|
||||
minSdkVersion 24
|
||||
targetSdkVersion 29
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
release {
|
||||
keyAlias keystoreProperties['keyAlias']
|
||||
keyPassword keystoreProperties['keyPassword']
|
||||
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
|
||||
storePassword keystoreProperties['storePassword']
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source '../..'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
}
|
||||
6
android/app/src/debug/AndroidManifest.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="app.lunasea.lunasea">
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
</manifest>
|
||||
46
android/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,46 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="app.lunasea.lunasea">
|
||||
<application
|
||||
android:name="io.flutter.app.FlutterApplication"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:label="LunaSea"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
<!-- Displays an Android View that continues showing the launch screen
|
||||
Drawable until Flutter paints its first frame, then this splash
|
||||
screen fades out. A splash screen is useful to avoid any visual
|
||||
gap between the end of Android's launch screen and the painting of
|
||||
Flutter's first frame. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.SplashScreenDrawable"
|
||||
android:resource="@drawable/launch_background"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
</manifest>
|
||||
@@ -0,0 +1,9 @@
|
||||
package app.lunasea.lunasea
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
import android.os.Build
|
||||
import android.view.ViewTreeObserver
|
||||
import android.view.WindowManager
|
||||
class MainActivity: FlutterActivity() {
|
||||
}
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
BIN
android/app/src/main/res/drawable-hdpi/splash.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
BIN
android/app/src/main/res/drawable-mdpi/splash.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
BIN
android/app/src/main/res/drawable-xhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
BIN
android/app/src/main/res/drawable-xxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
BIN
android/app/src/main/res/drawable-xxxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
16
android/app/src/main/res/drawable/launch_background.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@color/splash_color" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
<item>
|
||||
<bitmap android:gravity="center" android:src="@drawable/splash" />
|
||||
</item>
|
||||
|
||||
</layer-list>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 949 B |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
5
android/app/src/main/res/values/colors.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="splash_color">#32323E</color>
|
||||
<color name="ic_launcher_background">#32323E</color>
|
||||
</resources>
|
||||
20
android/app/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
Flutter draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">@color/splash_color</item>
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
|
||||
</style>
|
||||
</resources>
|
||||
9
android/app/src/profile/AndroidManifest.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="app.lunasea.lunasea">
|
||||
<!-- Flutter needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
</manifest>
|
||||
31
android/build.gradle
Normal file
@@ -0,0 +1,31 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.3.50'
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.5.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.buildDir = '../build'
|
||||
subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(':app')
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
4
android/gradle.properties
Normal file
@@ -0,0 +1,4 @@
|
||||
org.gradle.jvmargs=-Xmx1536M
|
||||
android.enableR8=true
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
6
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
#Fri Jun 23 08:50:38 CEST 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
|
||||
15
android/settings.gradle
Normal file
@@ -0,0 +1,15 @@
|
||||
include ':app'
|
||||
|
||||
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
|
||||
|
||||
def plugins = new Properties()
|
||||
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
|
||||
if (pluginsFile.exists()) {
|
||||
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
|
||||
}
|
||||
|
||||
plugins.each { name, path ->
|
||||
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
|
||||
include ":$name"
|
||||
project(":$name").projectDir = pluginDirectory
|
||||
}
|
||||
1
android/settings_aar.gradle
Normal file
@@ -0,0 +1 @@
|
||||
include ':app'
|
||||
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 810 B After Width: | Height: | Size: 810 B |
BIN
assets/images/colors/black.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 809 B After Width: | Height: | Size: 809 B |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 401 B After Width: | Height: | Size: 350 B |
|
Before Width: | Height: | Size: 836 B After Width: | Height: | Size: 748 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 605 B After Width: | Height: | Size: 547 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 836 B After Width: | Height: | Size: 748 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 945 B |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.4 KiB |
@@ -4,11 +4,23 @@ 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 {
|
||||
Box<dynamic> _box = Database.lunaSeaBox;
|
||||
Map<String, dynamic> _data = {};
|
||||
for(var key in _box.keys) {
|
||||
_data[key] = _box.get(key);
|
||||
dynamic _value = _box.get(key);
|
||||
if(validateType(_value)) _data[key] = _value;
|
||||
}
|
||||
return _data;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@ class Filesystem {
|
||||
}
|
||||
|
||||
static Future<String> get appDirectory async {
|
||||
Directory appDocDir = await getApplicationDocumentsDirectory();
|
||||
Directory appDocDir;
|
||||
if(Platform.isIOS) appDocDir = await getApplicationDocumentsDirectory();
|
||||
if(Platform.isAndroid) appDocDir = await getExternalStorageDirectory();
|
||||
return '${appDocDir.path}';
|
||||
}
|
||||
|
||||
@@ -41,10 +43,10 @@ class Filesystem {
|
||||
String _directory = await configDirectory;
|
||||
//Create configuration folder if needed
|
||||
Directory directory = Directory(_directory);
|
||||
if(!await directory.exists()) await directory.create();
|
||||
await directory.create(recursive: true);
|
||||
//Write the configuration to the filsystem
|
||||
File file = File(_file);
|
||||
file?.writeAsString(config);
|
||||
await file?.writeAsString(config);
|
||||
}
|
||||
|
||||
static Future<void> exportDownloadToFilesystem(String name, String data) async {
|
||||
@@ -52,9 +54,9 @@ class Filesystem {
|
||||
String _directory = await downloadDirectory;
|
||||
//Create download folder if needed
|
||||
Directory directory = Directory(_directory);
|
||||
if(!await directory.exists()) await directory.create();
|
||||
await directory.create(recursive: true);
|
||||
//Write the NZB to the filesystem
|
||||
File file = File(_nzb);
|
||||
file?.writeAsString(data);
|
||||
await file?.writeAsString(data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ class Import {
|
||||
|
||||
static void _setLunaSea(Map data) {
|
||||
Box<dynamic> _box = Database.lunaSeaBox;
|
||||
_box.put('profile', data['profile']);
|
||||
_box.put(LunaSeaDatabaseValue.ENABLED_PROFILE.key, data['profile']);
|
||||
}
|
||||
|
||||
static void _setProfiles(List data) {
|
||||
|
||||
@@ -11,25 +11,25 @@ class Constants {
|
||||
'sonarr': SonarrConstants.SERVICE_MAP,
|
||||
'nzbget': {
|
||||
'name': 'NZBGet',
|
||||
'desc': 'Manage your downloads',
|
||||
'desc': 'Manage Usenet Downloads',
|
||||
'icon': CustomIcons.nzbget,
|
||||
'route': '/nzbget',
|
||||
},
|
||||
'sabnzbd': {
|
||||
'name': 'SABnzbd',
|
||||
'desc': 'Manage your downloads',
|
||||
'desc': 'Manage Usenet Downloads',
|
||||
'icon': CustomIcons.sabnzbd,
|
||||
'route': '/sabnzbd',
|
||||
},
|
||||
'search': {
|
||||
'name': 'Search',
|
||||
'desc': 'Search newznab indexers',
|
||||
'desc': 'Search Newznab Indexers',
|
||||
'icon': Icons.search,
|
||||
'route': '/search',
|
||||
},
|
||||
'settings': {
|
||||
'name': 'Settings',
|
||||
'desc': 'Update your configuration',
|
||||
'desc': 'Update Configuration',
|
||||
'icon': Icons.settings,
|
||||
'route': '/settings',
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export './adapters/indexer.dart';
|
||||
export './adapters/lunasea.dart';
|
||||
export './adapters/profile.dart';
|
||||
|
||||
37
lib/core/database/adapters/lunasea.dart
Normal file
@@ -0,0 +1,37 @@
|
||||
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,
|
||||
THEME_AMOLED,
|
||||
SELECTED_BROWSER,
|
||||
}
|
||||
|
||||
extension LunaSeaDatabaseValueExtension on LunaSeaDatabaseValue {
|
||||
String get key {
|
||||
switch(this) {
|
||||
case LunaSeaDatabaseValue.ENABLED_PROFILE: return 'profile';
|
||||
case LunaSeaDatabaseValue.THEME_AMOLED: return 'LUNASEA_THEME_AMOLED';
|
||||
case LunaSeaDatabaseValue.SELECTED_BROWSER: return 'LUNASEA_SELECTED_BROWSER';
|
||||
default: return '';
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,10 @@ class Database {
|
||||
//Core
|
||||
Hive.registerAdapter(IndexerHiveObjectAdapter());
|
||||
Hive.registerAdapter(ProfileHiveObjectAdapter());
|
||||
//Modules
|
||||
//Modules - General
|
||||
LunaSeaDatabase.registerAdapters();
|
||||
HomeDatabase.registerAdapters();
|
||||
//Modules - Automation
|
||||
LidarrDatabase.registerAdapters();
|
||||
RadarrDatabase.registerAdapters();
|
||||
SonarrDatabase.registerAdapters();
|
||||
@@ -40,7 +43,7 @@ class Database {
|
||||
clearIndexersBox();
|
||||
//Set default profile & enabled profile
|
||||
profilesBox.put('default', ProfileHiveObject.empty());
|
||||
lunaSeaBox.put('profile', 'default');
|
||||
lunaSeaBox.put(LunaSeaDatabaseValue.ENABLED_PROFILE.key, 'default');
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,104 @@
|
||||
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 {
|
||||
bool launched = await launch(
|
||||
url,
|
||||
forceSafariVC: false,
|
||||
universalLinksOnly: true,
|
||||
);
|
||||
if(!launched) await launch(url);
|
||||
//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
|
||||
|
||||
51
lib/core/extensions/string/links.g.dart
Normal file
@@ -0,0 +1,51 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'links.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class LSBrowsersAdapter extends TypeAdapter<LSBrowsers> {
|
||||
@override
|
||||
final typeId = 11;
|
||||
|
||||
@override
|
||||
LSBrowsers read(BinaryReader reader) {
|
||||
switch (reader.readByte()) {
|
||||
case 0:
|
||||
return LSBrowsers.APPLE_SAFARI;
|
||||
case 1:
|
||||
return LSBrowsers.BRAVE_BROWSER;
|
||||
case 2:
|
||||
return LSBrowsers.GOOGLE_CHROME;
|
||||
case 3:
|
||||
return LSBrowsers.MICROSOFT_EDGE;
|
||||
case 4:
|
||||
return LSBrowsers.MOZILLA_FIREFOX;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, LSBrowsers obj) {
|
||||
switch (obj) {
|
||||
case LSBrowsers.APPLE_SAFARI:
|
||||
writer.writeByte(0);
|
||||
break;
|
||||
case LSBrowsers.BRAVE_BROWSER:
|
||||
writer.writeByte(1);
|
||||
break;
|
||||
case LSBrowsers.GOOGLE_CHROME:
|
||||
writer.writeByte(2);
|
||||
break;
|
||||
case LSBrowsers.MICROSOFT_EDGE:
|
||||
writer.writeByte(3);
|
||||
break;
|
||||
case LSBrowsers.MOZILLA_FIREFOX:
|
||||
writer.writeByte(4);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ class Logger {
|
||||
Logger._();
|
||||
|
||||
static void initialize() {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
LogsConfig config = FLog.getDefaultConfigurations()
|
||||
..formatType = FormatType.FORMAT_SQUARE
|
||||
..timestampFormat = 'MMMM dd, y - hh:mm:ss a';
|
||||
|
||||
@@ -12,6 +12,6 @@ class ModuleFlags {
|
||||
static const SABNZBD = true;
|
||||
static const NZBGET = true;
|
||||
//Monitoring
|
||||
static const MONITORING = true;
|
||||
static const MONITORING = false;
|
||||
static const TAUTULLI = true;
|
||||
}
|
||||
|
||||
@@ -26,118 +26,129 @@ class Routes {
|
||||
|
||||
static Map<String, WidgetBuilder> get _home => <String, WidgetBuilder> {
|
||||
// /
|
||||
Home.ROUTE_NAME: (BuildContext context) => Home(),
|
||||
Home.ROUTE_NAME: (context) => Home(),
|
||||
// /home/*
|
||||
HomeCalendar.ROUTE_NAME: (BuildContext context) => HomeCalendar(refreshIndicatorKey: null),
|
||||
HomeQuickAccess.ROUTE_NAME: (BuildContext context) => HomeQuickAccess(),
|
||||
HomeCalendar.ROUTE_NAME: (context) => HomeCalendar(refreshIndicatorKey: null),
|
||||
HomeQuickAccess.ROUTE_NAME: (context) => HomeQuickAccess(),
|
||||
};
|
||||
|
||||
static Map<String, WidgetBuilder> get _settings => <String, WidgetBuilder> {
|
||||
// /settings
|
||||
Settings.ROUTE_NAME: (BuildContext context) => Settings(),
|
||||
Settings.ROUTE_NAME: (context) => Settings(),
|
||||
// /settings/*
|
||||
SettingsIndexers.ROUTE_NAME: (BuildContext context) => SettingsIndexers(),
|
||||
SettingsClients.ROUTE_NAME: (BuildContext context) => SettingsClients(),
|
||||
SettingsGeneral.ROUTE_NAME: (BuildContext context) => SettingsGeneral(),
|
||||
SettingsAutomation.ROUTE_NAME: (BuildContext context) => SettingsAutomation(),
|
||||
// /settings/indexers/*
|
||||
SettingsIndexersAdd.ROUTE_NAME: (BuildContext context) => SettingsIndexersAdd(),
|
||||
SettingsIndexerEdit.ROUTE_NAME: (BuildContext context) => SettingsIndexerEdit(),
|
||||
SettingsGeneral.ROUTE_NAME: (context) => SettingsGeneral(),
|
||||
SettingsSystem.ROUTE_NAME: (context) => SettingsSystem(),
|
||||
SettingsModules.ROUTE_NAME: (context) => SettingsModules(),
|
||||
// /settings/modules/*
|
||||
// Automation
|
||||
SettingsModulesLidarr.ROUTE_NAME: (context) => SettingsModulesLidarr(),
|
||||
SettingsModulesRadarr.ROUTE_NAME: (context) => SettingsModulesRadarr(),
|
||||
SettingsModulesSonarr.ROUTE_NAME: (context) => SettingsModulesSonarr(),
|
||||
// Clients
|
||||
SettingsModulesNZBGet.ROUTE_NAME: (context) => SettingsModulesNZBGet(),
|
||||
SettingsModulesSABnzbd.ROUTE_NAME: (context) => SettingsModulesSABnzbd(),
|
||||
// General
|
||||
SettingsModulesCalendar.ROUTE_NAME: (context) => SettingsModulesCalendar(),
|
||||
SettingsModulesLunaSea.ROUTE_NAME: (context) => SettingsModulesLunaSea(),
|
||||
SettingsModulesSearch.ROUTE_NAME: (context) => SettingsModulesSearch(),
|
||||
// /settings/modules/indexers/*
|
||||
SettingsModulesSearchAdd.ROUTE_NAME: (context) => SettingsModulesSearchAdd(),
|
||||
SettingsModulesSearchEdit.ROUTE_NAME: (context) => SettingsModulesSearchEdit(),
|
||||
// /settings/general/logs/*
|
||||
SettingsGeneralLogsTypes.ROUTE_NAME: (BuildContext context) => SettingsGeneralLogsTypes(),
|
||||
SettingsGeneralLogsView.ROUTE_NAME: (BuildContext context) => SettingsGeneralLogsView(),
|
||||
SettingsGeneralLogsDetails.ROUTE_NAME: (BuildContext context) => SettingsGeneralLogsDetails(),
|
||||
SettingsGeneralLogsTypes.ROUTE_NAME: (context) => SettingsGeneralLogsTypes(),
|
||||
SettingsGeneralLogsView.ROUTE_NAME: (context) => SettingsGeneralLogsView(),
|
||||
SettingsGeneralLogsDetails.ROUTE_NAME: (context) => SettingsGeneralLogsDetails(),
|
||||
};
|
||||
|
||||
static Map<String, WidgetBuilder> get _search => <String, WidgetBuilder> {
|
||||
// /search
|
||||
Search.ROUTE_NAME: (BuildContext context) => Search(),
|
||||
Search.ROUTE_NAME: (context) => Search(),
|
||||
// /search/*
|
||||
SearchSearch.ROUTE_NAME: (BuildContext context) => SearchSearch(),
|
||||
SearchCategories.ROUTE_NAME: (BuildContext context) => SearchCategories(),
|
||||
SearchSubcategories.ROUTE_NAME: (BuildContext context) => SearchSubcategories(),
|
||||
SearchResults.ROUTE_NAME: (BuildContext context) => SearchResults(),
|
||||
SearchDetails.ROUTE_NAME: (BuildContext context) => SearchDetails(),
|
||||
SearchSearch.ROUTE_NAME: (context) => SearchSearch(),
|
||||
SearchCategories.ROUTE_NAME: (context) => SearchCategories(),
|
||||
SearchSubcategories.ROUTE_NAME: (context) => SearchSubcategories(),
|
||||
SearchResults.ROUTE_NAME: (context) => SearchResults(),
|
||||
SearchDetails.ROUTE_NAME: (context) => SearchDetails(),
|
||||
};
|
||||
|
||||
static Map<String, WidgetBuilder> get _lidarr => <String, WidgetBuilder> {
|
||||
// /lidarr
|
||||
Lidarr.ROUTE_NAME: (BuildContext context) => Lidarr(),
|
||||
Lidarr.ROUTE_NAME: (context) => Lidarr(),
|
||||
// /lidarr/*
|
||||
LidarrCatalogue.ROUTE_NAME: (BuildContext context) => LidarrCatalogue(refreshIndicatorKey: null, refreshAllPages: null),
|
||||
LidarrMissing.ROUTE_NAME: (BuildContext context) => LidarrMissing(refreshIndicatorKey: null, refreshAllPages: null),
|
||||
LidarrHistory.ROUTE_NAME: (BuildContext context) => LidarrHistory(refreshIndicatorKey: null, refreshAllPages: null),
|
||||
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: (BuildContext context) => LidarrAddSearch(),
|
||||
LidarrAddDetails.ROUTE_NAME: (BuildContext context) => LidarrAddDetails(),
|
||||
LidarrAddSearch.ROUTE_NAME: (context) => LidarrAddSearch(),
|
||||
LidarrAddDetails.ROUTE_NAME: (context) => LidarrAddDetails(),
|
||||
// /lidarr/edit/*
|
||||
LidarrEditArtist.ROUTE_NAME: (BuildContext context) => LidarrEditArtist(),
|
||||
LidarrEditArtist.ROUTE_NAME: (context) => LidarrEditArtist(),
|
||||
// /lidarr/details/*
|
||||
LidarrDetailsAlbum.ROUTE_NAME: (BuildContext context) => LidarrDetailsAlbum(),
|
||||
LidarrDetailsArtist.ROUTE_NAME: (BuildContext context) => LidarrDetailsArtist(),
|
||||
LidarrDetailsAlbum.ROUTE_NAME: (context) => LidarrDetailsAlbum(),
|
||||
LidarrDetailsArtist.ROUTE_NAME: (context) => LidarrDetailsArtist(),
|
||||
// /lidarr/search/*
|
||||
LidarrSearchDetails.ROUTE_NAME: (BuildContext context) => LidarrSearchDetails(),
|
||||
LidarrSearchResults.ROUTE_NAME: (BuildContext context) => LidarrSearchResults(),
|
||||
LidarrSearchDetails.ROUTE_NAME: (context) => LidarrSearchDetails(),
|
||||
LidarrSearchResults.ROUTE_NAME: (context) => LidarrSearchResults(),
|
||||
};
|
||||
|
||||
static Map<String, WidgetBuilder> get _radarr => <String, WidgetBuilder> {
|
||||
// /radarr
|
||||
Radarr.ROUTE_NAME: (BuildContext context) => Radarr(),
|
||||
Radarr.ROUTE_NAME: (context) => Radarr(),
|
||||
// /radarr/*
|
||||
RadarrCatalogue.ROUTE_NAME: (BuildContext context) => RadarrCatalogue(refreshIndicatorKey: null, refreshAllPages: null),
|
||||
RadarrMissing.ROUTE_NAME: (BuildContext context) => RadarrMissing(refreshIndicatorKey: null, refreshAllPages: null),
|
||||
RadarrUpcoming.ROUTE_NAME: (BuildContext context) => RadarrUpcoming(refreshIndicatorKey: null, refreshAllPages: null),
|
||||
RadarrHistory.ROUTE_NAME: (BuildContext context) => RadarrHistory(refreshIndicatorKey: null, refreshAllPages: null),
|
||||
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: (BuildContext context) => RadarrAddSearch(),
|
||||
RadarrAddDetails.ROUTE_NAME: (BuildContext context) => RadarrAddDetails(),
|
||||
RadarrAddSearch.ROUTE_NAME: (context) => RadarrAddSearch(),
|
||||
RadarrAddDetails.ROUTE_NAME: (context) => RadarrAddDetails(),
|
||||
// /radarr/details/*
|
||||
RadarrDetailsMovie.ROUTE_NAME: (BuildContext context) => RadarrDetailsMovie(),
|
||||
RadarrDetailsSearchResults.ROUTE_NAME: (BuildContext context) => RadarrDetailsSearchResults(),
|
||||
RadarrDetailsMovie.ROUTE_NAME: (context) => RadarrDetailsMovie(),
|
||||
RadarrDetailsSearchResults.ROUTE_NAME: (context) => RadarrDetailsSearchResults(),
|
||||
// /radarr/edit/*
|
||||
RadarrEditMovie.ROUTE_NAME: (BuildContext context) => RadarrEditMovie(),
|
||||
RadarrEditMovie.ROUTE_NAME: (context) => RadarrEditMovie(),
|
||||
};
|
||||
|
||||
static Map<String, WidgetBuilder> get _sonarr => <String, WidgetBuilder> {
|
||||
// /sonarr
|
||||
Sonarr.ROUTE_NAME: (BuildContext context) => Sonarr(),
|
||||
Sonarr.ROUTE_NAME: (context) => Sonarr(),
|
||||
// /sonarr/*
|
||||
SonarrCatalogue.ROUTE_NAME: (BuildContext context) => SonarrCatalogue(refreshIndicatorKey: null, refreshAllPages: null),
|
||||
SonarrMissing.ROUTE_NAME: (BuildContext context) => SonarrMissing(refreshIndicatorKey: null, refreshAllPages: null),
|
||||
SonarrUpcoming.ROUTE_NAME: (BuildContext context) => SonarrUpcoming(refreshIndicatorKey: null, refreshAllPages: null),
|
||||
SonarrHistory.ROUTE_NAME: (BuildContext context) => SonarrHistory(refreshIndicatorKey: null, refreshAllPages: null),
|
||||
SonarrCatalogue.ROUTE_NAME: (context) => SonarrCatalogue(refreshIndicatorKey: null, refreshAllPages: null),
|
||||
SonarrMissing.ROUTE_NAME: (context) => SonarrMissing(refreshIndicatorKey: null, refreshAllPages: null),
|
||||
SonarrUpcoming.ROUTE_NAME: (context) => SonarrUpcoming(refreshIndicatorKey: null, refreshAllPages: null),
|
||||
SonarrHistory.ROUTE_NAME: (context) => SonarrHistory(refreshIndicatorKey: null, refreshAllPages: null),
|
||||
// /sonarr/add/*
|
||||
SonarrAddSearch.ROUTE_NAME: (BuildContext context) => SonarrAddSearch(),
|
||||
SonarrAddDetails.ROUTE_NAME: (BuildContext context) => SonarrAddDetails(),
|
||||
SonarrAddSearch.ROUTE_NAME: (context) => SonarrAddSearch(),
|
||||
SonarrAddDetails.ROUTE_NAME: (context) => SonarrAddDetails(),
|
||||
// /sonarr/details/*
|
||||
SonarrDetailsSeries.ROUTE_NAME: (BuildContext context) => SonarrDetailsSeries(),
|
||||
SonarrDetailsSeason.ROUTE_NAME: (BuildContext context) => SonarrDetailsSeason(),
|
||||
SonarrDetailsSeries.ROUTE_NAME: (context) => SonarrDetailsSeries(),
|
||||
SonarrDetailsSeason.ROUTE_NAME: (context) => SonarrDetailsSeason(),
|
||||
// /sonarr/edit/*
|
||||
SonarrEditSeries.ROUTE_NAME: (BuildContext context) => SonarrEditSeries(),
|
||||
SonarrEditSeries.ROUTE_NAME: (context) => SonarrEditSeries(),
|
||||
// /sonarr/search/*
|
||||
SonarrSearchDetails.ROUTE_NAME: (BuildContext context) => SonarrSearchDetails(),
|
||||
SonarrSearchResults.ROUTE_NAME: (BuildContext context) => SonarrSearchResults(),
|
||||
SonarrSearchDetails.ROUTE_NAME: (context) => SonarrSearchDetails(),
|
||||
SonarrSearchResults.ROUTE_NAME: (context) => SonarrSearchResults(),
|
||||
};
|
||||
|
||||
static Map<String, WidgetBuilder> get _nzbget => <String, WidgetBuilder> {
|
||||
// /nzbget
|
||||
NZBGet.ROUTE_NAME: (BuildContext context) => NZBGet(),
|
||||
NZBGet.ROUTE_NAME: (context) => NZBGet(),
|
||||
// /nzbget/*
|
||||
NZBGetHistory.ROUTE_NAME: (BuildContext context) => NZBGetHistory(refreshIndicatorKey: null),
|
||||
NZBGetQueue.ROUTE_NAME: (BuildContext context) => NZBGetQueue(refreshIndicatorKey: null),
|
||||
NZBGetStatistics.ROUTE_NAME: (BuildContext context) => NZBGetStatistics(),
|
||||
NZBGetHistory.ROUTE_NAME: (context) => NZBGetHistory(refreshIndicatorKey: null),
|
||||
NZBGetQueue.ROUTE_NAME: (context) => NZBGetQueue(refreshIndicatorKey: null),
|
||||
NZBGetStatistics.ROUTE_NAME: (context) => NZBGetStatistics(),
|
||||
// /nzbget/history/*
|
||||
NZBGetHistoryDetails.ROUTE_NAME: (BuildContext context) => NZBGetHistoryDetails(),
|
||||
NZBGetHistoryDetails.ROUTE_NAME: (context) => NZBGetHistoryDetails(),
|
||||
};
|
||||
|
||||
static Map<String, WidgetBuilder> get _sabnzbd => <String, WidgetBuilder> {
|
||||
// /sabnzbd
|
||||
SABnzbd.ROUTE_NAME: (BuildContext context) => SABnzbd(),
|
||||
SABnzbd.ROUTE_NAME: (context) => SABnzbd(),
|
||||
// /sabnzbd/*
|
||||
SABnzbdHistory.ROUTE_NAME: (BuildContext context) => SABnzbdHistory(refreshIndicatorKey: null),
|
||||
SABnzbdQueue.ROUTE_NAME: (BuildContext context) => SABnzbdQueue(refreshIndicatorKey: null),
|
||||
SABnzbdStatistics.ROUTE_NAME: (BuildContext context) => SABnzbdStatistics(),
|
||||
SABnzbdHistory.ROUTE_NAME: (context) => SABnzbdHistory(refreshIndicatorKey: null),
|
||||
SABnzbdQueue.ROUTE_NAME: (context) => SABnzbdQueue(refreshIndicatorKey: null),
|
||||
SABnzbdStatistics.ROUTE_NAME: (context) => SABnzbdStatistics(),
|
||||
// /sabnzbd/history/*
|
||||
SABnzbdHistoryDetails.ROUTE_NAME: (BuildContext context) => SABnzbdHistoryDetails(),
|
||||
SABnzbdHistoryDetails.ROUTE_NAME: (context) => SABnzbdHistoryDetails(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
|
||||
class Themes {
|
||||
Themes._();
|
||||
|
||||
static ThemeData getDefaultTheme() {
|
||||
static ThemeData getDarkTheme() => LunaSeaDatabaseValue.THEME_AMOLED.data
|
||||
? _pureBlackTheme()
|
||||
: _midnightTheme();
|
||||
|
||||
static ThemeData _midnightTheme() {
|
||||
const _textStyle = TextStyle(
|
||||
color: Colors.white,
|
||||
letterSpacing: Constants.UI_LETTER_SPACING,
|
||||
);
|
||||
|
||||
return ThemeData(
|
||||
brightness: Brightness.dark,
|
||||
canvasColor: LSColors.primary,
|
||||
primaryColor: LSColors.secondary,
|
||||
accentColor: LSColors.accent,
|
||||
@@ -19,6 +24,11 @@ class Themes {
|
||||
splashColor: LSColors.splash,
|
||||
dialogBackgroundColor: LSColors.secondary,
|
||||
dividerColor: LSColors.accent.withAlpha(0),
|
||||
dividerTheme: DividerThemeData(
|
||||
color: LSColors.accent,
|
||||
indent: 100.0,
|
||||
endIndent: 100.0,
|
||||
),
|
||||
iconTheme: IconThemeData(
|
||||
color: Colors.white,
|
||||
),
|
||||
@@ -38,6 +48,50 @@ class Themes {
|
||||
subtitle2: _textStyle,
|
||||
overline: _textStyle,
|
||||
),
|
||||
visualDensity: VisualDensity.adaptivePlatformDensity,
|
||||
);
|
||||
}
|
||||
|
||||
static ThemeData _pureBlackTheme() {
|
||||
const _textStyle = TextStyle(
|
||||
color: Colors.white,
|
||||
letterSpacing: Constants.UI_LETTER_SPACING,
|
||||
);
|
||||
return ThemeData(
|
||||
brightness: Brightness.dark,
|
||||
canvasColor: Colors.black,
|
||||
primaryColor: Colors.black,
|
||||
accentColor: LSColors.accent,
|
||||
highlightColor: LSColors.secondary,
|
||||
cardColor: Colors.black,
|
||||
splashColor: LSColors.splash,
|
||||
dialogBackgroundColor: Colors.black,
|
||||
dividerColor: LSColors.accent.withAlpha(0),
|
||||
dividerTheme: DividerThemeData(
|
||||
color: LSColors.accent,
|
||||
indent: 72.0,
|
||||
endIndent: 72.0,
|
||||
),
|
||||
iconTheme: IconThemeData(
|
||||
color: Colors.white,
|
||||
),
|
||||
unselectedWidgetColor: Colors.white,
|
||||
textTheme: TextTheme(
|
||||
bodyText1: _textStyle,
|
||||
bodyText2: _textStyle,
|
||||
headline1: _textStyle,
|
||||
headline2: _textStyle,
|
||||
headline3: _textStyle,
|
||||
headline4: _textStyle,
|
||||
headline5: _textStyle,
|
||||
headline6: _textStyle,
|
||||
button: _textStyle,
|
||||
caption: _textStyle,
|
||||
subtitle1: _textStyle,
|
||||
subtitle2: _textStyle,
|
||||
overline: _textStyle,
|
||||
),
|
||||
visualDensity: VisualDensity.adaptivePlatformDensity,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ class LSCard extends StatelessWidget {
|
||||
? EdgeInsets.symmetric(horizontal: 6.0, vertical: 6.0)
|
||||
: margin,
|
||||
color: color == null
|
||||
? LSColors.secondary
|
||||
? Theme.of(context).primaryColor
|
||||
: color,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ Decoration LSCardBackground({ @required String uri, bool darken = false }) => Bo
|
||||
uri,
|
||||
useDiskCache: true,
|
||||
loadFailedCallback: () {},
|
||||
fallbackAssetImage: 'assets/images/secondary_color.png',
|
||||
fallbackAssetImage: 'assets/images/colors/secondary.png',
|
||||
retryLimit: 1,
|
||||
),
|
||||
colorFilter: ColorFilter.mode(LSColors.secondary.withOpacity(darken ? 0.10 : 0.20), BlendMode.dstATop),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
import 'package:lunasea/core/constants.dart';
|
||||
|
||||
class LSCardTile extends StatelessWidget {
|
||||
@@ -15,7 +16,7 @@ class LSCardTile extends StatelessWidget {
|
||||
|
||||
LSCardTile({
|
||||
@required this.title,
|
||||
@required this.subtitle,
|
||||
this.subtitle,
|
||||
this.trailing,
|
||||
this.leading,
|
||||
this.onTap,
|
||||
|
||||
@@ -18,7 +18,7 @@ class LSDialogSearch {
|
||||
),
|
||||
],
|
||||
content: ValueListenableBuilder(
|
||||
valueListenable: Database.lunaSeaBox.listenable(keys: ['profile']),
|
||||
valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.ENABLED_PROFILE.key]),
|
||||
builder: (context, lunaBox, widget) => ValueListenableBuilder(
|
||||
valueListenable: Database.profilesBox.listenable(),
|
||||
builder: (context, profilesBox, widget) => LSDialog.content(
|
||||
@@ -33,13 +33,13 @@ class LSDialogSearch {
|
||||
height: 2,
|
||||
color: LSColors.accent,
|
||||
),
|
||||
value: lunaBox.get('profile'),
|
||||
value: lunaBox.get(LunaSeaDatabaseValue.ENABLED_PROFILE.key),
|
||||
items: (profilesBox as Box).keys.map<DropdownMenuItem<String>>((dynamic value) => DropdownMenuItem(
|
||||
value: value,
|
||||
child: Text(value),
|
||||
)).toList(),
|
||||
onChanged: (value) {
|
||||
lunaBox.put('profile', value);
|
||||
lunaBox.put(LunaSeaDatabaseValue.ENABLED_PROFILE.key, value);
|
||||
},
|
||||
isExpanded: true,
|
||||
),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
import 'package:lunasea/modules.dart';
|
||||
|
||||
class LSDialogSettings {
|
||||
LSDialogSettings._();
|
||||
@@ -115,8 +116,9 @@ class LSDialogSettings {
|
||||
textAlign: TextAlign.center,
|
||||
text: TextSpan(
|
||||
children: [
|
||||
LSDialog.textSpanContent(text: 'The exported logs can be found in '),
|
||||
LSDialog.bolded(title: '<On My Device>/LunaSea/FLogs', fontSize: 16.0),
|
||||
if(Platform.isIOS || Platform.isAndroid) LSDialog.textSpanContent(text: 'The exported logs can be found in '),
|
||||
if(Platform.isIOS) LSDialog.bolded(title: '<On My Device>/LunaSea/FLogs', fontSize: 16.0),
|
||||
if(Platform.isAndroid) LSDialog.bolded(title: '<Storage>/Android/data/app.lunasea.lunasea/files/FLogs', fontSize: 16.0),
|
||||
LSDialog.textSpanContent(text: '.'),
|
||||
],
|
||||
),
|
||||
@@ -394,4 +396,124 @@ class LSDialogSettings {
|
||||
);
|
||||
return [_flag, _profile];
|
||||
}
|
||||
|
||||
static Future<List> changeBrowser(BuildContext context) async {
|
||||
//Returns
|
||||
bool _flag = false;
|
||||
LSBrowsers _browser;
|
||||
//Setter
|
||||
void _setValues(bool flag, LSBrowsers browser) {
|
||||
_flag = flag;
|
||||
_browser = browser;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: LSDialog.title(text: 'Open Links In...'),
|
||||
actions: <Widget>[
|
||||
LSDialog.cancel(context, textColor: LSColors.accent),
|
||||
],
|
||||
content: LSDialog.content(
|
||||
children: List.generate(
|
||||
LSBrowsers.values.length,
|
||||
(index) => ListTile(
|
||||
leading: LSIcon(
|
||||
icon: LSBrowsers.values[index].icon,
|
||||
color: LSColors.list(index),
|
||||
),
|
||||
title: Text(
|
||||
LSBrowsers.values[index].name,
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
onTap: () => _setValues(true, LSBrowsers.values[index]),
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
contentPadding: EdgeInsets.only(left: 24.0, top: 20.0, right: 24.0),
|
||||
),
|
||||
);
|
||||
return [_flag, _browser];
|
||||
}
|
||||
|
||||
static Future<List> homeChangeStartingDay(BuildContext context) async {
|
||||
//Returns
|
||||
bool _flag = false;
|
||||
CalendarStartingDay _startingDate;
|
||||
//Setter
|
||||
void _setValues(bool flag, CalendarStartingDay startingDate) {
|
||||
_flag = flag;
|
||||
_startingDate = startingDate;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: LSDialog.title(text: 'Starting Day of Week'),
|
||||
actions: <Widget>[
|
||||
LSDialog.cancel(context, textColor: LSColors.accent),
|
||||
],
|
||||
content: LSDialog.content(
|
||||
children: List.generate(
|
||||
CalendarStartingDay.values.length,
|
||||
(index) => ListTile(
|
||||
leading: LSIcon(
|
||||
icon: CustomIcons.calendar,
|
||||
color: LSColors.list(index),
|
||||
),
|
||||
title: Text(
|
||||
CalendarStartingDay.values[index].name,
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
onTap: () => _setValues(true, CalendarStartingDay.values[index]),
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
contentPadding: EdgeInsets.only(left: 24.0, top: 20.0, right: 24.0),
|
||||
),
|
||||
);
|
||||
return [_flag, _startingDate];
|
||||
}
|
||||
|
||||
static Future<List> homeChangeStartingSize(BuildContext context) async {
|
||||
//Returns
|
||||
bool _flag = false;
|
||||
CalendarStartingSize _startingSize;
|
||||
//Setter
|
||||
void _setValues(bool flag, CalendarStartingSize startingSize) {
|
||||
_flag = flag;
|
||||
_startingSize = startingSize;
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: LSDialog.title(text: 'Starting Size'),
|
||||
actions: <Widget>[
|
||||
LSDialog.cancel(context, textColor: LSColors.accent),
|
||||
],
|
||||
content: LSDialog.content(
|
||||
children: List.generate(
|
||||
CalendarStartingSize.values.length,
|
||||
(index) => ListTile(
|
||||
leading: LSIcon(
|
||||
icon: CalendarStartingSize.values[index].icon,
|
||||
color: LSColors.list(index),
|
||||
),
|
||||
title: Text(
|
||||
CalendarStartingSize.values[index].name,
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
onTap: () => _setValues(true, CalendarStartingSize.values[index]),
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
contentPadding: EdgeInsets.only(left: 24.0, top: 20.0, right: 24.0),
|
||||
),
|
||||
);
|
||||
return [_flag, _startingSize];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
|
||||
@@ -157,62 +159,58 @@ class LSDialogSystem {
|
||||
},
|
||||
),
|
||||
],
|
||||
content: SingleChildScrollView(
|
||||
child: ListBody(
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'Are you sure you want to backup your current configuration?\n',
|
||||
content: LSDialog.content(
|
||||
children: [
|
||||
LSDialog.textContent(text: 'Are you sure you want to backup your current configuration?\n'),
|
||||
LSDialog.textContent(text: 'All backups are encrypted before being exported to the filesystem, please enter an encryption key for the backup.\n'),
|
||||
RichText(
|
||||
textAlign: TextAlign.center,
|
||||
text: TextSpan(
|
||||
children: [
|
||||
if(Platform.isIOS || Platform.isAndroid) LSDialog.textSpanContent(text: 'The backups can be found in '),
|
||||
if(Platform.isIOS) LSDialog.bolded(title: '<On My Device>/LunaSea/configurations', fontSize: 16.0),
|
||||
if(Platform.isAndroid) LSDialog.bolded(title: '<Storage>/Android/data/app.lunasea.lunasea/files/configurations', fontSize: 16.0),
|
||||
LSDialog.textSpanContent(text: '.'),
|
||||
],
|
||||
),
|
||||
),
|
||||
Form(
|
||||
key: formKey,
|
||||
child: TextFormField(
|
||||
autofocus: true,
|
||||
autocorrect: false,
|
||||
obscureText: true,
|
||||
controller: textController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Encryption Key',
|
||||
labelStyle: TextStyle(
|
||||
color: Colors.white54,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
),
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
cursorColor: Color(Constants.ACCENT_COLOR),
|
||||
validator: (value) {
|
||||
if(value.length < 8) {
|
||||
return 'Minimum of 8 characters';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
Text(
|
||||
'All backups are encrypted before being exported to the filesystem, please enter an encryption key for the backup.',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
Form(
|
||||
key: formKey,
|
||||
child: TextFormField(
|
||||
autofocus: true,
|
||||
autocorrect: false,
|
||||
obscureText: true,
|
||||
controller: textController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Encryption Key',
|
||||
labelStyle: TextStyle(
|
||||
color: Colors.white54,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
),
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
cursorColor: Color(Constants.ACCENT_COLOR),
|
||||
validator: (value) {
|
||||
if(value.length < 8) {
|
||||
return 'Minimum of 8 characters';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
padding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -291,7 +289,7 @@ class LSDialogSystem {
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Clear Configuration',
|
||||
'Reset LunaSea',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -311,7 +309,7 @@ class LSDialogSystem {
|
||||
),
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Clear',
|
||||
'Reset',
|
||||
style: TextStyle(
|
||||
color: Colors.red,
|
||||
),
|
||||
@@ -324,7 +322,7 @@ class LSDialogSystem {
|
||||
],
|
||||
content: SingleChildScrollView(
|
||||
child: Text(
|
||||
'Are you sure you want to clear your configuration?\n\nYou will be starting from a clean slate, please ensure you backup your current configuration first!',
|
||||
'Are you sure you want to reset LunaSea and clear your configuration?\n\nYou will be starting from a clean slate, please ensure you backup your current configuration first!',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
@@ -338,70 +336,6 @@ class LSDialogSystem {
|
||||
return [flag];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> showDeleteConfigurationPrompt(BuildContext context, List<String> paths) async {
|
||||
bool flag = false;
|
||||
String path = '';
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Delete Configuration',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: Color(Constants.ACCENT_COLOR),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
content: Container(
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: paths.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return ListTile(
|
||||
leading: Icon(
|
||||
Icons.delete,
|
||||
color: Constants.LIST_COLOR_ICONS[index%Constants.LIST_COLOR_ICONS.length],
|
||||
),
|
||||
title: Text(
|
||||
paths[(paths.length-index-1)].substring(
|
||||
paths[(paths.length-index-1)].indexOf('/configurations/')+16,
|
||||
paths[(paths.length-index-1)].indexOf('.json')
|
||||
).replaceAll('%', '\n'),
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onTap: () async {
|
||||
path = paths[paths.length-index-1];
|
||||
flag = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
contentPadding: EdgeInsets.fromLTRB(32.0, 0.0, 0.0, 0.0),
|
||||
);
|
||||
},
|
||||
),
|
||||
width: 400,
|
||||
),
|
||||
contentPadding: EdgeInsets.fromLTRB(0.0, 16.0, 0.0, 0.0),
|
||||
);
|
||||
},
|
||||
);
|
||||
return [flag, path];
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> showEncryptionKeyPrompt(BuildContext context) async {
|
||||
bool flag = false;
|
||||
final formKey = GlobalKey<FormState>();
|
||||
|
||||
@@ -1,21 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
|
||||
class LSDivider extends StatelessWidget {
|
||||
final double padding;
|
||||
final Color color;
|
||||
|
||||
LSDivider({
|
||||
this.padding = 14.0,
|
||||
this.color,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Divider(
|
||||
color: color == null
|
||||
? LSColors.accent
|
||||
: color,
|
||||
indent: padding,
|
||||
endIndent: padding,
|
||||
);
|
||||
Widget build(BuildContext context) => Divider();
|
||||
}
|
||||
@@ -6,25 +6,32 @@ class CustomIcons {
|
||||
static const _kFontFam = 'CustomIcons';
|
||||
static const _kFontPkg = null;
|
||||
|
||||
static const IconData arrow_right = const IconData(0xe800, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData calendar_missing = const IconData(0xe801, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData calendar = const IconData(0xe802, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData clients = const IconData(0xe803, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData documentation = const IconData(0xe804, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData github = const IconData(0xe805, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData history = const IconData(0xe806, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData home = const IconData(0xe807, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData layers = const IconData(0xe808, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData monitoring = const IconData(0xe809, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData movies = const IconData(0xe80a, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData music = const IconData(0xe80b, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData nzbget = const IconData(0xe80c, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData queue = const IconData(0xe80d, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData reddit = const IconData(0xe80e, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData rss = const IconData(0xe80f, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData sabnzbd = const IconData(0xe810, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData settings = const IconData(0xe811, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData television = const IconData(0xe812, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData upcoming = const IconData(0xe813, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData user = const IconData(0xe814, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData arrow_right = IconData(0xe800, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData calendar_missing = IconData(0xe801, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData calendar = IconData(0xe802, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData clients = IconData(0xe803, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData documentation = IconData(0xe804, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData github = IconData(0xe805, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData history = IconData(0xe806, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData home = IconData(0xe807, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData layers = IconData(0xe808, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData monitoring = IconData(0xe809, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData movies = IconData(0xe80a, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData music = IconData(0xe80b, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData nzbget = IconData(0xe80c, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData queue = IconData(0xe80d, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData reddit = IconData(0xe80e, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData rss = IconData(0xe80f, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData sabnzbd = IconData(0xe810, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData settings = IconData(0xe811, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData television = IconData(0xe812, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData upcoming = IconData(0xe813, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData user = IconData(0xe814, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData bravebrowser = IconData(0xe815, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData microsoftedge = IconData(0xe816, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData firefox = IconData(0xe817, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData safari = IconData(0xe818, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData chrome = IconData(0xe819, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData code = IconData(0xe81a, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData modules = IconData(0xe81b, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ class LSTextInputBar extends StatefulWidget {
|
||||
final IconData labelIcon;
|
||||
final void Function(String, bool) onChanged;
|
||||
final void Function(String) onSubmitted;
|
||||
final Color color;
|
||||
final EdgeInsets margin;
|
||||
|
||||
LSTextInputBar({
|
||||
@@ -18,7 +17,6 @@ class LSTextInputBar extends StatefulWidget {
|
||||
this.action = TextInputAction.search,
|
||||
this.labelText = 'Search...',
|
||||
this.labelIcon = Icons.search,
|
||||
this.color,
|
||||
this.margin = Constants.UI_CARD_MARGIN,
|
||||
});
|
||||
|
||||
@@ -69,8 +67,6 @@ class _State extends State<LSTextInputBar> {
|
||||
onChanged: (value) => widget.onChanged(value, false),
|
||||
onSubmitted: widget.onSubmitted,
|
||||
),
|
||||
color: widget.color == null
|
||||
? LSColors.secondary
|
||||
: widget.color,
|
||||
color: Theme.of(context).canvasColor,
|
||||
);
|
||||
}
|
||||
@@ -4,16 +4,20 @@ class LSListView extends StatelessWidget {
|
||||
final List<Widget> children;
|
||||
final bool padBottom;
|
||||
final EdgeInsetsGeometry customPadding;
|
||||
final ScrollController controller;
|
||||
|
||||
LSListView({
|
||||
@required this.children,
|
||||
this.padBottom = false,
|
||||
this.controller,
|
||||
this.customPadding = const EdgeInsets.symmetric(vertical: 8.0),
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scrollbar(
|
||||
controller: controller,
|
||||
child: ListView(
|
||||
controller: controller,
|
||||
children: children,
|
||||
padding: padBottom
|
||||
? EdgeInsets.fromLTRB(0.0, 8.0, 0.0, 20.0)
|
||||
|
||||
@@ -14,7 +14,7 @@ class LSNotEnabled extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) => LSGenericMessage(
|
||||
text: service == Constants.NO_SERVICES_ENABLED
|
||||
? 'No Services Enabled'
|
||||
? 'No Modules Enabled'
|
||||
: '$service Is Not Enabled',
|
||||
showButton: showButton,
|
||||
buttonText: 'Return Home',
|
||||
|
||||
@@ -14,6 +14,5 @@ Widget LSAppBar({
|
||||
),
|
||||
centerTitle: false,
|
||||
elevation: 0,
|
||||
backgroundColor: LSColors.secondary,
|
||||
actions: actions,
|
||||
);
|
||||
|
||||
@@ -14,7 +14,6 @@ Widget LSAppBarTabs({
|
||||
),
|
||||
centerTitle: false,
|
||||
elevation: 0,
|
||||
backgroundColor: LSColors.secondary,
|
||||
bottom: TabBar(
|
||||
tabs: [
|
||||
for(int i=0; i<tabTitles.length; i++)
|
||||
|
||||
@@ -25,7 +25,7 @@ class LSBottomNavigationBar extends StatelessWidget {
|
||||
iconSize: 24.0,
|
||||
padding: EdgeInsets.fromLTRB(18.0, 5.0, 12.0, 5.0),
|
||||
duration: Duration(milliseconds: Constants.UI_NAVIGATION_SPEED),
|
||||
tabBackgroundColor: LSColors.primary,
|
||||
tabBackgroundColor: Theme.of(context).canvasColor,
|
||||
activeColor: LSColors.accent,
|
||||
tabs: List.generate(
|
||||
icons.length,
|
||||
@@ -41,7 +41,8 @@ class LSBottomNavigationBar extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: LSColors.secondary,
|
||||
color: Theme.of(context).primaryColor,
|
||||
//LSColors.secondary,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,12 +10,12 @@ class LSDrawer extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => ValueListenableBuilder(
|
||||
valueListenable: Database.lunaSeaBox.listenable(keys: ['profile']),
|
||||
valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.ENABLED_PROFILE.key]),
|
||||
builder: (context, lunaBox, widget) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: Database.indexersBox.listenable(),
|
||||
builder: (context, indexerBox, widget) {
|
||||
ProfileHiveObject profile = Database.profilesBox.get(lunaBox.get('profile'));
|
||||
ProfileHiveObject profile = Database.profilesBox.get(lunaBox.get(LunaSeaDatabaseValue.ENABLED_PROFILE.key));
|
||||
return Drawer(
|
||||
child: ListView(
|
||||
children: _getDrawerEntries(context, profile, (ModuleFlags.SEARCH && (indexerBox as Box).length > 0)),
|
||||
@@ -33,20 +33,20 @@ class LSDrawer extends StatelessWidget {
|
||||
UserAccountsDrawerHeader(
|
||||
accountName: LSTitle(text: 'LunaSea'),
|
||||
accountEmail: ValueListenableBuilder(
|
||||
valueListenable: Database.lunaSeaBox.listenable(keys: ['profile']),
|
||||
valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.ENABLED_PROFILE.key]),
|
||||
builder: (context, lunaBox, widget) => ValueListenableBuilder(
|
||||
valueListenable: Database.profilesBox.listenable(),
|
||||
builder: (context, profilesBox, widget) => Padding(
|
||||
child: DropdownButton(
|
||||
icon: LSIcon(icon: Icons.arrow_drop_down, color: Colors.white70),
|
||||
underline: Container(),
|
||||
value: lunaBox.get('profile'),
|
||||
value: lunaBox.get(LunaSeaDatabaseValue.ENABLED_PROFILE.key),
|
||||
items: (profilesBox as Box).keys.map<DropdownMenuItem<String>>((dynamic value) => DropdownMenuItem(
|
||||
value: value,
|
||||
child: Text(value),
|
||||
)).toList(),
|
||||
onChanged: (value) {
|
||||
lunaBox.put('profile', value);
|
||||
lunaBox.put(LunaSeaDatabaseValue.ENABLED_PROFILE.key, value);
|
||||
},
|
||||
isDense: true,
|
||||
isExpanded: true,
|
||||
@@ -76,7 +76,7 @@ class LSDrawer extends StatelessWidget {
|
||||
route: '/settings',
|
||||
justPush: true,
|
||||
),
|
||||
LSDivider(padding: 18.0),
|
||||
LSDivider(),
|
||||
if(showIndexerSearch) _buildEntry(
|
||||
context: context,
|
||||
icon: Icons.search,
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:flutter_advanced_networkimage/provider.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
|
||||
class LSSliverAppBar extends StatelessWidget {
|
||||
final ScrollController _controller = ScrollController(initialScrollOffset: _EXPANDED_HEIGHT - 80.0);
|
||||
final String title;
|
||||
final String backgroundURI;
|
||||
final List<Widget> actions;
|
||||
@@ -16,41 +17,46 @@ class LSSliverAppBar extends StatelessWidget {
|
||||
this.actions,
|
||||
});
|
||||
|
||||
static const _EXPANDED_HEIGHT = 200.0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => NestedScrollView(
|
||||
controller: _controller,
|
||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||
SliverOverlapAbsorber(
|
||||
sliver: SliverSafeArea(
|
||||
top: false,
|
||||
bottom: false,
|
||||
sliver: SliverAppBar(
|
||||
expandedHeight: 200.0,
|
||||
expandedHeight: _EXPANDED_HEIGHT,
|
||||
pinned: true,
|
||||
elevation: Constants.UI_ELEVATION,
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
title: Container(
|
||||
child: Text(
|
||||
title,
|
||||
overflow: TextOverflow.fade,
|
||||
softWrap: false,
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
letterSpacing: Constants.UI_LETTER_SPACING,
|
||||
),
|
||||
),
|
||||
padding: EdgeInsets.symmetric(horizontal: 96.0),
|
||||
title: Text(
|
||||
title,
|
||||
overflow: TextOverflow.fade,
|
||||
softWrap: false,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
letterSpacing: Constants.UI_LETTER_SPACING,
|
||||
),
|
||||
),
|
||||
centerTitle: false,
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
collapseMode: CollapseMode.parallax,
|
||||
background: TransitionToImage(
|
||||
image: AdvancedNetworkImage(
|
||||
backgroundURI,
|
||||
useDiskCache: true,
|
||||
fallbackAssetImage: 'assets/images/secondary_color.png',
|
||||
fallbackAssetImage: LunaSeaDatabaseValue.THEME_AMOLED.data
|
||||
? 'assets/images/colors/black.png'
|
||||
: 'assets/images/colors/secondary.png',
|
||||
retryLimit: 1,
|
||||
),
|
||||
fit: BoxFit.cover,
|
||||
loadingWidget: Image.asset(
|
||||
'assets/images/secondary_color.png',
|
||||
LunaSeaDatabaseValue.THEME_AMOLED.data
|
||||
? 'assets/images/colors/black.png'
|
||||
: 'assets/images/colors/secondary.png',
|
||||
),
|
||||
color: LSColors.secondary.withAlpha((255/1.5).floor()),
|
||||
blendMode: BlendMode.darken,
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:flutter_advanced_networkimage/provider.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
|
||||
class LSSliverAppBarTabs extends StatelessWidget {
|
||||
final ScrollController _controller = ScrollController(initialScrollOffset: _EXPANDED_HEIGHT - 80.0);
|
||||
final String title;
|
||||
final String backgroundURI;
|
||||
final List<Widget> actions;
|
||||
@@ -18,42 +19,46 @@ class LSSliverAppBarTabs extends StatelessWidget {
|
||||
@required this.bottom,
|
||||
});
|
||||
|
||||
static const _EXPANDED_HEIGHT = 200.0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => NestedScrollView(
|
||||
controller: _controller,
|
||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||
SliverOverlapAbsorber(
|
||||
sliver: SliverSafeArea(
|
||||
top: false,
|
||||
bottom: false,
|
||||
sliver: SliverAppBar(
|
||||
expandedHeight: 200.0,
|
||||
expandedHeight: _EXPANDED_HEIGHT,
|
||||
pinned: true,
|
||||
elevation: Constants.UI_ELEVATION,
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
titlePadding: EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 64.0),
|
||||
title: Container(
|
||||
child: Text(
|
||||
title,
|
||||
overflow: TextOverflow.fade,
|
||||
softWrap: false,
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
letterSpacing: Constants.UI_LETTER_SPACING,
|
||||
),
|
||||
),
|
||||
padding: EdgeInsets.symmetric(horizontal: 96.0),
|
||||
title: Text(
|
||||
title,
|
||||
overflow: TextOverflow.fade,
|
||||
softWrap: false,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
letterSpacing: Constants.UI_LETTER_SPACING,
|
||||
),
|
||||
),
|
||||
centerTitle: false,
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
collapseMode: CollapseMode.parallax,
|
||||
background: TransitionToImage(
|
||||
image: AdvancedNetworkImage(
|
||||
backgroundURI,
|
||||
useDiskCache: true,
|
||||
fallbackAssetImage: 'assets/images/secondary_color.png',
|
||||
fallbackAssetImage: LunaSeaDatabaseValue.THEME_AMOLED.data
|
||||
? 'assets/images/colors/black.png'
|
||||
: 'assets/images/colors/secondary.png',
|
||||
retryLimit: 1,
|
||||
),
|
||||
fit: BoxFit.cover,
|
||||
loadingWidget: Image.asset(
|
||||
'assets/images/secondary_color.png',
|
||||
LunaSeaDatabaseValue.THEME_AMOLED.data
|
||||
? 'assets/images/colors/black.png'
|
||||
: 'assets/images/colors/secondary.png',
|
||||
),
|
||||
color: LSColors.secondary.withAlpha((255/1.5).floor()),
|
||||
blendMode: BlendMode.darken,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
|
||||
class LSRefreshIndicator extends StatefulWidget {
|
||||
final Widget child;
|
||||
@@ -21,7 +20,7 @@ class _State extends State<LSRefreshIndicator> {
|
||||
Widget build(BuildContext context) {
|
||||
return RefreshIndicator(
|
||||
key: widget.refreshKey,
|
||||
backgroundColor: LSColors.secondary,
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
onRefresh: widget.onRefresh,
|
||||
child: widget.child,
|
||||
);
|
||||
|
||||
@@ -80,6 +80,6 @@ Future<void> LSSnackBar({
|
||||
)
|
||||
: null,
|
||||
animationDuration: Duration(milliseconds: 375),
|
||||
backgroundColor: LSColors.secondary,
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
)..show(context);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ class LSHeader extends StatelessWidget {
|
||||
color: LSColors.accent,
|
||||
),
|
||||
),
|
||||
padding: EdgeInsets.only(top: 4.0),
|
||||
padding: EdgeInsets.only(top: 4.0, left: 1.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,24 +1,41 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
|
||||
void main() async {
|
||||
_init();
|
||||
Logger.initialize();
|
||||
await Database.initialize();
|
||||
runZoned<Future<void>>(
|
||||
runZonedGuarded<Future<void>>(
|
||||
() async => runApp(_BIOS()),
|
||||
onError: (Object error, StackTrace stack) => Logger.fatal(error, stack),
|
||||
(Object error, StackTrace stack) => Logger.fatal(error, stack),
|
||||
);
|
||||
}
|
||||
|
||||
void _init() {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||
systemNavigationBarColor: Colors.black,
|
||||
systemNavigationBarDividerColor: Colors.black,
|
||||
statusBarColor: Colors.transparent,
|
||||
));
|
||||
}
|
||||
|
||||
class _BIOS extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) => Providers.providers(
|
||||
child: MaterialApp(
|
||||
title: 'LunaSea',
|
||||
debugShowCheckedModeBanner: false,
|
||||
routes: Routes.getRoutes(),
|
||||
theme: Themes.getDefaultTheme(),
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.THEME_AMOLED.key]),
|
||||
builder: (context, box, _) {
|
||||
return MaterialApp(
|
||||
title: 'LunaSea',
|
||||
debugShowCheckedModeBanner: false,
|
||||
routes: Routes.getRoutes(),
|
||||
darkTheme: Themes.getDarkTheme(),
|
||||
theme: Themes.getDarkTheme(),
|
||||
);
|
||||
}
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export './home/api.dart';
|
||||
export './home/core.dart';
|
||||
export './home/routes.dart';
|
||||
export './home/state.dart';
|
||||
export './home/widgets.dart';
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/adapter.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
import '../../home.dart';
|
||||
|
||||
class CalendarAPI extends API {
|
||||
final Map<String, dynamic> lidarr;
|
||||
final Map<String, dynamic> radarr;
|
||||
final Map<String, dynamic> sonarr;
|
||||
|
||||
CalendarAPI._internal({
|
||||
@required this.lidarr,
|
||||
@required this.radarr,
|
||||
@required this.sonarr,
|
||||
});
|
||||
|
||||
factory CalendarAPI.from(ProfileHiveObject profile) {
|
||||
return CalendarAPI._internal(
|
||||
lidarr: profile?.getLidarr(),
|
||||
radarr: profile?.getRadarr(),
|
||||
sonarr: profile?.getSonarr(),
|
||||
);
|
||||
}
|
||||
|
||||
void logError(String methodName, String text, Object error) => Logger.error('package:lunasea/core/api/calendar/api.dart', methodName, 'Home: $text', error, StackTrace.current);
|
||||
void logWarning(String methodName, String text) => Logger.warning('package:lunasea/core/api/calendar/api.dart', methodName, 'Home: $text');
|
||||
|
||||
Future<bool> testConnection() async => true;
|
||||
|
||||
Future<Map<DateTime, List>> getUpcoming(DateTime today) async {
|
||||
Map<DateTime, List> _upcoming = {};
|
||||
await _getLidarrUpcoming(_upcoming, today);
|
||||
await _getRadarrUpcoming(_upcoming, today);
|
||||
await _getSonarrUpcoming(_upcoming, today);
|
||||
return _upcoming.isEmpty
|
||||
? await Future.delayed(Duration(milliseconds: 500), () => {})
|
||||
: _upcoming;
|
||||
}
|
||||
|
||||
Future<void> _getLidarrUpcoming(Map<DateTime, List> map, DateTime today, { int startOffset = 7, int endOffset = 60 }) async {
|
||||
try {
|
||||
if(ModuleFlags.AUTOMATION && ModuleFlags.LIDARR && lidarr['enabled']) {
|
||||
String start = DateFormat('y-MM-dd').format(today.subtract(Duration(days: startOffset)));
|
||||
String end = DateFormat('y-MM-dd').format(today.add(Duration(days: endOffset)));
|
||||
String uri = '${lidarr['host']}/api/v1/calendar?apikey=${lidarr['key']}&start=$start&end=$end';
|
||||
Dio _client = Dio();
|
||||
if(!lidarr['strict_tls']) {
|
||||
(_client.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {
|
||||
client.badCertificateCallback = (X509Certificate cert, String host, int port) => true;
|
||||
};
|
||||
}
|
||||
Response response = await _client.get(uri);
|
||||
if(response.data.length > 0) {
|
||||
for(var entry in response.data) {
|
||||
DateTime date = DateTime.tryParse(entry['releaseDate'] ?? '')?.toLocal()?.lsDateTime_floor();
|
||||
if(date != null) {
|
||||
List day = map[date] ?? [];
|
||||
day.add(CalendarLidarrData(
|
||||
id: entry['id'] ?? 0,
|
||||
title: entry['artist']['artistName'] ?? 'Unknown Artist',
|
||||
albumTitle: entry['title'] ?? 'Unknown Album Title',
|
||||
artistId: entry['artist']['id'] ?? 0,
|
||||
hasAllFiles: (entry['statistics'] != null ? entry['statistics']['percentOfTracks'] ?? 0 : 0) == 100,
|
||||
));
|
||||
map[date] = day;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logError('_getLidarrUpcoming', 'Failed to fetch Lidarr upcoming content', error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _getRadarrUpcoming(Map<DateTime, List> map, DateTime today, { int startOffset = 7, int endOffset = 60 }) async {
|
||||
try {
|
||||
if(ModuleFlags.AUTOMATION && ModuleFlags.RADARR && radarr['enabled']) {
|
||||
String start = DateFormat('y-MM-dd').format(today.subtract(Duration(days: startOffset)));
|
||||
String end = DateFormat('y-MM-dd').format(today.add(Duration(days: endOffset)));
|
||||
String uri = '${radarr['host']}/api/calendar?apikey=${radarr['key']}&start=$start&end=$end';
|
||||
Dio _client = Dio();
|
||||
if(!radarr['strict_tls']) {
|
||||
(_client.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {
|
||||
client.badCertificateCallback = (X509Certificate cert, String host, int port) => true;
|
||||
};
|
||||
}
|
||||
Response response = await _client.get(uri);
|
||||
if(response.data.length > 0) {
|
||||
for(var entry in response.data) {
|
||||
DateTime date = DateTime.tryParse(entry['physicalRelease'] ?? '')?.toLocal()?.lsDateTime_floor();
|
||||
if(date != null) {
|
||||
List day = map[date] ?? [];
|
||||
day.add(CalendarRadarrData(
|
||||
id: entry['id'] ?? 0,
|
||||
title: entry['title'] ?? 'Unknown Title',
|
||||
hasFile: entry['hasFile'] ?? false,
|
||||
fileQualityProfile: entry['hasFile'] ? entry['movieFile']['quality']['quality']['name'] : '',
|
||||
year: entry['year'] ?? 0,
|
||||
runtime: entry['runtime'] ?? 0,
|
||||
));
|
||||
map[date] = day;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logError('_getRadarrUpcoming', 'Failed to fetch Radarr upcoming content', error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _getSonarrUpcoming(Map<DateTime, List> map, DateTime today, { int startOffset = 7, int endOffset = 60 }) async {
|
||||
try {
|
||||
if(ModuleFlags.AUTOMATION && ModuleFlags.SONARR && sonarr['enabled']) {
|
||||
String start = DateFormat('y-MM-dd').format(today.subtract(Duration(days: startOffset)));
|
||||
String end = DateFormat('y-MM-dd').format(today.add(Duration(days: endOffset)));
|
||||
String uri = '${sonarr['host']}/api/calendar?apikey=${sonarr['key']}&start=$start&end=$end';
|
||||
Dio _client = Dio();
|
||||
if(!sonarr['strict_tls']) {
|
||||
(_client.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {
|
||||
client.badCertificateCallback = (X509Certificate cert, String host, int port) => true;
|
||||
};
|
||||
}
|
||||
Response response = await _client.get(uri);
|
||||
if(response.data.length > 0) {
|
||||
for(var entry in response.data) {
|
||||
DateTime date = DateTime.tryParse(entry['airDateUtc'] ?? '')?.toLocal()?.lsDateTime_floor();
|
||||
if(date != null) {
|
||||
List day = map[date] ?? [];
|
||||
day.add(CalendarSonarrData(
|
||||
id: entry['id'] ?? 0,
|
||||
seriesID: entry['seriesId'] ?? 0,
|
||||
title: entry['series']['title'] ?? 'Unknown Series',
|
||||
episodeTitle: entry['title'] ?? 'Unknown Episode Title',
|
||||
seasonNumber: entry['seasonNumber'] ?? -1,
|
||||
episodeNumber: entry['episodeNumber'] ?? -1,
|
||||
airTime: entry['airDateUtc'] ?? '',
|
||||
hasFile: entry['hasFile'] ?? false,
|
||||
fileQualityProfile: entry['hasFile'] ? entry['episodeFile']['quality']['quality']['name'] : '',
|
||||
));
|
||||
map[date] = day;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logError('_getSonarrUpcoming', 'Failed to fetch Sonarr upcoming content', error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
lib/modules/home/core.dart
Normal file
@@ -0,0 +1,3 @@
|
||||
export './core/api.dart';
|
||||
export './core/adapters.dart';
|
||||
export './core/database.dart';
|
||||
2
lib/modules/home/core/adapters.dart
Normal file
@@ -0,0 +1,2 @@
|
||||
export './adapters/calendar_starting_day.dart';
|
||||
export './adapters/calendar_starting_size.dart';
|
||||
50
lib/modules/home/core/adapters/calendar_starting_day.dart
Normal file
@@ -0,0 +1,50 @@
|
||||
import 'package:lunasea/core/database/database.dart';
|
||||
import 'package:table_calendar/table_calendar.dart';
|
||||
|
||||
part 'calendar_starting_day.g.dart';
|
||||
|
||||
@HiveType(typeId: 12, adapterName: 'CalendarStartingDayAdapter')
|
||||
enum CalendarStartingDay {
|
||||
@HiveField(0)
|
||||
MONDAY,
|
||||
@HiveField(1)
|
||||
TUESDAY,
|
||||
@HiveField(2)
|
||||
WEDNESDAY,
|
||||
@HiveField(3)
|
||||
THURSDAY,
|
||||
@HiveField(4)
|
||||
FRIDAY,
|
||||
@HiveField(5)
|
||||
SATURDAY,
|
||||
@HiveField(6)
|
||||
SUNDAY,
|
||||
}
|
||||
|
||||
extension CalendarStartingDayExtension on CalendarStartingDay {
|
||||
StartingDayOfWeek get data {
|
||||
switch(this) {
|
||||
case CalendarStartingDay.MONDAY: return StartingDayOfWeek.monday;
|
||||
case CalendarStartingDay.TUESDAY: return StartingDayOfWeek.tuesday;
|
||||
case CalendarStartingDay.WEDNESDAY: return StartingDayOfWeek.wednesday;
|
||||
case CalendarStartingDay.THURSDAY: return StartingDayOfWeek.thursday;
|
||||
case CalendarStartingDay.FRIDAY: return StartingDayOfWeek.friday;
|
||||
case CalendarStartingDay.SATURDAY: return StartingDayOfWeek.saturday;
|
||||
case CalendarStartingDay.SUNDAY: return StartingDayOfWeek.sunday;
|
||||
default: return StartingDayOfWeek.monday;
|
||||
}
|
||||
}
|
||||
|
||||
String get name {
|
||||
switch(this) {
|
||||
case CalendarStartingDay.MONDAY: return 'Monday';
|
||||
case CalendarStartingDay.TUESDAY: return 'Tuesday';
|
||||
case CalendarStartingDay.WEDNESDAY: return 'Wednesday';
|
||||
case CalendarStartingDay.THURSDAY: return 'Thursday';
|
||||
case CalendarStartingDay.FRIDAY: return 'Friday';
|
||||
case CalendarStartingDay.SATURDAY: return 'Saturday';
|
||||
case CalendarStartingDay.SUNDAY: return 'Sunday';
|
||||
default: return 'Unknown Starting Day';
|
||||
}
|
||||
}
|
||||
}
|
||||
61
lib/modules/home/core/adapters/calendar_starting_day.g.dart
Normal file
@@ -0,0 +1,61 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'calendar_starting_day.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class CalendarStartingDayAdapter extends TypeAdapter<CalendarStartingDay> {
|
||||
@override
|
||||
final typeId = 12;
|
||||
|
||||
@override
|
||||
CalendarStartingDay read(BinaryReader reader) {
|
||||
switch (reader.readByte()) {
|
||||
case 0:
|
||||
return CalendarStartingDay.MONDAY;
|
||||
case 1:
|
||||
return CalendarStartingDay.TUESDAY;
|
||||
case 2:
|
||||
return CalendarStartingDay.WEDNESDAY;
|
||||
case 3:
|
||||
return CalendarStartingDay.THURSDAY;
|
||||
case 4:
|
||||
return CalendarStartingDay.FRIDAY;
|
||||
case 5:
|
||||
return CalendarStartingDay.SATURDAY;
|
||||
case 6:
|
||||
return CalendarStartingDay.SUNDAY;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, CalendarStartingDay obj) {
|
||||
switch (obj) {
|
||||
case CalendarStartingDay.MONDAY:
|
||||
writer.writeByte(0);
|
||||
break;
|
||||
case CalendarStartingDay.TUESDAY:
|
||||
writer.writeByte(1);
|
||||
break;
|
||||
case CalendarStartingDay.WEDNESDAY:
|
||||
writer.writeByte(2);
|
||||
break;
|
||||
case CalendarStartingDay.THURSDAY:
|
||||
writer.writeByte(3);
|
||||
break;
|
||||
case CalendarStartingDay.FRIDAY:
|
||||
writer.writeByte(4);
|
||||
break;
|
||||
case CalendarStartingDay.SATURDAY:
|
||||
writer.writeByte(5);
|
||||
break;
|
||||
case CalendarStartingDay.SUNDAY:
|
||||
writer.writeByte(6);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
44
lib/modules/home/core/adapters/calendar_starting_size.dart
Normal file
@@ -0,0 +1,44 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lunasea/core/database/database.dart';
|
||||
import 'package:table_calendar/table_calendar.dart';
|
||||
|
||||
part 'calendar_starting_size.g.dart';
|
||||
|
||||
@HiveType(typeId: 13, adapterName: 'CalendarStartingSizeAdapter')
|
||||
enum CalendarStartingSize {
|
||||
@HiveField(0)
|
||||
ONE_WEEK,
|
||||
@HiveField(1)
|
||||
TWO_WEEKS,
|
||||
@HiveField(2)
|
||||
ONE_MONTH,
|
||||
}
|
||||
|
||||
extension CalendarStartingSizeExtension on CalendarStartingSize {
|
||||
CalendarFormat get data {
|
||||
switch(this) {
|
||||
case CalendarStartingSize.TWO_WEEKS: return CalendarFormat.twoWeeks;
|
||||
case CalendarStartingSize.ONE_MONTH: return CalendarFormat.month;
|
||||
case CalendarStartingSize.ONE_WEEK:
|
||||
default: return CalendarFormat.week;
|
||||
}
|
||||
}
|
||||
|
||||
String get name {
|
||||
switch(this) {
|
||||
case CalendarStartingSize.TWO_WEEKS: return 'Two Weeks';
|
||||
case CalendarStartingSize.ONE_MONTH: return 'One Month';
|
||||
case CalendarStartingSize.ONE_WEEK:
|
||||
default: return 'One Week';
|
||||
}
|
||||
}
|
||||
|
||||
IconData get icon {
|
||||
switch(this) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||