mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-03-11 13:15:36 +00:00
feat: Implement app lock feature with biometric authentication
- Added AppLockScreen for biometric authentication to unlock the app. - Introduced security settings screen to enable/disable app lock. - Integrated local_auth package for biometric authentication support. - Created security state providers to manage app lock state. - Updated chapter list tile widget to support dismiss actions for bookmarking and marking chapters as read. - Enhanced CBZ conversion process to include ComicInfo.xml metadata. - Added conditional UI elements based on platform capabilities. - Added Completed & Tracked filter in library
This commit is contained in:
parent
a5fd40fdfb
commit
f729380223
47 changed files with 3473 additions and 739 deletions
|
|
@ -1,7 +1,8 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.kodjodevf.mangayomi">
|
package="com.kodjodevf.mangayomi">
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,14 @@ import libmtorrentserver.Libmtorrentserver
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
import io.flutter.plugin.common.StandardMethodCodec
|
import io.flutter.plugin.common.StandardMethodCodec
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterFragmentActivity
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class MainActivity: FlutterActivity() {
|
class MainActivity: FlutterFragmentActivity() {
|
||||||
|
|
||||||
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
||||||
super.configureFlutterEngine(flutterEngine)
|
super.configureFlutterEngine(flutterEngine)
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,8 @@
|
||||||
<array>
|
<array>
|
||||||
<string>fetch</string>
|
<string>fetch</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>NSFaceIDUsageDescription</key>
|
||||||
|
<string>Mangayomi needs to authenticate using Face ID.</string>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>UIMainStoryboardFile</key>
|
<key>UIMainStoryboardFile</key>
|
||||||
|
|
|
||||||
|
|
@ -565,5 +565,17 @@
|
||||||
"forceLandscapeMode": "Force landscape mode",
|
"forceLandscapeMode": "Force landscape mode",
|
||||||
"forceLandscapeModeSubtitle": "Force the player to use landscape orientation.",
|
"forceLandscapeModeSubtitle": "Force the player to use landscape orientation.",
|
||||||
"dns_over_https": "DNS-over-HTTPS (DoH)",
|
"dns_over_https": "DNS-over-HTTPS (DoH)",
|
||||||
"dns_provider": "DNS Provider"
|
"dns_provider": "DNS Provider",
|
||||||
|
"tracked": "Tracked",
|
||||||
|
"auth_unlock_msg": "Authenticate to unlock Mangayomi",
|
||||||
|
"app_locked": "Mangayomi is locked",
|
||||||
|
"auth_to_continue": "Authenticate to continue",
|
||||||
|
"authenticating": "Authenticating...",
|
||||||
|
"unlock": "Unlock",
|
||||||
|
"security": "Security",
|
||||||
|
"auth_to_change_security_setting": "Authenticate to change security settings",
|
||||||
|
"app_lock": "App lock",
|
||||||
|
"require_biometric_or_device_credential": "Require biometric or device credential to open the app",
|
||||||
|
"biometric_or_device_credential_not_available": "Biometric authentication not available on this device",
|
||||||
|
"app_lock_description": "When app lock is enabled, you will be asked to authenticate \nevery time you open the app or switch back to it from the background."
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3466,6 +3466,78 @@ abstract class AppLocalizations {
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'DNS Provider'**
|
/// **'DNS Provider'**
|
||||||
String get dns_provider;
|
String get dns_provider;
|
||||||
|
|
||||||
|
/// No description provided for @tracked.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Tracked'**
|
||||||
|
String get tracked;
|
||||||
|
|
||||||
|
/// No description provided for @auth_unlock_msg.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Authenticate to unlock Mangayomi'**
|
||||||
|
String get auth_unlock_msg;
|
||||||
|
|
||||||
|
/// No description provided for @app_locked.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Mangayomi is locked'**
|
||||||
|
String get app_locked;
|
||||||
|
|
||||||
|
/// No description provided for @auth_to_continue.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Authenticate to continue'**
|
||||||
|
String get auth_to_continue;
|
||||||
|
|
||||||
|
/// No description provided for @authenticating.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Authenticating...'**
|
||||||
|
String get authenticating;
|
||||||
|
|
||||||
|
/// No description provided for @unlock.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Unlock'**
|
||||||
|
String get unlock;
|
||||||
|
|
||||||
|
/// No description provided for @security.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Security'**
|
||||||
|
String get security;
|
||||||
|
|
||||||
|
/// No description provided for @auth_to_change_security_setting.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Authenticate to change security settings'**
|
||||||
|
String get auth_to_change_security_setting;
|
||||||
|
|
||||||
|
/// No description provided for @app_lock.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'App lock'**
|
||||||
|
String get app_lock;
|
||||||
|
|
||||||
|
/// No description provided for @require_biometric_or_device_credential.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Require biometric or device credential to open the app'**
|
||||||
|
String get require_biometric_or_device_credential;
|
||||||
|
|
||||||
|
/// No description provided for @biometric_or_device_credential_not_available.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Biometric authentication not available on this device'**
|
||||||
|
String get biometric_or_device_credential_not_available;
|
||||||
|
|
||||||
|
/// No description provided for @app_lock_description.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'When app lock is enabled, you will be asked to authenticate \nevery time you open the app or switch back to it from the background.'**
|
||||||
|
String get app_lock_description;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppLocalizationsDelegate
|
class _AppLocalizationsDelegate
|
||||||
|
|
|
||||||
|
|
@ -1794,4 +1794,44 @@ class AppLocalizationsAr extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get dns_provider => 'DNS Provider';
|
String get dns_provider => 'DNS Provider';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tracked => 'Tracked';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_unlock_msg => 'Authenticate to unlock Mangayomi';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_locked => 'Mangayomi is locked';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_to_continue => 'Authenticate to continue';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get authenticating => 'Authenticating...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get unlock => 'Unlock';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get security => 'Security';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_to_change_security_setting =>
|
||||||
|
'Authenticate to change security settings';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_lock => 'App lock';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get require_biometric_or_device_credential =>
|
||||||
|
'Require biometric or device credential to open the app';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get biometric_or_device_credential_not_available =>
|
||||||
|
'Biometric authentication not available on this device';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_lock_description =>
|
||||||
|
'When app lock is enabled, you will be asked to authenticate \nevery time you open the app or switch back to it from the background.';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1800,4 +1800,44 @@ class AppLocalizationsAs extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get dns_provider => 'DNS Provider';
|
String get dns_provider => 'DNS Provider';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tracked => 'Tracked';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_unlock_msg => 'Authenticate to unlock Mangayomi';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_locked => 'Mangayomi is locked';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_to_continue => 'Authenticate to continue';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get authenticating => 'Authenticating...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get unlock => 'Unlock';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get security => 'Security';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_to_change_security_setting =>
|
||||||
|
'Authenticate to change security settings';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_lock => 'App lock';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get require_biometric_or_device_credential =>
|
||||||
|
'Require biometric or device credential to open the app';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get biometric_or_device_credential_not_available =>
|
||||||
|
'Biometric authentication not available on this device';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_lock_description =>
|
||||||
|
'When app lock is enabled, you will be asked to authenticate \nevery time you open the app or switch back to it from the background.';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1815,4 +1815,44 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get dns_provider => 'DNS Provider';
|
String get dns_provider => 'DNS Provider';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tracked => 'Tracked';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_unlock_msg => 'Authenticate to unlock Mangayomi';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_locked => 'Mangayomi is locked';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_to_continue => 'Authenticate to continue';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get authenticating => 'Authenticating...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get unlock => 'Unlock';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get security => 'Security';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_to_change_security_setting =>
|
||||||
|
'Authenticate to change security settings';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_lock => 'App lock';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get require_biometric_or_device_credential =>
|
||||||
|
'Require biometric or device credential to open the app';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get biometric_or_device_credential_not_available =>
|
||||||
|
'Biometric authentication not available on this device';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_lock_description =>
|
||||||
|
'When app lock is enabled, you will be asked to authenticate \nevery time you open the app or switch back to it from the background.';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1794,4 +1794,44 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get dns_provider => 'DNS Provider';
|
String get dns_provider => 'DNS Provider';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tracked => 'Tracked';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_unlock_msg => 'Authenticate to unlock Mangayomi';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_locked => 'Mangayomi is locked';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_to_continue => 'Authenticate to continue';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get authenticating => 'Authenticating...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get unlock => 'Unlock';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get security => 'Security';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_to_change_security_setting =>
|
||||||
|
'Authenticate to change security settings';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_lock => 'App lock';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get require_biometric_or_device_credential =>
|
||||||
|
'Require biometric or device credential to open the app';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get biometric_or_device_credential_not_available =>
|
||||||
|
'Biometric authentication not available on this device';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_lock_description =>
|
||||||
|
'When app lock is enabled, you will be asked to authenticate \nevery time you open the app or switch back to it from the background.';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1823,6 +1823,46 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get dns_provider => 'DNS Provider';
|
String get dns_provider => 'DNS Provider';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tracked => 'Tracked';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_unlock_msg => 'Authenticate to unlock Mangayomi';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_locked => 'Mangayomi is locked';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_to_continue => 'Authenticate to continue';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get authenticating => 'Authenticating...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get unlock => 'Unlock';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get security => 'Security';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_to_change_security_setting =>
|
||||||
|
'Authenticate to change security settings';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_lock => 'App lock';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get require_biometric_or_device_credential =>
|
||||||
|
'Require biometric or device credential to open the app';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get biometric_or_device_credential_not_available =>
|
||||||
|
'Biometric authentication not available on this device';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_lock_description =>
|
||||||
|
'When app lock is enabled, you will be asked to authenticate \nevery time you open the app or switch back to it from the background.';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The translations for Spanish Castilian, as used in Latin America and the Caribbean (`es_419`).
|
/// The translations for Spanish Castilian, as used in Latin America and the Caribbean (`es_419`).
|
||||||
|
|
|
||||||
|
|
@ -1823,4 +1823,44 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get dns_provider => 'DNS Provider';
|
String get dns_provider => 'DNS Provider';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tracked => 'Tracked';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_unlock_msg => 'Authenticate to unlock Mangayomi';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_locked => 'Mangayomi is locked';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_to_continue => 'Authenticate to continue';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get authenticating => 'Authenticating...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get unlock => 'Unlock';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get security => 'Security';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_to_change_security_setting =>
|
||||||
|
'Authenticate to change security settings';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_lock => 'App lock';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get require_biometric_or_device_credential =>
|
||||||
|
'Require biometric or device credential to open the app';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get biometric_or_device_credential_not_available =>
|
||||||
|
'Biometric authentication not available on this device';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_lock_description =>
|
||||||
|
'When app lock is enabled, you will be asked to authenticate \nevery time you open the app or switch back to it from the background.';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1800,4 +1800,44 @@ class AppLocalizationsHi extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get dns_provider => 'DNS Provider';
|
String get dns_provider => 'DNS Provider';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tracked => 'Tracked';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_unlock_msg => 'Authenticate to unlock Mangayomi';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_locked => 'Mangayomi is locked';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_to_continue => 'Authenticate to continue';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get authenticating => 'Authenticating...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get unlock => 'Unlock';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get security => 'Security';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_to_change_security_setting =>
|
||||||
|
'Authenticate to change security settings';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_lock => 'App lock';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get require_biometric_or_device_credential =>
|
||||||
|
'Require biometric or device credential to open the app';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get biometric_or_device_credential_not_available =>
|
||||||
|
'Biometric authentication not available on this device';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_lock_description =>
|
||||||
|
'When app lock is enabled, you will be asked to authenticate \nevery time you open the app or switch back to it from the background.';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1806,4 +1806,44 @@ class AppLocalizationsId extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get dns_provider => 'DNS Provider';
|
String get dns_provider => 'DNS Provider';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tracked => 'Tracked';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_unlock_msg => 'Authenticate to unlock Mangayomi';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_locked => 'Mangayomi is locked';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_to_continue => 'Authenticate to continue';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get authenticating => 'Authenticating...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get unlock => 'Unlock';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get security => 'Security';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_to_change_security_setting =>
|
||||||
|
'Authenticate to change security settings';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_lock => 'App lock';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get require_biometric_or_device_credential =>
|
||||||
|
'Require biometric or device credential to open the app';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get biometric_or_device_credential_not_available =>
|
||||||
|
'Biometric authentication not available on this device';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_lock_description =>
|
||||||
|
'When app lock is enabled, you will be asked to authenticate \nevery time you open the app or switch back to it from the background.';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1820,4 +1820,44 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get dns_provider => 'DNS Provider';
|
String get dns_provider => 'DNS Provider';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tracked => 'Tracked';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_unlock_msg => 'Authenticate to unlock Mangayomi';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_locked => 'Mangayomi is locked';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_to_continue => 'Authenticate to continue';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get authenticating => 'Authenticating...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get unlock => 'Unlock';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get security => 'Security';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_to_change_security_setting =>
|
||||||
|
'Authenticate to change security settings';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_lock => 'App lock';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get require_biometric_or_device_credential =>
|
||||||
|
'Require biometric or device credential to open the app';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get biometric_or_device_credential_not_available =>
|
||||||
|
'Biometric authentication not available on this device';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_lock_description =>
|
||||||
|
'When app lock is enabled, you will be asked to authenticate \nevery time you open the app or switch back to it from the background.';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1771,4 +1771,44 @@ class AppLocalizationsJa extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get dns_provider => 'DNS Provider';
|
String get dns_provider => 'DNS Provider';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tracked => 'Tracked';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_unlock_msg => 'Authenticate to unlock Mangayomi';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_locked => 'Mangayomi is locked';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_to_continue => 'Authenticate to continue';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get authenticating => 'Authenticating...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get unlock => 'Unlock';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get security => 'Security';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_to_change_security_setting =>
|
||||||
|
'Authenticate to change security settings';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_lock => 'App lock';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get require_biometric_or_device_credential =>
|
||||||
|
'Require biometric or device credential to open the app';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get biometric_or_device_credential_not_available =>
|
||||||
|
'Biometric authentication not available on this device';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_lock_description =>
|
||||||
|
'When app lock is enabled, you will be asked to authenticate \nevery time you open the app or switch back to it from the background.';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1818,6 +1818,46 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get dns_provider => 'DNS Provider';
|
String get dns_provider => 'DNS Provider';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tracked => 'Tracked';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_unlock_msg => 'Authenticate to unlock Mangayomi';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_locked => 'Mangayomi is locked';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_to_continue => 'Authenticate to continue';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get authenticating => 'Authenticating...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get unlock => 'Unlock';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get security => 'Security';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_to_change_security_setting =>
|
||||||
|
'Authenticate to change security settings';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_lock => 'App lock';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get require_biometric_or_device_credential =>
|
||||||
|
'Require biometric or device credential to open the app';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get biometric_or_device_credential_not_available =>
|
||||||
|
'Biometric authentication not available on this device';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_lock_description =>
|
||||||
|
'When app lock is enabled, you will be asked to authenticate \nevery time you open the app or switch back to it from the background.';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The translations for Portuguese, as used in Brazil (`pt_BR`).
|
/// The translations for Portuguese, as used in Brazil (`pt_BR`).
|
||||||
|
|
|
||||||
|
|
@ -1823,4 +1823,44 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get dns_provider => 'DNS Provider';
|
String get dns_provider => 'DNS Provider';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tracked => 'Tracked';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_unlock_msg => 'Authenticate to unlock Mangayomi';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_locked => 'Mangayomi is locked';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_to_continue => 'Authenticate to continue';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get authenticating => 'Authenticating...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get unlock => 'Unlock';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get security => 'Security';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_to_change_security_setting =>
|
||||||
|
'Authenticate to change security settings';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_lock => 'App lock';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get require_biometric_or_device_credential =>
|
||||||
|
'Require biometric or device credential to open the app';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get biometric_or_device_credential_not_available =>
|
||||||
|
'Biometric authentication not available on this device';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_lock_description =>
|
||||||
|
'When app lock is enabled, you will be asked to authenticate \nevery time you open the app or switch back to it from the background.';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1794,4 +1794,44 @@ class AppLocalizationsTh extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get dns_provider => 'DNS Provider';
|
String get dns_provider => 'DNS Provider';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tracked => 'Tracked';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_unlock_msg => 'Authenticate to unlock Mangayomi';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_locked => 'Mangayomi is locked';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_to_continue => 'Authenticate to continue';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get authenticating => 'Authenticating...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get unlock => 'Unlock';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get security => 'Security';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_to_change_security_setting =>
|
||||||
|
'Authenticate to change security settings';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_lock => 'App lock';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get require_biometric_or_device_credential =>
|
||||||
|
'Require biometric or device credential to open the app';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get biometric_or_device_credential_not_available =>
|
||||||
|
'Biometric authentication not available on this device';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_lock_description =>
|
||||||
|
'When app lock is enabled, you will be asked to authenticate \nevery time you open the app or switch back to it from the background.';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1806,4 +1806,44 @@ class AppLocalizationsTr extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get dns_provider => 'DNS Provider';
|
String get dns_provider => 'DNS Provider';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tracked => 'Tracked';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_unlock_msg => 'Authenticate to unlock Mangayomi';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_locked => 'Mangayomi is locked';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_to_continue => 'Authenticate to continue';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get authenticating => 'Authenticating...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get unlock => 'Unlock';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get security => 'Security';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_to_change_security_setting =>
|
||||||
|
'Authenticate to change security settings';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_lock => 'App lock';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get require_biometric_or_device_credential =>
|
||||||
|
'Require biometric or device credential to open the app';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get biometric_or_device_credential_not_available =>
|
||||||
|
'Biometric authentication not available on this device';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_lock_description =>
|
||||||
|
'When app lock is enabled, you will be asked to authenticate \nevery time you open the app or switch back to it from the background.';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1751,4 +1751,44 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get dns_provider => 'DNS Provider';
|
String get dns_provider => 'DNS Provider';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tracked => 'Tracked';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_unlock_msg => 'Authenticate to unlock Mangayomi';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_locked => 'Mangayomi is locked';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_to_continue => 'Authenticate to continue';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get authenticating => 'Authenticating...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get unlock => 'Unlock';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get security => 'Security';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get auth_to_change_security_setting =>
|
||||||
|
'Authenticate to change security settings';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_lock => 'App lock';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get require_biometric_or_device_credential =>
|
||||||
|
'Require biometric or device credential to open the app';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get biometric_or_device_credential_not_available =>
|
||||||
|
'Biometric authentication not available on this device';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_lock_description =>
|
||||||
|
'When app lock is enabled, you will be asked to authenticate \nevery time you open the app or switch back to it from the background.';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,8 @@ import 'package:mangayomi/utils/log/logger.dart';
|
||||||
import 'package:mangayomi/utils/url_protocol/api.dart';
|
import 'package:mangayomi/utils/url_protocol/api.dart';
|
||||||
import 'package:mangayomi/modules/more/settings/appearance/providers/theme_provider.dart';
|
import 'package:mangayomi/modules/more/settings/appearance/providers/theme_provider.dart';
|
||||||
import 'package:mangayomi/modules/library/providers/file_scanner.dart';
|
import 'package:mangayomi/modules/library/providers/file_scanner.dart';
|
||||||
|
import 'package:mangayomi/modules/more/settings/security/providers/security_state_provider.dart';
|
||||||
|
import 'package:mangayomi/modules/more/settings/security/app_lock_screen.dart';
|
||||||
import 'package:media_kit/media_kit.dart';
|
import 'package:media_kit/media_kit.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
@ -135,7 +137,7 @@ class MyApp extends ConsumerStatefulWidget {
|
||||||
ConsumerState<MyApp> createState() => _MyAppState();
|
ConsumerState<MyApp> createState() => _MyAppState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MyAppState extends ConsumerState<MyApp> {
|
class _MyAppState extends ConsumerState<MyApp> with WidgetsBindingObserver {
|
||||||
late AppLinks _appLinks;
|
late AppLinks _appLinks;
|
||||||
StreamSubscription<Uri>? _linkSubscription;
|
StreamSubscription<Uri>? _linkSubscription;
|
||||||
Uri? lastUri;
|
Uri? lastUri;
|
||||||
|
|
@ -143,6 +145,7 @@ class _MyAppState extends ConsumerState<MyApp> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
initializeDateFormatting();
|
initializeDateFormatting();
|
||||||
customDns = ref.read(customDnsStateProvider);
|
customDns = ref.read(customDnsStateProvider);
|
||||||
_checkTrackerRefresh();
|
_checkTrackerRefresh();
|
||||||
|
|
@ -162,6 +165,22 @@ class _MyAppState extends ConsumerState<MyApp> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
|
super.didChangeAppLifecycleState(state);
|
||||||
|
if (state == AppLifecycleState.paused ||
|
||||||
|
state == AppLifecycleState.hidden) {
|
||||||
|
if (Platform.isLinux) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Lock the app when going to background (if lock is enabled)
|
||||||
|
final lockEnabled = isar.settings.getSync(227)!.appLockEnabled ?? false;
|
||||||
|
if (lockEnabled) {
|
||||||
|
ref.read(appUnlockedStateProvider.notifier).lock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final followSystem = ref.watch(followSystemThemeStateProvider);
|
final followSystem = ref.watch(followSystemThemeStateProvider);
|
||||||
|
|
@ -180,7 +199,17 @@ class _MyAppState extends ConsumerState<MyApp> {
|
||||||
locale: locale,
|
locale: locale,
|
||||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||||
supportedLocales: AppLocalizations.supportedLocales,
|
supportedLocales: AppLocalizations.supportedLocales,
|
||||||
builder: BotToastInit(),
|
builder: Platform.isLinux
|
||||||
|
? null
|
||||||
|
: (context, child) {
|
||||||
|
child = BotToastInit()(context, child);
|
||||||
|
final isUnlocked = ref.watch(appUnlockedStateProvider);
|
||||||
|
final lockEnabled = ref.watch(appLockEnabledStateProvider);
|
||||||
|
if (lockEnabled && !isUnlocked) {
|
||||||
|
return const AppLockScreen();
|
||||||
|
}
|
||||||
|
return child;
|
||||||
|
},
|
||||||
routeInformationParser: router.routeInformationParser,
|
routeInformationParser: router.routeInformationParser,
|
||||||
routerDelegate: router.routerDelegate,
|
routerDelegate: router.routerDelegate,
|
||||||
routeInformationProvider: router.routeInformationProvider,
|
routeInformationProvider: router.routeInformationProvider,
|
||||||
|
|
@ -191,6 +220,7 @@ class _MyAppState extends ConsumerState<MyApp> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
MExtensionServerPlatform(ref).stopServer();
|
MExtensionServerPlatform(ref).stopServer();
|
||||||
_linkSubscription?.cancel();
|
_linkSubscription?.cancel();
|
||||||
discordRpc?.destroy();
|
discordRpc?.destroy();
|
||||||
|
|
|
||||||
|
|
@ -310,6 +310,20 @@ class Settings {
|
||||||
|
|
||||||
List<String>? localFolders;
|
List<String>? localFolders;
|
||||||
|
|
||||||
|
bool? appLockEnabled;
|
||||||
|
|
||||||
|
int? libraryFilterMangasCompletedType;
|
||||||
|
|
||||||
|
int? libraryFilterAnimeCompletedType;
|
||||||
|
|
||||||
|
int? libraryFilterNovelCompletedType;
|
||||||
|
|
||||||
|
int? libraryFilterMangasTrackingType;
|
||||||
|
|
||||||
|
int? libraryFilterAnimeTrackingType;
|
||||||
|
|
||||||
|
int? libraryFilterNovelTrackingType;
|
||||||
|
|
||||||
Settings({
|
Settings({
|
||||||
this.id = 227,
|
this.id = 227,
|
||||||
this.updatedAt = 0,
|
this.updatedAt = 0,
|
||||||
|
|
@ -450,6 +464,13 @@ class Settings {
|
||||||
this.downloadedOnlyMode = false,
|
this.downloadedOnlyMode = false,
|
||||||
this.algorithmWeights,
|
this.algorithmWeights,
|
||||||
this.localFolders,
|
this.localFolders,
|
||||||
|
this.appLockEnabled = false,
|
||||||
|
this.libraryFilterMangasCompletedType = 0,
|
||||||
|
this.libraryFilterAnimeCompletedType = 0,
|
||||||
|
this.libraryFilterNovelCompletedType = 0,
|
||||||
|
this.libraryFilterMangasTrackingType = 0,
|
||||||
|
this.libraryFilterAnimeTrackingType = 0,
|
||||||
|
this.libraryFilterNovelTrackingType = 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
Settings.fromJson(Map<String, dynamic> json) {
|
Settings.fromJson(Map<String, dynamic> json) {
|
||||||
|
|
@ -708,6 +729,13 @@ class Settings {
|
||||||
? AlgorithmWeights.fromJson(json['algorithmWeights'])
|
? AlgorithmWeights.fromJson(json['algorithmWeights'])
|
||||||
: null;
|
: null;
|
||||||
localFolders = json['localFolders'];
|
localFolders = json['localFolders'];
|
||||||
|
appLockEnabled = json['appLockEnabled'];
|
||||||
|
libraryFilterMangasCompletedType = json['libraryFilterMangasCompletedType'];
|
||||||
|
libraryFilterAnimeCompletedType = json['libraryFilterAnimeCompletedType'];
|
||||||
|
libraryFilterNovelCompletedType = json['libraryFilterNovelCompletedType'];
|
||||||
|
libraryFilterMangasTrackingType = json['libraryFilterMangasTrackingType'];
|
||||||
|
libraryFilterAnimeTrackingType = json['libraryFilterAnimeTrackingType'];
|
||||||
|
libraryFilterNovelTrackingType = json['libraryFilterNovelTrackingType'];
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
|
|
@ -872,6 +900,13 @@ class Settings {
|
||||||
if (algorithmWeights != null)
|
if (algorithmWeights != null)
|
||||||
'algorithmWeights': algorithmWeights!.toJson(),
|
'algorithmWeights': algorithmWeights!.toJson(),
|
||||||
'localFolders': localFolders,
|
'localFolders': localFolders,
|
||||||
|
'appLockEnabled': appLockEnabled,
|
||||||
|
'libraryFilterMangasCompletedType': libraryFilterMangasCompletedType,
|
||||||
|
'libraryFilterAnimeCompletedType': libraryFilterAnimeCompletedType,
|
||||||
|
'libraryFilterNovelCompletedType': libraryFilterNovelCompletedType,
|
||||||
|
'libraryFilterMangasTrackingType': libraryFilterMangasTrackingType,
|
||||||
|
'libraryFilterAnimeTrackingType': libraryFilterAnimeTrackingType,
|
||||||
|
'libraryFilterNovelTrackingType': libraryFilterNovelTrackingType,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -159,6 +159,12 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
|
||||||
final bookmarkedFilterType = watchWithSettingsAndManga(
|
final bookmarkedFilterType = watchWithSettingsAndManga(
|
||||||
mangaFilterBookmarkedStateProvider.call,
|
mangaFilterBookmarkedStateProvider.call,
|
||||||
);
|
);
|
||||||
|
final completedFilterType = watchWithSettingsAndManga(
|
||||||
|
mangaFilterCompletedStateProvider.call,
|
||||||
|
);
|
||||||
|
final trackingFilterType = watchWithSettingsAndManga(
|
||||||
|
mangaFilterTrackingStateProvider.call,
|
||||||
|
);
|
||||||
final sortType =
|
final sortType =
|
||||||
watchWithSettings(sortLibraryMangaStateProvider.call).index as int;
|
watchWithSettings(sortLibraryMangaStateProvider.call).index as int;
|
||||||
|
|
||||||
|
|
@ -174,6 +180,8 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
|
||||||
unreadFilterType: unreadFilterType,
|
unreadFilterType: unreadFilterType,
|
||||||
startedFilterType: startedFilterType,
|
startedFilterType: startedFilterType,
|
||||||
bookmarkedFilterType: bookmarkedFilterType,
|
bookmarkedFilterType: bookmarkedFilterType,
|
||||||
|
completedFilterType: completedFilterType,
|
||||||
|
trackingFilterType: trackingFilterType,
|
||||||
reverse: reverse,
|
reverse: reverse,
|
||||||
downloadedChapter: downloadedChapter,
|
downloadedChapter: downloadedChapter,
|
||||||
continueReaderBtn: continueReaderBtn,
|
continueReaderBtn: continueReaderBtn,
|
||||||
|
|
@ -195,6 +203,8 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
|
||||||
unreadFilterType: unreadFilterType,
|
unreadFilterType: unreadFilterType,
|
||||||
startedFilterType: startedFilterType,
|
startedFilterType: startedFilterType,
|
||||||
bookmarkedFilterType: bookmarkedFilterType,
|
bookmarkedFilterType: bookmarkedFilterType,
|
||||||
|
completedFilterType: completedFilterType,
|
||||||
|
trackingFilterType: trackingFilterType,
|
||||||
settings: settings,
|
settings: settings,
|
||||||
downloadedOnly: downloadedOnly,
|
downloadedOnly: downloadedOnly,
|
||||||
searchQuery: searchQuery,
|
searchQuery: searchQuery,
|
||||||
|
|
@ -217,6 +227,8 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
|
||||||
unreadFilterType: unreadFilterType,
|
unreadFilterType: unreadFilterType,
|
||||||
startedFilterType: startedFilterType,
|
startedFilterType: startedFilterType,
|
||||||
bookmarkedFilterType: bookmarkedFilterType,
|
bookmarkedFilterType: bookmarkedFilterType,
|
||||||
|
completedFilterType: completedFilterType,
|
||||||
|
trackingFilterType: trackingFilterType,
|
||||||
sortType: sortType,
|
sortType: sortType,
|
||||||
downloadedOnly: downloadedOnly,
|
downloadedOnly: downloadedOnly,
|
||||||
searchQuery: searchQuery,
|
searchQuery: searchQuery,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import 'package:isar_community/isar.dart';
|
||||||
import 'package:mangayomi/main.dart';
|
import 'package:mangayomi/main.dart';
|
||||||
import 'package:mangayomi/models/download.dart';
|
import 'package:mangayomi/models/download.dart';
|
||||||
import 'package:mangayomi/models/manga.dart';
|
import 'package:mangayomi/models/manga.dart';
|
||||||
|
import 'package:mangayomi/models/track.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
part 'library_filter_provider.g.dart';
|
part 'library_filter_provider.g.dart';
|
||||||
|
|
||||||
|
|
@ -17,10 +18,14 @@ Set<int> downloadedChapterIds(Ref ref) {
|
||||||
return downloads.whereType<int>().toSet();
|
return downloads.whereType<int>().toSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Pre-fetches all manga IDs that have at least one tracking entry.
|
||||||
|
@riverpod
|
||||||
|
Set<int> trackedMangaIds(Ref ref) {
|
||||||
|
final tracks = isar.tracks.where().findAllSync();
|
||||||
|
return tracks.map((t) => t.mangaId).whereType<int>().toSet();
|
||||||
|
}
|
||||||
|
|
||||||
/// Filters and sorts a list of [Manga] based on library filter/sort settings.
|
/// Filters and sorts a list of [Manga] based on library filter/sort settings.
|
||||||
///
|
|
||||||
/// Uses [downloadedChapterIds] for O(1) download lookups instead of
|
|
||||||
/// per-chapter Isar queries (previous behavior was O(chapters × manga)).
|
|
||||||
@riverpod
|
@riverpod
|
||||||
List<Manga> filteredLibraryManga(
|
List<Manga> filteredLibraryManga(
|
||||||
Ref ref, {
|
Ref ref, {
|
||||||
|
|
@ -29,12 +34,15 @@ List<Manga> filteredLibraryManga(
|
||||||
required int unreadFilterType,
|
required int unreadFilterType,
|
||||||
required int startedFilterType,
|
required int startedFilterType,
|
||||||
required int bookmarkedFilterType,
|
required int bookmarkedFilterType,
|
||||||
|
required int completedFilterType,
|
||||||
|
required int trackingFilterType,
|
||||||
required int sortType,
|
required int sortType,
|
||||||
required bool downloadedOnly,
|
required bool downloadedOnly,
|
||||||
required String searchQuery,
|
required String searchQuery,
|
||||||
required bool ignoreFiltersOnSearch,
|
required bool ignoreFiltersOnSearch,
|
||||||
}) {
|
}) {
|
||||||
final downloadedIds = ref.watch(downloadedChapterIdsProvider);
|
final downloadedIds = ref.watch(downloadedChapterIdsProvider);
|
||||||
|
final trackedIds = ref.watch(trackedMangaIdsProvider);
|
||||||
|
|
||||||
return _filterAndSortManga(
|
return _filterAndSortManga(
|
||||||
data: data,
|
data: data,
|
||||||
|
|
@ -42,11 +50,14 @@ List<Manga> filteredLibraryManga(
|
||||||
unreadFilterType: unreadFilterType,
|
unreadFilterType: unreadFilterType,
|
||||||
startedFilterType: startedFilterType,
|
startedFilterType: startedFilterType,
|
||||||
bookmarkedFilterType: bookmarkedFilterType,
|
bookmarkedFilterType: bookmarkedFilterType,
|
||||||
|
completedFilterType: completedFilterType,
|
||||||
|
trackingFilterType: trackingFilterType,
|
||||||
sortType: sortType,
|
sortType: sortType,
|
||||||
downloadedOnly: downloadedOnly,
|
downloadedOnly: downloadedOnly,
|
||||||
searchQuery: searchQuery,
|
searchQuery: searchQuery,
|
||||||
ignoreFiltersOnSearch: ignoreFiltersOnSearch,
|
ignoreFiltersOnSearch: ignoreFiltersOnSearch,
|
||||||
downloadedIds: downloadedIds,
|
downloadedIds: downloadedIds,
|
||||||
|
trackedIds: trackedIds,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -71,11 +82,14 @@ List<Manga> _filterAndSortManga({
|
||||||
required int unreadFilterType,
|
required int unreadFilterType,
|
||||||
required int startedFilterType,
|
required int startedFilterType,
|
||||||
required int bookmarkedFilterType,
|
required int bookmarkedFilterType,
|
||||||
|
required int completedFilterType,
|
||||||
|
required int trackingFilterType,
|
||||||
required int sortType,
|
required int sortType,
|
||||||
required bool downloadedOnly,
|
required bool downloadedOnly,
|
||||||
required String searchQuery,
|
required String searchQuery,
|
||||||
required bool ignoreFiltersOnSearch,
|
required bool ignoreFiltersOnSearch,
|
||||||
required Set<int> downloadedIds,
|
required Set<int> downloadedIds,
|
||||||
|
required Set<int> trackedIds,
|
||||||
}) {
|
}) {
|
||||||
List<Manga> mangas;
|
List<Manga> mangas;
|
||||||
|
|
||||||
|
|
@ -121,6 +135,24 @@ List<Manga> _filterAndSortManga({
|
||||||
if (!allNotBookmarked) return false;
|
if (!allNotBookmarked) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter by completed status
|
||||||
|
if (completedFilterType == 1) {
|
||||||
|
if (element.status != Status.completed) return false;
|
||||||
|
} else if (completedFilterType == 2) {
|
||||||
|
if (element.status == Status.completed) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter by tracking
|
||||||
|
if (trackingFilterType == 1) {
|
||||||
|
if (element.id == null || !trackedIds.contains(element.id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (trackingFilterType == 2) {
|
||||||
|
if (element.id != null && trackedIds.contains(element.id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Search filter
|
// Search filter
|
||||||
if (searchQuery.isNotEmpty) {
|
if (searchQuery.isNotEmpty) {
|
||||||
if (!_matchesSearchQuery(element, searchQuery)) return false;
|
if (!_matchesSearchQuery(element, searchQuery)) return false;
|
||||||
|
|
|
||||||
|
|
@ -58,26 +58,63 @@ final class DownloadedChapterIdsProvider
|
||||||
String _$downloadedChapterIdsHash() =>
|
String _$downloadedChapterIdsHash() =>
|
||||||
r'a51ff78fb0ad2548c719d1ca400ae474fc01e683';
|
r'a51ff78fb0ad2548c719d1ca400ae474fc01e683';
|
||||||
|
|
||||||
|
/// Pre-fetches all manga IDs that have at least one tracking entry.
|
||||||
|
|
||||||
|
@ProviderFor(trackedMangaIds)
|
||||||
|
final trackedMangaIdsProvider = TrackedMangaIdsProvider._();
|
||||||
|
|
||||||
|
/// Pre-fetches all manga IDs that have at least one tracking entry.
|
||||||
|
|
||||||
|
final class TrackedMangaIdsProvider
|
||||||
|
extends $FunctionalProvider<Set<int>, Set<int>, Set<int>>
|
||||||
|
with $Provider<Set<int>> {
|
||||||
|
/// Pre-fetches all manga IDs that have at least one tracking entry.
|
||||||
|
TrackedMangaIdsProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'trackedMangaIdsProvider',
|
||||||
|
isAutoDispose: true,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$trackedMangaIdsHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
$ProviderElement<Set<int>> $createElement($ProviderPointer pointer) =>
|
||||||
|
$ProviderElement(pointer);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<int> create(Ref ref) {
|
||||||
|
return trackedMangaIds(ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// {@macro riverpod.override_with_value}
|
||||||
|
Override overrideWithValue(Set<int> value) {
|
||||||
|
return $ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
providerOverride: $SyncValueProvider<Set<int>>(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$trackedMangaIdsHash() => r'8fd052ae3ff4e9fe47e66d5e24cd57233aa03d0a';
|
||||||
|
|
||||||
/// Filters and sorts a list of [Manga] based on library filter/sort settings.
|
/// Filters and sorts a list of [Manga] based on library filter/sort settings.
|
||||||
///
|
|
||||||
/// Uses [downloadedChapterIds] for O(1) download lookups instead of
|
|
||||||
/// per-chapter Isar queries (previous behavior was O(chapters × manga)).
|
|
||||||
|
|
||||||
@ProviderFor(filteredLibraryManga)
|
@ProviderFor(filteredLibraryManga)
|
||||||
final filteredLibraryMangaProvider = FilteredLibraryMangaFamily._();
|
final filteredLibraryMangaProvider = FilteredLibraryMangaFamily._();
|
||||||
|
|
||||||
/// Filters and sorts a list of [Manga] based on library filter/sort settings.
|
/// Filters and sorts a list of [Manga] based on library filter/sort settings.
|
||||||
///
|
|
||||||
/// Uses [downloadedChapterIds] for O(1) download lookups instead of
|
|
||||||
/// per-chapter Isar queries (previous behavior was O(chapters × manga)).
|
|
||||||
|
|
||||||
final class FilteredLibraryMangaProvider
|
final class FilteredLibraryMangaProvider
|
||||||
extends $FunctionalProvider<List<Manga>, List<Manga>, List<Manga>>
|
extends $FunctionalProvider<List<Manga>, List<Manga>, List<Manga>>
|
||||||
with $Provider<List<Manga>> {
|
with $Provider<List<Manga>> {
|
||||||
/// Filters and sorts a list of [Manga] based on library filter/sort settings.
|
/// Filters and sorts a list of [Manga] based on library filter/sort settings.
|
||||||
///
|
|
||||||
/// Uses [downloadedChapterIds] for O(1) download lookups instead of
|
|
||||||
/// per-chapter Isar queries (previous behavior was O(chapters × manga)).
|
|
||||||
FilteredLibraryMangaProvider._({
|
FilteredLibraryMangaProvider._({
|
||||||
required FilteredLibraryMangaFamily super.from,
|
required FilteredLibraryMangaFamily super.from,
|
||||||
required ({
|
required ({
|
||||||
|
|
@ -86,6 +123,8 @@ final class FilteredLibraryMangaProvider
|
||||||
int unreadFilterType,
|
int unreadFilterType,
|
||||||
int startedFilterType,
|
int startedFilterType,
|
||||||
int bookmarkedFilterType,
|
int bookmarkedFilterType,
|
||||||
|
int completedFilterType,
|
||||||
|
int trackingFilterType,
|
||||||
int sortType,
|
int sortType,
|
||||||
bool downloadedOnly,
|
bool downloadedOnly,
|
||||||
String searchQuery,
|
String searchQuery,
|
||||||
|
|
@ -125,6 +164,8 @@ final class FilteredLibraryMangaProvider
|
||||||
int unreadFilterType,
|
int unreadFilterType,
|
||||||
int startedFilterType,
|
int startedFilterType,
|
||||||
int bookmarkedFilterType,
|
int bookmarkedFilterType,
|
||||||
|
int completedFilterType,
|
||||||
|
int trackingFilterType,
|
||||||
int sortType,
|
int sortType,
|
||||||
bool downloadedOnly,
|
bool downloadedOnly,
|
||||||
String searchQuery,
|
String searchQuery,
|
||||||
|
|
@ -137,6 +178,8 @@ final class FilteredLibraryMangaProvider
|
||||||
unreadFilterType: argument.unreadFilterType,
|
unreadFilterType: argument.unreadFilterType,
|
||||||
startedFilterType: argument.startedFilterType,
|
startedFilterType: argument.startedFilterType,
|
||||||
bookmarkedFilterType: argument.bookmarkedFilterType,
|
bookmarkedFilterType: argument.bookmarkedFilterType,
|
||||||
|
completedFilterType: argument.completedFilterType,
|
||||||
|
trackingFilterType: argument.trackingFilterType,
|
||||||
sortType: argument.sortType,
|
sortType: argument.sortType,
|
||||||
downloadedOnly: argument.downloadedOnly,
|
downloadedOnly: argument.downloadedOnly,
|
||||||
searchQuery: argument.searchQuery,
|
searchQuery: argument.searchQuery,
|
||||||
|
|
@ -164,12 +207,9 @@ final class FilteredLibraryMangaProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$filteredLibraryMangaHash() =>
|
String _$filteredLibraryMangaHash() =>
|
||||||
r'34cd87ea154cc617e85572ede503b81fb36f2a97';
|
r'afecb3de71f1f8c1682a0bfd9949f8a372c7d1b6';
|
||||||
|
|
||||||
/// Filters and sorts a list of [Manga] based on library filter/sort settings.
|
/// Filters and sorts a list of [Manga] based on library filter/sort settings.
|
||||||
///
|
|
||||||
/// Uses [downloadedChapterIds] for O(1) download lookups instead of
|
|
||||||
/// per-chapter Isar queries (previous behavior was O(chapters × manga)).
|
|
||||||
|
|
||||||
final class FilteredLibraryMangaFamily extends $Family
|
final class FilteredLibraryMangaFamily extends $Family
|
||||||
with
|
with
|
||||||
|
|
@ -181,6 +221,8 @@ final class FilteredLibraryMangaFamily extends $Family
|
||||||
int unreadFilterType,
|
int unreadFilterType,
|
||||||
int startedFilterType,
|
int startedFilterType,
|
||||||
int bookmarkedFilterType,
|
int bookmarkedFilterType,
|
||||||
|
int completedFilterType,
|
||||||
|
int trackingFilterType,
|
||||||
int sortType,
|
int sortType,
|
||||||
bool downloadedOnly,
|
bool downloadedOnly,
|
||||||
String searchQuery,
|
String searchQuery,
|
||||||
|
|
@ -197,9 +239,6 @@ final class FilteredLibraryMangaFamily extends $Family
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Filters and sorts a list of [Manga] based on library filter/sort settings.
|
/// Filters and sorts a list of [Manga] based on library filter/sort settings.
|
||||||
///
|
|
||||||
/// Uses [downloadedChapterIds] for O(1) download lookups instead of
|
|
||||||
/// per-chapter Isar queries (previous behavior was O(chapters × manga)).
|
|
||||||
|
|
||||||
FilteredLibraryMangaProvider call({
|
FilteredLibraryMangaProvider call({
|
||||||
required List<Manga> data,
|
required List<Manga> data,
|
||||||
|
|
@ -207,6 +246,8 @@ final class FilteredLibraryMangaFamily extends $Family
|
||||||
required int unreadFilterType,
|
required int unreadFilterType,
|
||||||
required int startedFilterType,
|
required int startedFilterType,
|
||||||
required int bookmarkedFilterType,
|
required int bookmarkedFilterType,
|
||||||
|
required int completedFilterType,
|
||||||
|
required int trackingFilterType,
|
||||||
required int sortType,
|
required int sortType,
|
||||||
required bool downloadedOnly,
|
required bool downloadedOnly,
|
||||||
required String searchQuery,
|
required String searchQuery,
|
||||||
|
|
@ -218,6 +259,8 @@ final class FilteredLibraryMangaFamily extends $Family
|
||||||
unreadFilterType: unreadFilterType,
|
unreadFilterType: unreadFilterType,
|
||||||
startedFilterType: startedFilterType,
|
startedFilterType: startedFilterType,
|
||||||
bookmarkedFilterType: bookmarkedFilterType,
|
bookmarkedFilterType: bookmarkedFilterType,
|
||||||
|
completedFilterType: completedFilterType,
|
||||||
|
trackingFilterType: trackingFilterType,
|
||||||
sortType: sortType,
|
sortType: sortType,
|
||||||
downloadedOnly: downloadedOnly,
|
downloadedOnly: downloadedOnly,
|
||||||
searchQuery: searchQuery,
|
searchQuery: searchQuery,
|
||||||
|
|
|
||||||
|
|
@ -466,6 +466,118 @@ class MangaFilterBookmarkedState extends _$MangaFilterBookmarkedState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Completed filter ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
class MangaFilterCompletedState extends _$MangaFilterCompletedState {
|
||||||
|
@override
|
||||||
|
int build({
|
||||||
|
required List<Manga> mangaList,
|
||||||
|
required ItemType itemType,
|
||||||
|
required Settings settings,
|
||||||
|
}) {
|
||||||
|
state = getType();
|
||||||
|
return getType();
|
||||||
|
}
|
||||||
|
|
||||||
|
int getType() {
|
||||||
|
switch (itemType) {
|
||||||
|
case ItemType.manga:
|
||||||
|
return settings.libraryFilterMangasCompletedType ?? 0;
|
||||||
|
case ItemType.anime:
|
||||||
|
return settings.libraryFilterAnimeCompletedType ?? 0;
|
||||||
|
default:
|
||||||
|
return settings.libraryFilterNovelCompletedType ?? 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setType(int type) {
|
||||||
|
Settings appSettings = Settings();
|
||||||
|
switch (itemType) {
|
||||||
|
case ItemType.manga:
|
||||||
|
appSettings = settings..libraryFilterMangasCompletedType = type;
|
||||||
|
break;
|
||||||
|
case ItemType.anime:
|
||||||
|
appSettings = settings..libraryFilterAnimeCompletedType = type;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
appSettings = settings..libraryFilterNovelCompletedType = type;
|
||||||
|
}
|
||||||
|
isar.writeTxnSync(() {
|
||||||
|
isar.settings.putSync(
|
||||||
|
appSettings..updatedAt = DateTime.now().millisecondsSinceEpoch,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
state = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
void update() {
|
||||||
|
if (state == 0) {
|
||||||
|
setType(1);
|
||||||
|
} else if (state == 1) {
|
||||||
|
setType(2);
|
||||||
|
} else {
|
||||||
|
setType(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Tracking filter ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
class MangaFilterTrackingState extends _$MangaFilterTrackingState {
|
||||||
|
@override
|
||||||
|
int build({
|
||||||
|
required List<Manga> mangaList,
|
||||||
|
required ItemType itemType,
|
||||||
|
required Settings settings,
|
||||||
|
}) {
|
||||||
|
state = getType();
|
||||||
|
return getType();
|
||||||
|
}
|
||||||
|
|
||||||
|
int getType() {
|
||||||
|
switch (itemType) {
|
||||||
|
case ItemType.manga:
|
||||||
|
return settings.libraryFilterMangasTrackingType ?? 0;
|
||||||
|
case ItemType.anime:
|
||||||
|
return settings.libraryFilterAnimeTrackingType ?? 0;
|
||||||
|
default:
|
||||||
|
return settings.libraryFilterNovelTrackingType ?? 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setType(int type) {
|
||||||
|
Settings appSettings = Settings();
|
||||||
|
switch (itemType) {
|
||||||
|
case ItemType.manga:
|
||||||
|
appSettings = settings..libraryFilterMangasTrackingType = type;
|
||||||
|
break;
|
||||||
|
case ItemType.anime:
|
||||||
|
appSettings = settings..libraryFilterAnimeTrackingType = type;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
appSettings = settings..libraryFilterNovelTrackingType = type;
|
||||||
|
}
|
||||||
|
isar.writeTxnSync(() {
|
||||||
|
isar.settings.putSync(
|
||||||
|
appSettings..updatedAt = DateTime.now().millisecondsSinceEpoch,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
state = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
void update() {
|
||||||
|
if (state == 0) {
|
||||||
|
setType(1);
|
||||||
|
} else if (state == 1) {
|
||||||
|
setType(2);
|
||||||
|
} else {
|
||||||
|
setType(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@riverpod
|
@riverpod
|
||||||
class MangasFilterResultState extends _$MangasFilterResultState {
|
class MangasFilterResultState extends _$MangasFilterResultState {
|
||||||
@override
|
@override
|
||||||
|
|
@ -502,10 +614,26 @@ class MangasFilterResultState extends _$MangasFilterResultState {
|
||||||
settings: settings,
|
settings: settings,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
final completedFilterType = ref.watch(
|
||||||
|
mangaFilterCompletedStateProvider(
|
||||||
|
mangaList: mangaList,
|
||||||
|
itemType: itemType,
|
||||||
|
settings: settings,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final trackingFilterType = ref.watch(
|
||||||
|
mangaFilterTrackingStateProvider(
|
||||||
|
mangaList: mangaList,
|
||||||
|
itemType: itemType,
|
||||||
|
settings: settings,
|
||||||
|
),
|
||||||
|
);
|
||||||
return downloadFilterType == 0 &&
|
return downloadFilterType == 0 &&
|
||||||
unreadFilterType == 0 &&
|
unreadFilterType == 0 &&
|
||||||
startedFilterType == 0 &&
|
startedFilterType == 0 &&
|
||||||
bookmarkedFilterType == 0;
|
bookmarkedFilterType == 0 &&
|
||||||
|
completedFilterType == 0 &&
|
||||||
|
trackingFilterType == 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -693,6 +693,248 @@ abstract class _$MangaFilterBookmarkedState extends $Notifier<int> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ProviderFor(MangaFilterCompletedState)
|
||||||
|
final mangaFilterCompletedStateProvider = MangaFilterCompletedStateFamily._();
|
||||||
|
|
||||||
|
final class MangaFilterCompletedStateProvider
|
||||||
|
extends $NotifierProvider<MangaFilterCompletedState, int> {
|
||||||
|
MangaFilterCompletedStateProvider._({
|
||||||
|
required MangaFilterCompletedStateFamily super.from,
|
||||||
|
required ({List<Manga> mangaList, ItemType itemType, Settings settings})
|
||||||
|
super.argument,
|
||||||
|
}) : super(
|
||||||
|
retry: null,
|
||||||
|
name: r'mangaFilterCompletedStateProvider',
|
||||||
|
isAutoDispose: true,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$mangaFilterCompletedStateHash();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return r'mangaFilterCompletedStateProvider'
|
||||||
|
''
|
||||||
|
'$argument';
|
||||||
|
}
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
MangaFilterCompletedState create() => MangaFilterCompletedState();
|
||||||
|
|
||||||
|
/// {@macro riverpod.override_with_value}
|
||||||
|
Override overrideWithValue(int value) {
|
||||||
|
return $ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
providerOverride: $SyncValueProvider<int>(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is MangaFilterCompletedStateProvider &&
|
||||||
|
other.argument == argument;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
return argument.hashCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$mangaFilterCompletedStateHash() =>
|
||||||
|
r'8a9f60b94db16d65d29caa8598443c070f7c26e6';
|
||||||
|
|
||||||
|
final class MangaFilterCompletedStateFamily extends $Family
|
||||||
|
with
|
||||||
|
$ClassFamilyOverride<
|
||||||
|
MangaFilterCompletedState,
|
||||||
|
int,
|
||||||
|
int,
|
||||||
|
int,
|
||||||
|
({List<Manga> mangaList, ItemType itemType, Settings settings})
|
||||||
|
> {
|
||||||
|
MangaFilterCompletedStateFamily._()
|
||||||
|
: super(
|
||||||
|
retry: null,
|
||||||
|
name: r'mangaFilterCompletedStateProvider',
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
isAutoDispose: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
MangaFilterCompletedStateProvider call({
|
||||||
|
required List<Manga> mangaList,
|
||||||
|
required ItemType itemType,
|
||||||
|
required Settings settings,
|
||||||
|
}) => MangaFilterCompletedStateProvider._(
|
||||||
|
argument: (mangaList: mangaList, itemType: itemType, settings: settings),
|
||||||
|
from: this,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => r'mangaFilterCompletedStateProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _$MangaFilterCompletedState extends $Notifier<int> {
|
||||||
|
late final _$args =
|
||||||
|
ref.$arg
|
||||||
|
as ({List<Manga> mangaList, ItemType itemType, Settings settings});
|
||||||
|
List<Manga> get mangaList => _$args.mangaList;
|
||||||
|
ItemType get itemType => _$args.itemType;
|
||||||
|
Settings get settings => _$args.settings;
|
||||||
|
|
||||||
|
int build({
|
||||||
|
required List<Manga> mangaList,
|
||||||
|
required ItemType itemType,
|
||||||
|
required Settings settings,
|
||||||
|
});
|
||||||
|
@$mustCallSuper
|
||||||
|
@override
|
||||||
|
void runBuild() {
|
||||||
|
final ref = this.ref as $Ref<int, int>;
|
||||||
|
final element =
|
||||||
|
ref.element
|
||||||
|
as $ClassProviderElement<
|
||||||
|
AnyNotifier<int, int>,
|
||||||
|
int,
|
||||||
|
Object?,
|
||||||
|
Object?
|
||||||
|
>;
|
||||||
|
element.handleCreate(
|
||||||
|
ref,
|
||||||
|
() => build(
|
||||||
|
mangaList: _$args.mangaList,
|
||||||
|
itemType: _$args.itemType,
|
||||||
|
settings: _$args.settings,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ProviderFor(MangaFilterTrackingState)
|
||||||
|
final mangaFilterTrackingStateProvider = MangaFilterTrackingStateFamily._();
|
||||||
|
|
||||||
|
final class MangaFilterTrackingStateProvider
|
||||||
|
extends $NotifierProvider<MangaFilterTrackingState, int> {
|
||||||
|
MangaFilterTrackingStateProvider._({
|
||||||
|
required MangaFilterTrackingStateFamily super.from,
|
||||||
|
required ({List<Manga> mangaList, ItemType itemType, Settings settings})
|
||||||
|
super.argument,
|
||||||
|
}) : super(
|
||||||
|
retry: null,
|
||||||
|
name: r'mangaFilterTrackingStateProvider',
|
||||||
|
isAutoDispose: true,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$mangaFilterTrackingStateHash();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return r'mangaFilterTrackingStateProvider'
|
||||||
|
''
|
||||||
|
'$argument';
|
||||||
|
}
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
MangaFilterTrackingState create() => MangaFilterTrackingState();
|
||||||
|
|
||||||
|
/// {@macro riverpod.override_with_value}
|
||||||
|
Override overrideWithValue(int value) {
|
||||||
|
return $ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
providerOverride: $SyncValueProvider<int>(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is MangaFilterTrackingStateProvider &&
|
||||||
|
other.argument == argument;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
return argument.hashCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$mangaFilterTrackingStateHash() =>
|
||||||
|
r'fe79a139011725cf0a3d735930a41e1f593f0b70';
|
||||||
|
|
||||||
|
final class MangaFilterTrackingStateFamily extends $Family
|
||||||
|
with
|
||||||
|
$ClassFamilyOverride<
|
||||||
|
MangaFilterTrackingState,
|
||||||
|
int,
|
||||||
|
int,
|
||||||
|
int,
|
||||||
|
({List<Manga> mangaList, ItemType itemType, Settings settings})
|
||||||
|
> {
|
||||||
|
MangaFilterTrackingStateFamily._()
|
||||||
|
: super(
|
||||||
|
retry: null,
|
||||||
|
name: r'mangaFilterTrackingStateProvider',
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
isAutoDispose: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
MangaFilterTrackingStateProvider call({
|
||||||
|
required List<Manga> mangaList,
|
||||||
|
required ItemType itemType,
|
||||||
|
required Settings settings,
|
||||||
|
}) => MangaFilterTrackingStateProvider._(
|
||||||
|
argument: (mangaList: mangaList, itemType: itemType, settings: settings),
|
||||||
|
from: this,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => r'mangaFilterTrackingStateProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _$MangaFilterTrackingState extends $Notifier<int> {
|
||||||
|
late final _$args =
|
||||||
|
ref.$arg
|
||||||
|
as ({List<Manga> mangaList, ItemType itemType, Settings settings});
|
||||||
|
List<Manga> get mangaList => _$args.mangaList;
|
||||||
|
ItemType get itemType => _$args.itemType;
|
||||||
|
Settings get settings => _$args.settings;
|
||||||
|
|
||||||
|
int build({
|
||||||
|
required List<Manga> mangaList,
|
||||||
|
required ItemType itemType,
|
||||||
|
required Settings settings,
|
||||||
|
});
|
||||||
|
@$mustCallSuper
|
||||||
|
@override
|
||||||
|
void runBuild() {
|
||||||
|
final ref = this.ref as $Ref<int, int>;
|
||||||
|
final element =
|
||||||
|
ref.element
|
||||||
|
as $ClassProviderElement<
|
||||||
|
AnyNotifier<int, int>,
|
||||||
|
int,
|
||||||
|
Object?,
|
||||||
|
Object?
|
||||||
|
>;
|
||||||
|
element.handleCreate(
|
||||||
|
ref,
|
||||||
|
() => build(
|
||||||
|
mangaList: _$args.mangaList,
|
||||||
|
itemType: _$args.itemType,
|
||||||
|
settings: _$args.settings,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ProviderFor(MangasFilterResultState)
|
@ProviderFor(MangasFilterResultState)
|
||||||
final mangasFilterResultStateProvider = MangasFilterResultStateFamily._();
|
final mangasFilterResultStateProvider = MangasFilterResultStateFamily._();
|
||||||
|
|
||||||
|
|
@ -745,7 +987,7 @@ final class MangasFilterResultStateProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$mangasFilterResultStateHash() =>
|
String _$mangasFilterResultStateHash() =>
|
||||||
r'c6f916c35e9b7125ba073d09aa6838605b933b20';
|
r'6fbbc29f7e71e5d929f49fdaecd69a665bd034fb';
|
||||||
|
|
||||||
final class MangasFilterResultStateFamily extends $Family
|
final class MangasFilterResultStateFamily extends $Family
|
||||||
with
|
with
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ class LibraryBody extends ConsumerWidget {
|
||||||
final int unreadFilterType;
|
final int unreadFilterType;
|
||||||
final int startedFilterType;
|
final int startedFilterType;
|
||||||
final int bookmarkedFilterType;
|
final int bookmarkedFilterType;
|
||||||
|
final int completedFilterType;
|
||||||
|
final int trackingFilterType;
|
||||||
final bool reverse;
|
final bool reverse;
|
||||||
final bool downloadedChapter;
|
final bool downloadedChapter;
|
||||||
final bool continueReaderBtn;
|
final bool continueReaderBtn;
|
||||||
|
|
@ -45,6 +47,8 @@ class LibraryBody extends ConsumerWidget {
|
||||||
required this.unreadFilterType,
|
required this.unreadFilterType,
|
||||||
required this.startedFilterType,
|
required this.startedFilterType,
|
||||||
required this.bookmarkedFilterType,
|
required this.bookmarkedFilterType,
|
||||||
|
required this.completedFilterType,
|
||||||
|
required this.trackingFilterType,
|
||||||
required this.reverse,
|
required this.reverse,
|
||||||
required this.downloadedChapter,
|
required this.downloadedChapter,
|
||||||
required this.continueReaderBtn,
|
required this.continueReaderBtn,
|
||||||
|
|
@ -89,6 +93,8 @@ class LibraryBody extends ConsumerWidget {
|
||||||
unreadFilterType: unreadFilterType,
|
unreadFilterType: unreadFilterType,
|
||||||
startedFilterType: startedFilterType,
|
startedFilterType: startedFilterType,
|
||||||
bookmarkedFilterType: bookmarkedFilterType,
|
bookmarkedFilterType: bookmarkedFilterType,
|
||||||
|
completedFilterType: completedFilterType,
|
||||||
|
trackingFilterType: trackingFilterType,
|
||||||
sortType: sortType ?? 0,
|
sortType: sortType ?? 0,
|
||||||
downloadedOnly: downloadedOnly,
|
downloadedOnly: downloadedOnly,
|
||||||
searchQuery: searchQuery,
|
searchQuery: searchQuery,
|
||||||
|
|
@ -149,6 +155,8 @@ class CategoryBadge extends ConsumerWidget {
|
||||||
final int unreadFilterType;
|
final int unreadFilterType;
|
||||||
final int startedFilterType;
|
final int startedFilterType;
|
||||||
final int bookmarkedFilterType;
|
final int bookmarkedFilterType;
|
||||||
|
final int completedFilterType;
|
||||||
|
final int trackingFilterType;
|
||||||
final Settings settings;
|
final Settings settings;
|
||||||
final bool downloadedOnly;
|
final bool downloadedOnly;
|
||||||
final String searchQuery;
|
final String searchQuery;
|
||||||
|
|
@ -162,6 +170,8 @@ class CategoryBadge extends ConsumerWidget {
|
||||||
required this.unreadFilterType,
|
required this.unreadFilterType,
|
||||||
required this.startedFilterType,
|
required this.startedFilterType,
|
||||||
required this.bookmarkedFilterType,
|
required this.bookmarkedFilterType,
|
||||||
|
required this.completedFilterType,
|
||||||
|
required this.trackingFilterType,
|
||||||
required this.settings,
|
required this.settings,
|
||||||
required this.downloadedOnly,
|
required this.downloadedOnly,
|
||||||
required this.searchQuery,
|
required this.searchQuery,
|
||||||
|
|
@ -188,6 +198,8 @@ class CategoryBadge extends ConsumerWidget {
|
||||||
unreadFilterType: unreadFilterType,
|
unreadFilterType: unreadFilterType,
|
||||||
startedFilterType: startedFilterType,
|
startedFilterType: startedFilterType,
|
||||||
bookmarkedFilterType: bookmarkedFilterType,
|
bookmarkedFilterType: bookmarkedFilterType,
|
||||||
|
completedFilterType: completedFilterType,
|
||||||
|
trackingFilterType: trackingFilterType,
|
||||||
sortType: sortType ?? 0,
|
sortType: sortType ?? 0,
|
||||||
downloadedOnly: downloadedOnly,
|
downloadedOnly: downloadedOnly,
|
||||||
searchQuery: searchQuery,
|
searchQuery: searchQuery,
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,48 @@ class _FilterTab extends ConsumerWidget {
|
||||||
.update();
|
.update();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
ListTileChapterFilter(
|
||||||
|
label: l10n.completed,
|
||||||
|
type: ref.watch(
|
||||||
|
mangaFilterCompletedStateProvider(
|
||||||
|
itemType: itemType,
|
||||||
|
mangaList: entries,
|
||||||
|
settings: settings,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
ref
|
||||||
|
.read(
|
||||||
|
mangaFilterCompletedStateProvider(
|
||||||
|
itemType: itemType,
|
||||||
|
mangaList: entries,
|
||||||
|
settings: settings,
|
||||||
|
).notifier,
|
||||||
|
)
|
||||||
|
.update();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTileChapterFilter(
|
||||||
|
label: l10n.tracked,
|
||||||
|
type: ref.watch(
|
||||||
|
mangaFilterTrackingStateProvider(
|
||||||
|
itemType: itemType,
|
||||||
|
mangaList: entries,
|
||||||
|
settings: settings,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
ref
|
||||||
|
.read(
|
||||||
|
mangaFilterTrackingStateProvider(
|
||||||
|
itemType: itemType,
|
||||||
|
mangaList: entries,
|
||||||
|
settings: settings,
|
||||||
|
).notifier,
|
||||||
|
)
|
||||||
|
.update();
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:mangayomi/main.dart';
|
||||||
import 'package:mangayomi/modules/widgets/custom_extended_image_provider.dart';
|
import 'package:mangayomi/modules/widgets/custom_extended_image_provider.dart';
|
||||||
import 'package:mangayomi/modules/widgets/progress_center.dart';
|
import 'package:mangayomi/modules/widgets/progress_center.dart';
|
||||||
import 'package:mangayomi/utils/constant.dart';
|
import 'package:mangayomi/utils/constant.dart';
|
||||||
|
|
@ -30,110 +31,194 @@ class ChapterListTileWidget extends ConsumerWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final l10n = l10nLocalizations(context)!;
|
final l10n = l10nLocalizations(context)!;
|
||||||
return Container(
|
final isLongPressed = ref.watch(isLongPressedStateProvider);
|
||||||
color: chapterList.contains(chapter)
|
return Dismissible(
|
||||||
? context.primaryColor.withValues(alpha: 0.4)
|
key: ValueKey('chapter_swipe_${chapter.id}'),
|
||||||
: null,
|
direction: isLongPressed
|
||||||
child: GestureDetector(
|
? DismissDirection.none
|
||||||
onLongPress: () => _handleInteraction(ref),
|
: DismissDirection.horizontal,
|
||||||
onSecondaryTap: () => _handleInteraction(ref),
|
confirmDismiss: (direction) async {
|
||||||
child: ListTile(
|
if (direction == DismissDirection.startToEnd) {
|
||||||
contentPadding: EdgeInsets.symmetric(horizontal: 15),
|
// Swipe right → toggle bookmark
|
||||||
minLeadingWidth: 0,
|
final chap = chapter;
|
||||||
horizontalTitleGap: 13,
|
isar.writeTxnSync(() {
|
||||||
leading: Container(
|
chap.isBookmarked = !chap.isBookmarked!;
|
||||||
width: 2,
|
chap.updatedAt = DateTime.now().millisecondsSinceEpoch;
|
||||||
height: 40,
|
isar.chapters.putSync(chap);
|
||||||
decoration: BoxDecoration(
|
});
|
||||||
color: chapter.isRead!
|
} else if (direction == DismissDirection.endToStart) {
|
||||||
? Colors.grey.withValues(alpha: 0.3)
|
// Swipe left → toggle read
|
||||||
: context.primaryColor,
|
final chap = chapter;
|
||||||
borderRadius: BorderRadius.circular(10),
|
isar.writeTxnSync(() {
|
||||||
|
chap.isRead = !chap.isRead!;
|
||||||
|
if (!chap.isRead!) {
|
||||||
|
chap.lastPageRead = "1";
|
||||||
|
}
|
||||||
|
chap.updatedAt = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
isar.chapters.putSync(chap);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return false; // Don't dismiss, snap back
|
||||||
|
},
|
||||||
|
background: Container(
|
||||||
|
color: context.primaryColor,
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
child: Icon(
|
||||||
|
chapter.isBookmarked! ? Icons.bookmark_remove : Icons.bookmark_add,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
secondaryBackground: Container(
|
||||||
|
color: chapter.isRead! ? Colors.grey : Colors.green,
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
child: Icon(
|
||||||
|
chapter.isRead! ? Icons.visibility_off : Icons.done_all,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
color: chapterList.contains(chapter)
|
||||||
|
? context.primaryColor.withValues(alpha: 0.4)
|
||||||
|
: null,
|
||||||
|
child: GestureDetector(
|
||||||
|
onLongPress: () => _handleInteraction(ref),
|
||||||
|
onSecondaryTap: () => _handleInteraction(ref),
|
||||||
|
child: ListTile(
|
||||||
|
contentPadding: EdgeInsets.symmetric(horizontal: 15),
|
||||||
|
minLeadingWidth: 0,
|
||||||
|
horizontalTitleGap: 13,
|
||||||
|
leading: Container(
|
||||||
|
width: 2,
|
||||||
|
height: 40,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: chapter.isRead!
|
||||||
|
? Colors.grey.withValues(alpha: 0.3)
|
||||||
|
: context.primaryColor,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
tileColor: (chapter.isFiller ?? false)
|
||||||
tileColor: (chapter.isFiller ?? false)
|
? context.primaryColor.withValues(alpha: 0.15)
|
||||||
? context.primaryColor.withValues(alpha: 0.15)
|
: null,
|
||||||
: null,
|
textColor: chapter.isRead!
|
||||||
textColor: chapter.isRead!
|
? context.isLight
|
||||||
? context.isLight
|
? Colors.black.withValues(alpha: 0.4)
|
||||||
? Colors.black.withValues(alpha: 0.4)
|
: Colors.white.withValues(alpha: 0.3)
|
||||||
: Colors.white.withValues(alpha: 0.3)
|
: null,
|
||||||
: null,
|
selectedColor: chapter.isRead!
|
||||||
selectedColor: chapter.isRead!
|
? Colors.white.withValues(alpha: 0.3)
|
||||||
? Colors.white.withValues(alpha: 0.3)
|
: Colors.white,
|
||||||
: Colors.white,
|
onTap: () async => _handleInteraction(ref, context),
|
||||||
onTap: () async => _handleInteraction(ref, context),
|
title: Row(
|
||||||
title: Row(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
children: [
|
||||||
children: [
|
if (chapter.thumbnailUrl != null)
|
||||||
if (chapter.thumbnailUrl != null)
|
_thumbnailPreview(context, chapter.thumbnailUrl),
|
||||||
_thumbnailPreview(context, chapter.thumbnailUrl),
|
chapter.isBookmarked!
|
||||||
chapter.isBookmarked!
|
? Icon(
|
||||||
? Icon(Icons.bookmark, size: 16, color: context.primaryColor)
|
Icons.bookmark,
|
||||||
: SizedBox.shrink(),
|
size: 16,
|
||||||
chapter.description != null
|
|
||||||
? Flexible(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
_buildTitle(chapter.name!, context),
|
|
||||||
Text(
|
|
||||||
chapter.description!,
|
|
||||||
style: const TextStyle(fontSize: 11),
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: Flexible(child: _buildTitle(chapter.name!, context)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
subtitle: Row(
|
|
||||||
children: [
|
|
||||||
if (chapter.isFiller ?? false)
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Icon(Icons.label, size: 16, color: context.primaryColor),
|
|
||||||
Text(
|
|
||||||
" Filler ",
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 11,
|
|
||||||
color: context.primaryColor,
|
color: context.primaryColor,
|
||||||
),
|
)
|
||||||
),
|
: SizedBox.shrink(),
|
||||||
],
|
chapter.description != null
|
||||||
),
|
? Flexible(
|
||||||
if ((chapter.manga.value!.isLocalArchive ?? false) == false)
|
child: Column(
|
||||||
Text(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
chapter.dateUpload == null || chapter.dateUpload!.isEmpty
|
children: [
|
||||||
? ""
|
_buildTitle(chapter.name!, context),
|
||||||
: dateFormat(
|
Text(
|
||||||
chapter.dateUpload!,
|
chapter.description!,
|
||||||
ref: ref,
|
style: const TextStyle(fontSize: 11),
|
||||||
context: context,
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
style: const TextStyle(fontSize: 11),
|
)
|
||||||
),
|
: Flexible(child: _buildTitle(chapter.name!, context)),
|
||||||
if (!chapter.isRead!)
|
],
|
||||||
if (chapter.lastPageRead!.isNotEmpty &&
|
),
|
||||||
chapter.lastPageRead != "1")
|
subtitle: Row(
|
||||||
|
children: [
|
||||||
|
if (chapter.isFiller ?? false)
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.label, size: 16, color: context.primaryColor),
|
||||||
|
Text(
|
||||||
|
" Filler ",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
color: context.primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if ((chapter.manga.value!.isLocalArchive ?? false) == false)
|
||||||
|
Text(
|
||||||
|
chapter.dateUpload == null || chapter.dateUpload!.isEmpty
|
||||||
|
? ""
|
||||||
|
: dateFormat(
|
||||||
|
chapter.dateUpload!,
|
||||||
|
ref: ref,
|
||||||
|
context: context,
|
||||||
|
),
|
||||||
|
style: const TextStyle(fontSize: 11),
|
||||||
|
),
|
||||||
|
if (!chapter.isRead!)
|
||||||
|
if (chapter.lastPageRead!.isNotEmpty &&
|
||||||
|
chapter.lastPageRead != "1")
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Text(' • '),
|
||||||
|
Text(
|
||||||
|
chapter.manga.value!.itemType == ItemType.anime
|
||||||
|
? l10n.episode_progress(
|
||||||
|
Duration(
|
||||||
|
milliseconds: int.parse(
|
||||||
|
chapter.lastPageRead!,
|
||||||
|
),
|
||||||
|
).toString().substringBefore("."),
|
||||||
|
)
|
||||||
|
: l10n.page(
|
||||||
|
chapter.manga.value!.itemType ==
|
||||||
|
ItemType.manga
|
||||||
|
? chapter.lastPageRead!
|
||||||
|
: "${((double.tryParse(chapter.lastPageRead!) ?? 0) * 100).toStringAsFixed(0)} %",
|
||||||
|
),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
color: context.isLight
|
||||||
|
? Colors.black.withValues(alpha: 0.4)
|
||||||
|
: Colors.white.withValues(alpha: 0.3),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (chapter.scanlator?.isNotEmpty ?? false)
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const Text(' • '),
|
const Text(' • '),
|
||||||
Text(
|
Text(
|
||||||
chapter.manga.value!.itemType == ItemType.anime
|
chapter.scanlator!,
|
||||||
? l10n.episode_progress(
|
style: TextStyle(
|
||||||
Duration(
|
fontSize: 11,
|
||||||
milliseconds: int.parse(
|
color: chapter.isRead!
|
||||||
chapter.lastPageRead!,
|
? context.isLight
|
||||||
),
|
? Colors.black.withValues(alpha: 0.4)
|
||||||
).toString().substringBefore("."),
|
: Colors.white.withValues(alpha: 0.3)
|
||||||
)
|
: null,
|
||||||
: l10n.page(
|
),
|
||||||
chapter.manga.value!.itemType == ItemType.manga
|
),
|
||||||
? chapter.lastPageRead!
|
],
|
||||||
: "${((double.tryParse(chapter.lastPageRead!) ?? 0) * 100).toStringAsFixed(0)} %",
|
),
|
||||||
),
|
if (chapter.downloadSize != null)
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Text(' • '),
|
||||||
|
Text(
|
||||||
|
chapter.downloadSize!,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
color: context.isLight
|
color: context.isLight
|
||||||
|
|
@ -143,44 +228,13 @@ class ChapterListTileWidget extends ConsumerWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (chapter.scanlator?.isNotEmpty ?? false)
|
],
|
||||||
Row(
|
),
|
||||||
children: [
|
trailing:
|
||||||
const Text(' • '),
|
!sourceExist || (chapter.manga.value!.isLocalArchive ?? false)
|
||||||
Text(
|
? null
|
||||||
chapter.scanlator!,
|
: ChapterPageDownload(chapter: chapter),
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 11,
|
|
||||||
color: chapter.isRead!
|
|
||||||
? context.isLight
|
|
||||||
? Colors.black.withValues(alpha: 0.4)
|
|
||||||
: Colors.white.withValues(alpha: 0.3)
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (chapter.downloadSize != null)
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
const Text(' • '),
|
|
||||||
Text(
|
|
||||||
chapter.downloadSize!,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 11,
|
|
||||||
color: context.isLight
|
|
||||||
? Colors.black.withValues(alpha: 0.4)
|
|
||||||
: Colors.white.withValues(alpha: 0.3),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
trailing:
|
|
||||||
!sourceExist || (chapter.manga.value!.isLocalArchive ?? false)
|
|
||||||
? null
|
|
||||||
: ChapterPageDownload(chapter: chapter),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:archive/archive_io.dart';
|
import 'package:archive/archive_io.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
@ -5,19 +6,96 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
part 'convert_to_cbz.g.dart';
|
part 'convert_to_cbz.g.dart';
|
||||||
|
|
||||||
|
/// Metadata for ComicInfo.xml generation (serializable for isolate).
|
||||||
|
class ComicInfoData {
|
||||||
|
final String? title;
|
||||||
|
final String? series;
|
||||||
|
final String? number;
|
||||||
|
final String? writer;
|
||||||
|
final String? penciller;
|
||||||
|
final String? summary;
|
||||||
|
final String? genre;
|
||||||
|
final String? translator;
|
||||||
|
final String? publishingStatusStr;
|
||||||
|
final int pageCount;
|
||||||
|
|
||||||
|
const ComicInfoData({
|
||||||
|
this.title,
|
||||||
|
this.series,
|
||||||
|
this.number,
|
||||||
|
this.writer,
|
||||||
|
this.penciller,
|
||||||
|
this.summary,
|
||||||
|
this.genre,
|
||||||
|
this.translator,
|
||||||
|
this.publishingStatusStr,
|
||||||
|
this.pageCount = 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@riverpod
|
@riverpod
|
||||||
Future<List<String>> convertToCBZ(
|
Future<List<String>> convertToCBZ(
|
||||||
Ref ref,
|
Ref ref,
|
||||||
String chapterDir,
|
String chapterDir,
|
||||||
String mangaDir,
|
String mangaDir,
|
||||||
String chapterName,
|
String chapterName,
|
||||||
List<String> pageList,
|
List<String> pageList, {
|
||||||
) async {
|
ComicInfoData? comicInfo,
|
||||||
return compute(_convertToCBZ, (chapterDir, mangaDir, chapterName, pageList));
|
}) async {
|
||||||
|
return compute(_convertToCBZ, (
|
||||||
|
chapterDir,
|
||||||
|
mangaDir,
|
||||||
|
chapterName,
|
||||||
|
pageList,
|
||||||
|
comicInfo,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> _convertToCBZ((String, String, String, List<String>) datas) {
|
String _buildComicInfoXml(ComicInfoData info, int pageCount) {
|
||||||
final (chapterDir, mangaDir, chapterName, pageList) = datas;
|
final sb = StringBuffer();
|
||||||
|
sb.writeln('<?xml version="1.0" encoding="utf-8"?>');
|
||||||
|
sb.writeln(
|
||||||
|
'<ComicInfo xmlns:xsd="http://www.w3.org/2001/XMLSchema" '
|
||||||
|
'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">',
|
||||||
|
);
|
||||||
|
|
||||||
|
void addTag(String tag, String? value) {
|
||||||
|
if (value != null && value.isNotEmpty) {
|
||||||
|
final escaped = _xmlEscape(value);
|
||||||
|
sb.writeln(' <$tag>$escaped</$tag>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addTag('Title', info.title);
|
||||||
|
addTag('Series', info.series);
|
||||||
|
addTag('Number', info.number);
|
||||||
|
addTag('Writer', info.writer);
|
||||||
|
addTag('Penciller', info.penciller);
|
||||||
|
addTag('Summary', info.summary);
|
||||||
|
addTag('Genre', info.genre);
|
||||||
|
addTag('Translator', info.translator);
|
||||||
|
if (pageCount > 0) {
|
||||||
|
sb.writeln(' <PageCount>$pageCount</PageCount>');
|
||||||
|
}
|
||||||
|
addTag('PublishingStatusTachiyomi', info.publishingStatusStr);
|
||||||
|
|
||||||
|
sb.writeln('</ComicInfo>');
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
String _xmlEscape(String value) {
|
||||||
|
return value
|
||||||
|
.replaceAll('&', '&')
|
||||||
|
.replaceAll('<', '<')
|
||||||
|
.replaceAll('>', '>')
|
||||||
|
.replaceAll('"', '"')
|
||||||
|
.replaceAll("'", ''');
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> _convertToCBZ(
|
||||||
|
(String, String, String, List<String>, ComicInfoData?) datas,
|
||||||
|
) {
|
||||||
|
final (chapterDir, mangaDir, chapterName, pageList, comicInfo) = datas;
|
||||||
final imagesPaths = pageList.where((path) => path.endsWith('.jpg')).toList()
|
final imagesPaths = pageList.where((path) => path.endsWith('.jpg')).toList()
|
||||||
..sort();
|
..sort();
|
||||||
|
|
||||||
|
|
@ -39,6 +117,13 @@ List<String> _convertToCBZ((String, String, String, List<String>) datas) {
|
||||||
archive.add(ArchiveFile.bytes(fileName, bytes));
|
archive.add(ArchiveFile.bytes(fileName, bytes));
|
||||||
includedFiles.add(imagePath);
|
includedFiles.add(imagePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add ComicInfo.xml if metadata is provided
|
||||||
|
if (comicInfo != null) {
|
||||||
|
final xml = _buildComicInfoXml(comicInfo, includedFiles.length);
|
||||||
|
archive.add(ArchiveFile.bytes('ComicInfo.xml', utf8.encode(xml)));
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final cbzData = ZipEncoder().encode(archive);
|
final cbzData = ZipEncoder().encode(archive);
|
||||||
File(cbzPath).writeAsBytesSync(cbzData);
|
File(cbzPath).writeAsBytesSync(cbzData);
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,8 @@ final class ConvertToCBZProvider
|
||||||
with $FutureModifier<List<String>>, $FutureProvider<List<String>> {
|
with $FutureModifier<List<String>>, $FutureProvider<List<String>> {
|
||||||
ConvertToCBZProvider._({
|
ConvertToCBZProvider._({
|
||||||
required ConvertToCBZFamily super.from,
|
required ConvertToCBZFamily super.from,
|
||||||
required (String, String, String, List<String>) super.argument,
|
required (String, String, String, List<String>, {ComicInfoData? comicInfo})
|
||||||
|
super.argument,
|
||||||
}) : super(
|
}) : super(
|
||||||
retry: null,
|
retry: null,
|
||||||
name: r'convertToCBZProvider',
|
name: r'convertToCBZProvider',
|
||||||
|
|
@ -49,13 +50,22 @@ final class ConvertToCBZProvider
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<List<String>> create(Ref ref) {
|
FutureOr<List<String>> create(Ref ref) {
|
||||||
final argument = this.argument as (String, String, String, List<String>);
|
final argument =
|
||||||
|
this.argument
|
||||||
|
as (
|
||||||
|
String,
|
||||||
|
String,
|
||||||
|
String,
|
||||||
|
List<String>, {
|
||||||
|
ComicInfoData? comicInfo,
|
||||||
|
});
|
||||||
return convertToCBZ(
|
return convertToCBZ(
|
||||||
ref,
|
ref,
|
||||||
argument.$1,
|
argument.$1,
|
||||||
argument.$2,
|
argument.$2,
|
||||||
argument.$3,
|
argument.$3,
|
||||||
argument.$4,
|
argument.$4,
|
||||||
|
comicInfo: argument.comicInfo,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,13 +80,13 @@ final class ConvertToCBZProvider
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$convertToCBZHash() => r'56f4320034ec2420c8c2c2b22a2522721181ab54';
|
String _$convertToCBZHash() => r'0f75969b8eccb5932089e5e269a5bba4012842b8';
|
||||||
|
|
||||||
final class ConvertToCBZFamily extends $Family
|
final class ConvertToCBZFamily extends $Family
|
||||||
with
|
with
|
||||||
$FunctionalFamilyOverride<
|
$FunctionalFamilyOverride<
|
||||||
FutureOr<List<String>>,
|
FutureOr<List<String>>,
|
||||||
(String, String, String, List<String>)
|
(String, String, String, List<String>, {ComicInfoData? comicInfo})
|
||||||
> {
|
> {
|
||||||
ConvertToCBZFamily._()
|
ConvertToCBZFamily._()
|
||||||
: super(
|
: super(
|
||||||
|
|
@ -91,9 +101,16 @@ final class ConvertToCBZFamily extends $Family
|
||||||
String chapterDir,
|
String chapterDir,
|
||||||
String mangaDir,
|
String mangaDir,
|
||||||
String chapterName,
|
String chapterName,
|
||||||
List<String> pageList,
|
List<String> pageList, {
|
||||||
) => ConvertToCBZProvider._(
|
ComicInfoData? comicInfo,
|
||||||
argument: (chapterDir, mangaDir, chapterName, pageList),
|
}) => ConvertToCBZProvider._(
|
||||||
|
argument: (
|
||||||
|
chapterDir,
|
||||||
|
mangaDir,
|
||||||
|
chapterName,
|
||||||
|
pageList,
|
||||||
|
comicInfo: comicInfo,
|
||||||
|
),
|
||||||
from: this,
|
from: this,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import 'package:mangayomi/services/get_chapter_pages.dart';
|
||||||
import 'package:mangayomi/services/http/m_client.dart';
|
import 'package:mangayomi/services/http/m_client.dart';
|
||||||
import 'package:mangayomi/services/download_manager/m3u8/m3u8_downloader.dart';
|
import 'package:mangayomi/services/download_manager/m3u8/m3u8_downloader.dart';
|
||||||
import 'package:mangayomi/services/download_manager/m3u8/models/download.dart';
|
import 'package:mangayomi/services/download_manager/m3u8/models/download.dart';
|
||||||
|
import 'package:mangayomi/utils/chapter_recognition.dart';
|
||||||
import 'package:mangayomi/utils/extensions/chapter.dart';
|
import 'package:mangayomi/utils/extensions/chapter.dart';
|
||||||
import 'package:mangayomi/utils/extensions/string_extensions.dart';
|
import 'package:mangayomi/utils/extensions/string_extensions.dart';
|
||||||
import 'package:mangayomi/utils/headers.dart';
|
import 'package:mangayomi/utils/headers.dart';
|
||||||
|
|
@ -106,12 +107,31 @@ Future<void> downloadChapter(
|
||||||
Future<void> processConvert() async {
|
Future<void> processConvert() async {
|
||||||
if (!ref.read(saveAsCBZArchiveStateProvider)) return;
|
if (!ref.read(saveAsCBZArchiveStateProvider)) return;
|
||||||
try {
|
try {
|
||||||
|
// Extract chapter number from name (e.g., "Chapter 5" → "5")
|
||||||
|
final chapterNumber = ChapterRecognition().parseChapterNumber(
|
||||||
|
chapter.manga.value!.name!,
|
||||||
|
chapter.name!,
|
||||||
|
);
|
||||||
|
|
||||||
|
final comicInfo = ComicInfoData(
|
||||||
|
title: chapter.name,
|
||||||
|
series: manga.name,
|
||||||
|
number: chapterNumber.toString(),
|
||||||
|
writer: manga.author,
|
||||||
|
penciller: manga.artist,
|
||||||
|
summary: manga.description,
|
||||||
|
genre: manga.genre?.join(', '),
|
||||||
|
translator: chapter.scanlator,
|
||||||
|
publishingStatusStr: manga.status.name,
|
||||||
|
);
|
||||||
|
|
||||||
await ref.read(
|
await ref.read(
|
||||||
convertToCBZProvider(
|
convertToCBZProvider(
|
||||||
chapterDirectory.path,
|
chapterDirectory.path,
|
||||||
mangaMainDirectory!.path,
|
mangaMainDirectory!.path,
|
||||||
chapter.name!,
|
chapter.name!,
|
||||||
pages.map((e) => e.fileName!).toList(),
|
pages.map((e) => e.fileName!).toList(),
|
||||||
|
comicInfo: comicInfo,
|
||||||
).future,
|
).future,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -136,7 +136,7 @@ final class DownloadChapterProvider
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$downloadChapterHash() => r'db235f856cf106c89d6124c361a51f2e312e9aa3';
|
String _$downloadChapterHash() => r'34ecaeac678ca578ce785b8e43d089e95cba89d0';
|
||||||
|
|
||||||
final class DownloadChapterFamily extends $Family
|
final class DownloadChapterFamily extends $Family
|
||||||
with
|
with
|
||||||
|
|
|
||||||
86
lib/modules/more/settings/security/app_lock_screen.dart
Normal file
86
lib/modules/more/settings/security/app_lock_screen.dart
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:local_auth/local_auth.dart';
|
||||||
|
import 'package:mangayomi/modules/more/settings/security/providers/security_state_provider.dart';
|
||||||
|
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||||
|
|
||||||
|
class AppLockScreen extends ConsumerStatefulWidget {
|
||||||
|
const AppLockScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<AppLockScreen> createState() => _AppLockScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AppLockScreenState extends ConsumerState<AppLockScreen> {
|
||||||
|
final LocalAuthentication _localAuth = LocalAuthentication();
|
||||||
|
bool _isAuthenticating = false;
|
||||||
|
late final l10n = context.l10n;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) => _authenticate());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _authenticate() async {
|
||||||
|
if (_isAuthenticating) return;
|
||||||
|
setState(() => _isAuthenticating = true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final didAuthenticate = await _localAuth.authenticate(
|
||||||
|
localizedReason: l10n.auth_unlock_msg,
|
||||||
|
biometricOnly: false,
|
||||||
|
persistAcrossBackgrounding: true,
|
||||||
|
);
|
||||||
|
if (didAuthenticate && mounted) {
|
||||||
|
ref.read(appUnlockedStateProvider.notifier).unlock();
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
} finally {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() => _isAuthenticating = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return PopScope(
|
||||||
|
canPop: false,
|
||||||
|
child: Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.lock_outline,
|
||||||
|
size: 80,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
Text(
|
||||||
|
l10n.app_locked,
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
l10n.auth_unlock_msg,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
FilledButton.icon(
|
||||||
|
onPressed: _isAuthenticating ? null : _authenticate,
|
||||||
|
icon: const Icon(Icons.fingerprint),
|
||||||
|
label: Text(
|
||||||
|
_isAuthenticating ? l10n.authenticating : l10n.unlock,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
import 'package:mangayomi/main.dart';
|
||||||
|
import 'package:mangayomi/models/settings.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
part 'security_state_provider.g.dart';
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
class AppLockEnabledState extends _$AppLockEnabledState {
|
||||||
|
@override
|
||||||
|
bool build() {
|
||||||
|
return isar.settings.getSync(227)!.appLockEnabled ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set(bool value) {
|
||||||
|
final settings = isar.settings.getSync(227)!;
|
||||||
|
state = value;
|
||||||
|
isar.writeTxnSync(
|
||||||
|
() => isar.settings.putSync(
|
||||||
|
settings
|
||||||
|
..appLockEnabled = value
|
||||||
|
..updatedAt = DateTime.now().millisecondsSinceEpoch,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tracks whether the app is currently unlocked.
|
||||||
|
/// Resets to false when app goes to background (if lock is enabled).
|
||||||
|
@riverpod
|
||||||
|
class AppUnlockedState extends _$AppUnlockedState {
|
||||||
|
@override
|
||||||
|
bool build() {
|
||||||
|
// If app lock is not enabled, always unlocked
|
||||||
|
final lockEnabled = isar.settings.getSync(227)!.appLockEnabled ?? false;
|
||||||
|
return !lockEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void unlock() => state = true;
|
||||||
|
|
||||||
|
void lock() => state = false;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,125 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'security_state_provider.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint, type=warning
|
||||||
|
|
||||||
|
@ProviderFor(AppLockEnabledState)
|
||||||
|
final appLockEnabledStateProvider = AppLockEnabledStateProvider._();
|
||||||
|
|
||||||
|
final class AppLockEnabledStateProvider
|
||||||
|
extends $NotifierProvider<AppLockEnabledState, bool> {
|
||||||
|
AppLockEnabledStateProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'appLockEnabledStateProvider',
|
||||||
|
isAutoDispose: true,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$appLockEnabledStateHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
AppLockEnabledState create() => AppLockEnabledState();
|
||||||
|
|
||||||
|
/// {@macro riverpod.override_with_value}
|
||||||
|
Override overrideWithValue(bool value) {
|
||||||
|
return $ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
providerOverride: $SyncValueProvider<bool>(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$appLockEnabledStateHash() =>
|
||||||
|
r'cdd466aee9037e776f5adf992e11ccedb8c58e74';
|
||||||
|
|
||||||
|
abstract class _$AppLockEnabledState extends $Notifier<bool> {
|
||||||
|
bool build();
|
||||||
|
@$mustCallSuper
|
||||||
|
@override
|
||||||
|
void runBuild() {
|
||||||
|
final ref = this.ref as $Ref<bool, bool>;
|
||||||
|
final element =
|
||||||
|
ref.element
|
||||||
|
as $ClassProviderElement<
|
||||||
|
AnyNotifier<bool, bool>,
|
||||||
|
bool,
|
||||||
|
Object?,
|
||||||
|
Object?
|
||||||
|
>;
|
||||||
|
element.handleCreate(ref, build);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tracks whether the app is currently unlocked.
|
||||||
|
/// Resets to false when app goes to background (if lock is enabled).
|
||||||
|
|
||||||
|
@ProviderFor(AppUnlockedState)
|
||||||
|
final appUnlockedStateProvider = AppUnlockedStateProvider._();
|
||||||
|
|
||||||
|
/// Tracks whether the app is currently unlocked.
|
||||||
|
/// Resets to false when app goes to background (if lock is enabled).
|
||||||
|
final class AppUnlockedStateProvider
|
||||||
|
extends $NotifierProvider<AppUnlockedState, bool> {
|
||||||
|
/// Tracks whether the app is currently unlocked.
|
||||||
|
/// Resets to false when app goes to background (if lock is enabled).
|
||||||
|
AppUnlockedStateProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'appUnlockedStateProvider',
|
||||||
|
isAutoDispose: true,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$appUnlockedStateHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
AppUnlockedState create() => AppUnlockedState();
|
||||||
|
|
||||||
|
/// {@macro riverpod.override_with_value}
|
||||||
|
Override overrideWithValue(bool value) {
|
||||||
|
return $ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
providerOverride: $SyncValueProvider<bool>(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$appUnlockedStateHash() => r'e5dd0982d0fc0b51cb3db8e6be04d11490d46b9b';
|
||||||
|
|
||||||
|
/// Tracks whether the app is currently unlocked.
|
||||||
|
/// Resets to false when app goes to background (if lock is enabled).
|
||||||
|
|
||||||
|
abstract class _$AppUnlockedState extends $Notifier<bool> {
|
||||||
|
bool build();
|
||||||
|
@$mustCallSuper
|
||||||
|
@override
|
||||||
|
void runBuild() {
|
||||||
|
final ref = this.ref as $Ref<bool, bool>;
|
||||||
|
final element =
|
||||||
|
ref.element
|
||||||
|
as $ClassProviderElement<
|
||||||
|
AnyNotifier<bool, bool>,
|
||||||
|
bool,
|
||||||
|
Object?,
|
||||||
|
Object?
|
||||||
|
>;
|
||||||
|
element.handleCreate(ref, build);
|
||||||
|
}
|
||||||
|
}
|
||||||
113
lib/modules/more/settings/security/security_screen.dart
Normal file
113
lib/modules/more/settings/security/security_screen.dart
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:local_auth/local_auth.dart';
|
||||||
|
import 'package:mangayomi/modules/more/settings/security/providers/security_state_provider.dart';
|
||||||
|
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||||
|
|
||||||
|
class SecurityScreen extends ConsumerStatefulWidget {
|
||||||
|
const SecurityScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<SecurityScreen> createState() => _SecurityScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SecurityScreenState extends ConsumerState<SecurityScreen> {
|
||||||
|
final LocalAuthentication _localAuth = LocalAuthentication();
|
||||||
|
bool _canCheckBiometrics = false;
|
||||||
|
late final l10n = context.l10n;
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_checkBiometricAvailability();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _checkBiometricAvailability() async {
|
||||||
|
try {
|
||||||
|
final canAuth = await _localAuth.canCheckBiometrics;
|
||||||
|
final isDeviceSupported = await _localAuth.isDeviceSupported();
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_canCheckBiometrics = canAuth || isDeviceSupported;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() => _canCheckBiometrics = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> _authenticate() async {
|
||||||
|
try {
|
||||||
|
return await _localAuth.authenticate(
|
||||||
|
localizedReason: l10n.auth_to_change_security_setting,
|
||||||
|
biometricOnly: false,
|
||||||
|
persistAcrossBackgrounding: true,
|
||||||
|
);
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final appLockEnabled = ref.watch(appLockEnabledStateProvider);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: Text(l10n.security)),
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SwitchListTile(
|
||||||
|
value: appLockEnabled,
|
||||||
|
title: Text(l10n.app_lock),
|
||||||
|
subtitle: Text(
|
||||||
|
_canCheckBiometrics
|
||||||
|
? l10n.require_biometric_or_device_credential
|
||||||
|
: l10n.biometric_or_device_credential_not_available,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onChanged: _canCheckBiometrics
|
||||||
|
? (value) async {
|
||||||
|
if (value) {
|
||||||
|
final authenticated = await _authenticate();
|
||||||
|
if (authenticated) {
|
||||||
|
ref
|
||||||
|
.read(appLockEnabledStateProvider.notifier)
|
||||||
|
.set(true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final authenticated = await _authenticate();
|
||||||
|
if (authenticated) {
|
||||||
|
ref
|
||||||
|
.read(appLockEnabledStateProvider.notifier)
|
||||||
|
.set(false);
|
||||||
|
ref.read(appUnlockedStateProvider.notifier).unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
|
||||||
|
const Divider(),
|
||||||
|
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Text(
|
||||||
|
l10n.app_lock_description,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:mangayomi/modules/more/widgets/list_tile_widget.dart';
|
import 'package:mangayomi/modules/more/widgets/list_tile_widget.dart';
|
||||||
|
|
@ -54,6 +56,12 @@ class SettingsScreen extends StatelessWidget {
|
||||||
icon: Icons.explore_rounded,
|
icon: Icons.explore_rounded,
|
||||||
onTap: () => context.push('/browseS'),
|
onTap: () => context.push('/browseS'),
|
||||||
),
|
),
|
||||||
|
if (!Platform.isLinux)
|
||||||
|
ListTileWidget(
|
||||||
|
title: l10n.security,
|
||||||
|
icon: Icons.security_rounded,
|
||||||
|
onTap: () => context.push('/security'),
|
||||||
|
),
|
||||||
ListTileWidget(
|
ListTileWidget(
|
||||||
title: l10n.about,
|
title: l10n.about,
|
||||||
icon: Icons.info_outline,
|
icon: Icons.info_outline,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:bot_toast/bot_toast.dart';
|
import 'package:bot_toast/bot_toast.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:mangayomi/models/manga.dart';
|
import 'package:mangayomi/models/manga.dart';
|
||||||
import 'package:mangayomi/models/settings.dart';
|
import 'package:mangayomi/models/settings.dart';
|
||||||
|
|
@ -56,6 +57,7 @@ import 'package:mangayomi/modules/more/settings/browse/browse_screen.dart';
|
||||||
import 'package:mangayomi/modules/more/settings/general/general_screen.dart';
|
import 'package:mangayomi/modules/more/settings/general/general_screen.dart';
|
||||||
import 'package:mangayomi/modules/more/settings/reader/reader_screen.dart';
|
import 'package:mangayomi/modules/more/settings/reader/reader_screen.dart';
|
||||||
import 'package:mangayomi/modules/more/settings/settings_screen.dart';
|
import 'package:mangayomi/modules/more/settings/settings_screen.dart';
|
||||||
|
import 'package:mangayomi/modules/more/settings/security/security_screen.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
part 'router.g.dart';
|
part 'router.g.dart';
|
||||||
|
|
@ -86,6 +88,7 @@ class RouterCurrentLocationState extends _$RouterCurrentLocationState {
|
||||||
bool _didSubscribe = false;
|
bool _didSubscribe = false;
|
||||||
@override
|
@override
|
||||||
String? build() {
|
String? build() {
|
||||||
|
ref.keepAlive();
|
||||||
// Delay listener‐registration until after the first frame.
|
// Delay listener‐registration until after the first frame.
|
||||||
if (!_didSubscribe) {
|
if (!_didSubscribe) {
|
||||||
_didSubscribe = true;
|
_didSubscribe = true;
|
||||||
|
|
@ -207,6 +210,7 @@ class RouterNotifier extends ChangeNotifier {
|
||||||
),
|
),
|
||||||
_genericRoute(name: "downloads", child: const DownloadsScreen()),
|
_genericRoute(name: "downloads", child: const DownloadsScreen()),
|
||||||
_genericRoute(name: "dataAndStorage", child: const DataAndStorage()),
|
_genericRoute(name: "dataAndStorage", child: const DataAndStorage()),
|
||||||
|
_genericRoute(name: "security", child: const SecurityScreen()),
|
||||||
_genericRoute(name: "manageTrackers", child: const ManageTrackersScreen()),
|
_genericRoute(name: "manageTrackers", child: const ManageTrackersScreen()),
|
||||||
_genericRoute<TrackPreference>(
|
_genericRoute<TrackPreference>(
|
||||||
name: "trackingDetail",
|
name: "trackingDetail",
|
||||||
|
|
@ -280,36 +284,24 @@ class RouterNotifier extends ChangeNotifier {
|
||||||
return child!;
|
return child!;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
pageBuilder: (context, state) {
|
pageBuilder: (Platform.isIOS || Platform.isMacOS)
|
||||||
final pageChild = builder != null ? builder(state.extra as T) : child!;
|
? (context, state) {
|
||||||
return transitionPage(key: state.pageKey, child: pageChild);
|
final pageChild = builder != null
|
||||||
},
|
? builder(state.extra as T)
|
||||||
|
: child!;
|
||||||
|
return transitionPage(key: state.pageKey, child: pageChild);
|
||||||
|
}
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Page transitionPage({required LocalKey key, required child}) {
|
Page transitionPage({required LocalKey key, required child}) {
|
||||||
return Platform.isIOS
|
return CupertinoPage(key: key, child: child);
|
||||||
? CupertinoPage(key: key, child: child)
|
|
||||||
: CustomTransition(child: child, key: key);
|
|
||||||
}
|
|
||||||
|
|
||||||
class CustomTransition extends CustomTransitionPage {
|
|
||||||
CustomTransition({required LocalKey super.key, required super.child})
|
|
||||||
: super(
|
|
||||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
|
||||||
return FadeTransition(opacity: animation, child: child);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Route createRoute({required Widget page}) {
|
Route createRoute({required Widget page}) {
|
||||||
return Platform.isIOS
|
return (Platform.isIOS || Platform.isMacOS)
|
||||||
? CupertinoPageRoute(builder: (context) => page)
|
? CupertinoPageRoute(builder: (context) => page)
|
||||||
: PageRouteBuilder(
|
: MaterialPageRoute(builder: (context) => page);
|
||||||
pageBuilder: (context, animation, secondaryAnimation) => page,
|
|
||||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
|
||||||
return FadeTransition(opacity: animation, child: child);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import flutter_inappwebview_macos
|
||||||
import flutter_qjs
|
import flutter_qjs
|
||||||
import flutter_web_auth_2
|
import flutter_web_auth_2
|
||||||
import isar_community_flutter_libs
|
import isar_community_flutter_libs
|
||||||
|
import local_auth_darwin
|
||||||
import m_extension_server
|
import m_extension_server
|
||||||
import media_kit_libs_macos_video
|
import media_kit_libs_macos_video
|
||||||
import media_kit_video
|
import media_kit_video
|
||||||
|
|
@ -35,6 +36,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
FlutterQjsPlugin.register(with: registry.registrar(forPlugin: "FlutterQjsPlugin"))
|
FlutterQjsPlugin.register(with: registry.registrar(forPlugin: "FlutterQjsPlugin"))
|
||||||
FlutterWebAuth2Plugin.register(with: registry.registrar(forPlugin: "FlutterWebAuth2Plugin"))
|
FlutterWebAuth2Plugin.register(with: registry.registrar(forPlugin: "FlutterWebAuth2Plugin"))
|
||||||
IsarFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "IsarFlutterLibsPlugin"))
|
IsarFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "IsarFlutterLibsPlugin"))
|
||||||
|
LocalAuthPlugin.register(with: registry.registrar(forPlugin: "LocalAuthPlugin"))
|
||||||
MExtensionServerPlugin.register(with: registry.registrar(forPlugin: "MExtensionServerPlugin"))
|
MExtensionServerPlugin.register(with: registry.registrar(forPlugin: "MExtensionServerPlugin"))
|
||||||
MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin"))
|
MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin"))
|
||||||
MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin"))
|
MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin"))
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,9 @@ PODS:
|
||||||
- FlutterMacOS (1.0.0)
|
- FlutterMacOS (1.0.0)
|
||||||
- isar_community_flutter_libs (1.0.0):
|
- isar_community_flutter_libs (1.0.0):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
- local_auth_darwin (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
- m_extension_server (0.0.1):
|
- m_extension_server (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- media_kit_libs_macos_video (1.0.4):
|
- media_kit_libs_macos_video (1.0.4):
|
||||||
|
|
@ -58,6 +61,7 @@ DEPENDENCIES:
|
||||||
- flutter_web_auth_2 (from `Flutter/ephemeral/.symlinks/plugins/flutter_web_auth_2/macos`)
|
- flutter_web_auth_2 (from `Flutter/ephemeral/.symlinks/plugins/flutter_web_auth_2/macos`)
|
||||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||||
- isar_community_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/isar_community_flutter_libs/macos`)
|
- isar_community_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/isar_community_flutter_libs/macos`)
|
||||||
|
- local_auth_darwin (from `Flutter/ephemeral/.symlinks/plugins/local_auth_darwin/darwin`)
|
||||||
- m_extension_server (from `Flutter/ephemeral/.symlinks/plugins/m_extension_server/macos`)
|
- m_extension_server (from `Flutter/ephemeral/.symlinks/plugins/m_extension_server/macos`)
|
||||||
- media_kit_libs_macos_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos`)
|
- media_kit_libs_macos_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos`)
|
||||||
- media_kit_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos`)
|
- media_kit_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos`)
|
||||||
|
|
@ -97,6 +101,8 @@ EXTERNAL SOURCES:
|
||||||
:path: Flutter/ephemeral
|
:path: Flutter/ephemeral
|
||||||
isar_community_flutter_libs:
|
isar_community_flutter_libs:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/isar_community_flutter_libs/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/isar_community_flutter_libs/macos
|
||||||
|
local_auth_darwin:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/local_auth_darwin/darwin
|
||||||
m_extension_server:
|
m_extension_server:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/m_extension_server/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/m_extension_server/macos
|
||||||
media_kit_libs_macos_video:
|
media_kit_libs_macos_video:
|
||||||
|
|
@ -135,6 +141,7 @@ SPEC CHECKSUMS:
|
||||||
flutter_web_auth_2: 62b08da29f15a20fa63f144234622a1488d45b65
|
flutter_web_auth_2: 62b08da29f15a20fa63f144234622a1488d45b65
|
||||||
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
||||||
isar_community_flutter_libs: a631ceb5622413b56bcd0a8bf49cb55bf3d8bb2b
|
isar_community_flutter_libs: a631ceb5622413b56bcd0a8bf49cb55bf3d8bb2b
|
||||||
|
local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb
|
||||||
m_extension_server: 50e95a61bbf93c9a33ddc812d0753bddf1c01456
|
m_extension_server: 50e95a61bbf93c9a33ddc812d0753bddf1c01456
|
||||||
media_kit_libs_macos_video: 85a23e549b5f480e72cae3e5634b5514bc692f65
|
media_kit_libs_macos_video: 85a23e549b5f480e72cae3e5634b5514bc692f65
|
||||||
media_kit_video: fa6564e3799a0a28bff39442334817088b7ca758
|
media_kit_video: fa6564e3799a0a28bff39442334817088b7ca758
|
||||||
|
|
|
||||||
40
pubspec.lock
40
pubspec.lock
|
|
@ -946,6 +946,46 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.2"
|
version: "1.0.2"
|
||||||
|
local_auth:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: local_auth
|
||||||
|
sha256: ae6f382f638108c6becd134318d7c3f0a93875383a54010f61d7c97ac05d5137
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.1"
|
||||||
|
local_auth_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: local_auth_android
|
||||||
|
sha256: dc9663a7bc8ac33d7d988e63901974f63d527ebef260eabd19c479447cc9c911
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.5"
|
||||||
|
local_auth_darwin:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: local_auth_darwin
|
||||||
|
sha256: a8c3d4e17454111f7fd31ff72a31222359f6059f7fe956c2dcfe0f88f49826d4
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.3"
|
||||||
|
local_auth_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: local_auth_platform_interface
|
||||||
|
sha256: f98b8e388588583d3f781f6806e4f4c9f9e189d898d27f0c249b93a1973dd122
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
|
local_auth_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: local_auth_windows
|
||||||
|
sha256: be12c5b8ba5e64896983123655c5f67d2484ecfcc95e367952ad6e3bff94cb16
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.1"
|
||||||
logging:
|
logging:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,7 @@ dependencies:
|
||||||
url: https://github.com/Schnitzel5/flutter-discord-rpc.git
|
url: https://github.com/Schnitzel5/flutter-discord-rpc.git
|
||||||
ref: main
|
ref: main
|
||||||
table_calendar: ^3.2.0
|
table_calendar: ^3.2.0
|
||||||
|
local_auth: ^3.0.1
|
||||||
m_extension_server:
|
m_extension_server:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/kodjodevf/m_extension_server.git
|
url: https://github.com/kodjodevf/m_extension_server.git
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
#include <flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h>
|
#include <flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h>
|
||||||
#include <flutter_qjs/flutter_qjs_plugin.h>
|
#include <flutter_qjs/flutter_qjs_plugin.h>
|
||||||
#include <isar_community_flutter_libs/isar_flutter_libs_plugin.h>
|
#include <isar_community_flutter_libs/isar_flutter_libs_plugin.h>
|
||||||
|
#include <local_auth_windows/local_auth_plugin.h>
|
||||||
#include <m_extension_server/m_extension_server_plugin_c_api.h>
|
#include <m_extension_server/m_extension_server_plugin_c_api.h>
|
||||||
#include <media_kit_libs_windows_video/media_kit_libs_windows_video_plugin_c_api.h>
|
#include <media_kit_libs_windows_video/media_kit_libs_windows_video_plugin_c_api.h>
|
||||||
#include <media_kit_video/media_kit_video_plugin_c_api.h>
|
#include <media_kit_video/media_kit_video_plugin_c_api.h>
|
||||||
|
|
@ -34,6 +35,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
registry->GetRegistrarForPlugin("FlutterQjsPlugin"));
|
registry->GetRegistrarForPlugin("FlutterQjsPlugin"));
|
||||||
IsarFlutterLibsPluginRegisterWithRegistrar(
|
IsarFlutterLibsPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("IsarFlutterLibsPlugin"));
|
registry->GetRegistrarForPlugin("IsarFlutterLibsPlugin"));
|
||||||
|
LocalAuthPluginRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("LocalAuthPlugin"));
|
||||||
MExtensionServerPluginCApiRegisterWithRegistrar(
|
MExtensionServerPluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("MExtensionServerPluginCApi"));
|
registry->GetRegistrarForPlugin("MExtensionServerPluginCApi"));
|
||||||
MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar(
|
MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar(
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
flutter_inappwebview_windows
|
flutter_inappwebview_windows
|
||||||
flutter_qjs
|
flutter_qjs
|
||||||
isar_community_flutter_libs
|
isar_community_flutter_libs
|
||||||
|
local_auth_windows
|
||||||
m_extension_server
|
m_extension_server
|
||||||
media_kit_libs_windows_video
|
media_kit_libs_windows_video
|
||||||
media_kit_video
|
media_kit_video
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue