M .metadata => .metadata +22 -2
@@ 4,7 4,27 @@
# This file should be version controlled and should not be manually edited.
version:
- revision: 78910062997c3a836feee883712c241a5fd22983
- channel: stable
+ revision: "d693b4b9dbac2acd4477aea4555ca6dcbea44ba2"
+ channel: "stable"
project_type: app
+
+# Tracks metadata for the flutter migrate command
+migration:
+ platforms:
+ - platform: root
+ create_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2
+ base_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2
+ - platform: android
+ create_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2
+ base_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2
+
+ # User provided section
+
+ # List of Local paths (relative to this file) that should be
+ # ignored by the migrate tool.
+ #
+ # Files that are not part of the templates will be ignored by default.
+ unmanaged_files:
+ - 'lib/main.dart'
+ - 'ios/Runner.xcodeproj/project.pbxproj'
A analysis_options.yaml => analysis_options.yaml +28 -0
@@ 0,0 1,28 @@
+# This file configures the analyzer, which statically analyzes Dart code to
+# check for errors, warnings, and lints.
+#
+# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
+# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
+# invoked from the command line by running `flutter analyze`.
+
+# The following line activates a set of recommended lints for Flutter apps,
+# packages, and plugins designed to encourage good coding practices.
+include: package:flutter_lints/flutter.yaml
+
+linter:
+ # The lint rules applied to this project can be customized in the
+ # section below to disable rules from the `package:flutter_lints/flutter.yaml`
+ # included above or to enable additional rules. A list of all available lints
+ # and their documentation is published at https://dart.dev/lints.
+ #
+ # Instead of disabling a lint rule for the entire project in the
+ # section below, it can also be suppressed for a single line of code
+ # or a specific dart file by using the `// ignore: name_of_lint` and
+ # `// ignore_for_file: name_of_lint` syntax on the line or in the file
+ # producing the lint.
+ rules:
+ # avoid_print: false # Uncomment to disable the `avoid_print` rule
+ # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
A android-old/.gitignore => android-old/.gitignore +11 -0
@@ 0,0 1,11 @@
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
+key.properties
A android-old/app/build.gradle => android-old/app/build.gradle +82 -0
@@ 0,0 1,82 @@
+def localProperties = new Properties()
+def localPropertiesFile = rootProject.file('local.properties')
+if (localPropertiesFile.exists()) {
+ localPropertiesFile.withReader('UTF-8') { reader ->
+ localProperties.load(reader)
+ }
+}
+
+def flutterRoot = localProperties.getProperty('flutter.sdk')
+if (flutterRoot == null) {
+ throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
+}
+
+def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
+if (flutterVersionCode == null) {
+ flutterVersionCode = '1'
+}
+
+def flutterVersionName = localProperties.getProperty('flutter.versionName')
+if (flutterVersionName == null) {
+ flutterVersionName = '1.0'
+}
+
+apply plugin: 'kotlin-android'
+apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
+
+def keystoreProperties = new Properties()
+def keystorePropertiesFile = rootProject.file('key.properties')
+if (keystorePropertiesFile.exists()) {
+ keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
+}
+
+plugins {
+ id "com.android.application"
+ id "dev.flutter.flutter-plugin-loader"
+}
+
+android {
+ compileSdkVersion 33
+
+ sourceSets {
+ main.java.srcDirs += 'src/main/kotlin'
+ }
+
+ lintOptions {
+ disable 'InvalidPackage'
+ }
+
+ defaultConfig {
+ // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+ applicationId "me.hyliu.fluent_reader_lite"
+ minSdkVersion 24
+ targetSdkVersion 33
+ versionCode flutterVersionCode.toInteger()
+ versionName flutterVersionName
+ }
+
+ signingConfigs {
+ release {
+ keyAlias keystoreProperties['keyAlias']
+ keyPassword keystoreProperties['keyPassword']
+ storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
+ storePassword keystoreProperties['storePassword']
+ }
+ }
+
+ buildTypes {
+ release {
+ signingConfig signingConfigs.release
+ shrinkResources false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+flutter {
+ source '../..'
+}
+
+dependencies {
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+}
A android-old/app/proguard-rules.pro => android-old/app/proguard-rules.pro +1 -0
@@ 0,0 1,1 @@
+-keep class io.flutter.plugin.editing.** { *; }<
\ No newline at end of file
A android-old/app/src/debug/AndroidManifest.xml => android-old/app/src/debug/AndroidManifest.xml +7 -0
@@ 0,0 1,7 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="me.hyliu.fluent_reader_lite">
+ <!-- Flutter needs it to communicate with the running application
+ to allow setting breakpoints, to provide hot reload, etc.
+ -->
+ <uses-permission android:name="android.permission.INTERNET"/>
+</manifest>
A android-old/app/src/main/AndroidManifest.xml => android-old/app/src/main/AndroidManifest.xml +62 -0
@@ 0,0 1,62 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="me.hyliu.fluent_reader_lite">
+ <!-- io.flutter.app.FlutterApplication is an android.app.Application that
+ calls FlutterMain.startInitialization(this); in its onCreate method.
+ In most cases you can leave this as-is, but you if you want to provide
+ additional functionality it is fine to subclass or reimplement
+ FlutterApplication and put your custom class here. -->
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+ <application
+ android:name="${applicationName}"
+ android:label="Fluent Reader"
+ android:icon="@mipmap/ic_launcher"
+ android:usesCleartextTraffic="true">
+ <activity
+ android:name=".MainActivity"
+ android:launchMode="singleTop"
+ android:theme="@style/LaunchTheme"
+ android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
+ android:hardwareAccelerated="true"
+ android:windowSoftInputMode="adjustResize"
+ android:exported="true">
+ <!-- Specifies an Android theme to apply to this Activity as soon as
+ the Android process has started. This theme is visible to the user
+ while the Flutter UI initializes. After that, this theme continues
+ to determine the Window background behind the Flutter UI. -->
+ <meta-data
+ android:name="io.flutter.embedding.android.NormalTheme"
+ android:resource="@style/NormalTheme"
+ />
+ <!-- Displays an Android View that continues showing the launch screen
+ Drawable until Flutter paints its first frame, then this splash
+ screen fades out. A splash screen is useful to avoid any visual
+ gap between the end of Android's launch screen and the painting of
+ Flutter's first frame. -->
+ <meta-data
+ android:name="io.flutter.embedding.android.SplashScreenDrawable"
+ android:resource="@drawable/launch_background"
+ />
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <!-- Accepts URIs that begin with YOUR_SCHEME://YOUR_HOST -->
+ <data
+ android:scheme="fluent-reader"
+ android:host="import" />
+ </intent-filter>
+ </activity>
+ <!-- Don't delete the meta-data below.
+ This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
+ <meta-data
+ android:name="flutterEmbedding"
+ android:value="2" />
+ </application>
+</manifest>
A android-old/app/src/main/ic_launcher-playstore.png => android-old/app/src/main/ic_launcher-playstore.png +0 -0
A android-old/app/src/main/kotlin/com/example/fluent_reader_lite/MainActivity.kt => android-old/app/src/main/kotlin/com/example/fluent_reader_lite/MainActivity.kt +6 -0
@@ 0,0 1,6 @@
+package me.hyliu.fluent_reader_lite
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity() {
+}
A android-old/app/src/main/res/drawable-night/launch_background.xml => android-old/app/src/main/res/drawable-night/launch_background.xml +11 -0
@@ 0,0 1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@android:color/black" />
+
+ <item>
+ <bitmap
+ android:gravity="center"
+ android:src="@mipmap/logo" />
+ </item>
+</layer-list>
A android-old/app/src/main/res/drawable/launch_background.xml => android-old/app/src/main/res/drawable/launch_background.xml +11 -0
@@ 0,0 1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@android:color/white" />
+
+ <item>
+ <bitmap
+ android:gravity="center"
+ android:src="@mipmap/logo" />
+ </item>
+</layer-list>
A android-old/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml => android-old/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +5 -0
@@ 0,0 1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@color/ic_launcher_background"/>
+ <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
+</adaptive-icon><
\ No newline at end of file
A android-old/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml => android-old/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +5 -0
@@ 0,0 1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@color/ic_launcher_background"/>
+ <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
+</adaptive-icon><
\ No newline at end of file
A android-old/app/src/main/res/mipmap-hdpi/ic_launcher.png => android-old/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
A android-old/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png => android-old/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png +0 -0
A android-old/app/src/main/res/mipmap-hdpi/ic_launcher_round.png => android-old/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
A android-old/app/src/main/res/mipmap-mdpi/ic_launcher.png => android-old/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
A android-old/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png => android-old/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png +0 -0
A android-old/app/src/main/res/mipmap-mdpi/ic_launcher_round.png => android-old/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
A android-old/app/src/main/res/mipmap-xhdpi/ic_launcher.png => android-old/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
A android-old/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png => android-old/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png +0 -0
A android-old/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png => android-old/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
A android-old/app/src/main/res/mipmap-xxhdpi/ic_launcher.png => android-old/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
A android-old/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png => android-old/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png +0 -0
A android-old/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png => android-old/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
A android-old/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png => android-old/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
A android-old/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png => android-old/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png +0 -0
A android-old/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png => android-old/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
A android-old/app/src/main/res/mipmap-xxxhdpi/logo.png => android-old/app/src/main/res/mipmap-xxxhdpi/logo.png +0 -0
A android-old/app/src/main/res/values/ic_launcher_background.xml => android-old/app/src/main/res/values/ic_launcher_background.xml +4 -0
@@ 0,0 1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <color name="ic_launcher_background">#FFFFFF</color>
+</resources><
\ No newline at end of file
A android-old/app/src/main/res/values/styles.xml => android-old/app/src/main/res/values/styles.xml +18 -0
@@ 0,0 1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Theme applied to the Android Window while the process is starting -->
+ <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
+ <!-- Show a splash screen on the activity. Automatically removed when
+ Flutter draws its first frame -->
+ <item name="android:windowBackground">@drawable/launch_background</item>
+ </style>
+ <!-- Theme applied to the Android Window as soon as the process has started.
+ This theme determines the color of the Android Window while your
+ Flutter UI initializes, as well as behind your Flutter UI while its
+ running.
+
+ This Theme is only used starting with V2 of Flutter's Android embedding. -->
+ <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
+ <item name="android:windowBackground">@android:color/white</item>
+ </style>
+</resources>
A android-old/app/src/profile/AndroidManifest.xml => android-old/app/src/profile/AndroidManifest.xml +7 -0
@@ 0,0 1,7 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="me.hyliu.fluent_reader_lite">
+ <!-- Flutter needs it to communicate with the running application
+ to allow setting breakpoints, to provide hot reload, etc.
+ -->
+ <uses-permission android:name="android.permission.INTERNET"/>
+</manifest>
A android-old/build.gradle => android-old/build.gradle +35 -0
@@ 0,0 1,35 @@
+plugins {
+ id "dev.flutter.flutter-gradle-plugin"
+}
+
+buildscript {
+ ext.kotlin_version = '1.7.0'
+ repositories {
+ google()
+ jcenter()
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:7.0.2'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+ }
+}
+
+rootProject.buildDir = '../build'
+subprojects {
+ project.buildDir = "${rootProject.buildDir}/${project.name}"
+}
+subprojects {
+ project.evaluationDependsOn(':app')
+}
+
+tasks.register("clean", Delete) {
+ delete rootProject.layout.buildDirectory
+}
A android-old/gradle.properties => android-old/gradle.properties +4 -0
@@ 0,0 1,4 @@
+org.gradle.jvmargs=-Xmx1536M
+android.useAndroidX=true
+android.enableJetifier=true
+android.enableR8=true
A android-old/gradle/wrapper/gradle-wrapper.properties => android-old/gradle/wrapper/gradle-wrapper.properties +5 -0
@@ 0,0 1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
A android-old/settings.gradle => android-old/settings.gradle +11 -0
@@ 0,0 1,11 @@
+include ':app'
+
+def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
+def properties = new Properties()
+
+assert localPropertiesFile.exists()
+localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
+
+def flutterSdkPath = properties.getProperty("flutter.sdk")
+assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
M android/.gitignore => android/.gitignore +4 -1
@@ 5,7 5,10 @@ gradle-wrapper.jar
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
+.cxx/
# Remember to never publicly share your keystore.
-# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
+# See https://flutter.dev/to/reference-keystore
key.properties
+**/*.keystore
+**/*.jks
M android/app/build.gradle => android/app/build.gradle +9 -9
@@ 1,3 1,7 @@
+plugins {
+ id "com.android.application"
+}
+
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
@@ 21,10 25,6 @@ if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
-apply plugin: 'com.android.application'
-apply plugin: 'kotlin-android'
-apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
-
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
@@ 32,21 32,21 @@ if (keystorePropertiesFile.exists()) {
}
android {
- compileSdkVersion 33
+ compileSdk 33
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
- lintOptions {
+ lint {
disable 'InvalidPackage'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "me.hyliu.fluent_reader_lite"
- minSdkVersion 24
- targetSdkVersion 33
+ minSdk 24
+ targetSdk 33
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
@@ 75,4 75,4 @@ flutter {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
-}
+}<
\ No newline at end of file
M android/app/src/debug/AndroidManifest.xml => android/app/src/debug/AndroidManifest.xml +3 -3
@@ 1,6 1,6 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="me.hyliu.fluent_reader_lite">
- <!-- Flutter needs it to communicate with the running application
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- The INTERNET permission is required for development. Specifically,
+ the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
M android/app/src/main/AndroidManifest.xml => android/app/src/main/AndroidManifest.xml +11 -5
@@ 1,10 1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="me.hyliu.fluent_reader_lite">
- <!-- io.flutter.app.FlutterApplication is an android.app.Application that
- calls FlutterMain.startInitialization(this); in its onCreate method.
- In most cases you can leave this as-is, but you if you want to provide
- additional functionality it is fine to subclass or reimplement
- FlutterApplication and put your custom class here. -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
@@ 59,4 54,15 @@
android:name="flutterEmbedding"
android:value="2" />
</application>
+ <!-- Required to query activities that can process text, see:
+ https://developer.android.com/training/package-visibility and
+ https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
+
+ In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
+ <queries>
+ <intent>
+ <action android:name="android.intent.action.PROCESS_TEXT"/>
+ <data android:mimeType="text/plain"/>
+ </intent>
+ </queries>
</manifest>
M android/app/src/main/kotlin/com/example/fluent_reader_lite/MainActivity.kt => android/app/src/main/kotlin/com/example/fluent_reader_lite/MainActivity.kt +2 -3
@@ 1,6 1,5 @@
-package me.hyliu.fluent_reader_lite
+package com.example.fluent_reader_lite
import io.flutter.embedding.android.FlutterActivity
-class MainActivity: FlutterActivity() {
-}
+class MainActivity : FlutterActivity()
A android/app/src/main/res/drawable-v21/launch_background.xml => android/app/src/main/res/drawable-v21/launch_background.xml +12 -0
@@ 0,0 1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="?android:colorBackground" />
+
+ <!-- You can insert your own image assets here -->
+ <!-- <item>
+ <bitmap
+ android:gravity="center"
+ android:src="@mipmap/launch_image" />
+ </item> -->
+</layer-list>
M android/app/src/main/res/drawable/launch_background.xml => android/app/src/main/res/drawable/launch_background.xml +4 -3
@@ 3,9 3,10 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
- <item>
+ <!-- You can insert your own image assets here -->
+ <!-- <item>
<bitmap
android:gravity="center"
- android:src="@mipmap/logo" />
- </item>
+ android:src="@mipmap/launch_image" />
+ </item> -->
</layer-list>
M android/app/src/main/res/mipmap-hdpi/ic_launcher.png => android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
M android/app/src/main/res/mipmap-mdpi/ic_launcher.png => android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
M android/app/src/main/res/mipmap-xhdpi/ic_launcher.png => android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
M android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png => android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
M android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png => android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
A android/app/src/main/res/values-night/styles.xml => android/app/src/main/res/values-night/styles.xml +18 -0
@@ 0,0 1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
+ <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
+ <!-- Show a splash screen on the activity. Automatically removed when
+ the Flutter engine draws its first frame -->
+ <item name="android:windowBackground">@drawable/launch_background</item>
+ </style>
+ <!-- Theme applied to the Android Window as soon as the process has started.
+ This theme determines the color of the Android Window while your
+ Flutter UI initializes, as well as behind your Flutter UI while its
+ running.
+
+ This Theme is only used starting with V2 of Flutter's Android embedding. -->
+ <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
+ <item name="android:windowBackground">?android:colorBackground</item>
+ </style>
+</resources>
M android/app/src/profile/AndroidManifest.xml => android/app/src/profile/AndroidManifest.xml +3 -3
@@ 1,6 1,6 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="me.hyliu.fluent_reader_lite">
- <!-- Flutter needs it to communicate with the running application
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- The INTERNET permission is required for development. Specifically,
+ the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
M android/build.gradle => android/build.gradle +7 -3
@@ 1,3 1,7 @@
+plugins {
+ id "dev.flutter.flutter-gradle-plugin"
+}
+
buildscript {
ext.kotlin_version = '1.7.0'
repositories {
@@ 26,6 30,6 @@ subprojects {
project.evaluationDependsOn(':app')
}
-task clean(type: Delete) {
- delete rootProject.buildDir
-}
+tasks.register("clean", Delete) {
+ delete rootProject.layout.buildDirectory
+}<
\ No newline at end of file
M android/gradle.properties => android/gradle.properties +1 -2
@@ 1,4 1,3 @@
-org.gradle.jvmargs=-Xmx1536M
+org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true
-android.enableR8=true
M android/gradle/wrapper/gradle-wrapper.properties => android/gradle/wrapper/gradle-wrapper.properties +1 -1
@@ 1,5 1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
M android/settings.gradle => android/settings.gradle +22 -8
@@ 1,11 1,25 @@
-include ':app'
+pluginManagement {
+ def flutterSdkPath = {
+ def properties = new Properties()
+ file("local.properties").withInputStream { properties.load(it) }
+ def flutterSdkPath = properties.getProperty("flutter.sdk")
+ assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+ return flutterSdkPath
+ }()
-def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
-def properties = new Properties()
+ includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
-assert localPropertiesFile.exists()
-localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
-def flutterSdkPath = properties.getProperty("flutter.sdk")
-assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
-apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
+plugins {
+ id "dev.flutter.flutter-plugin-loader" version "1.0.0"
+ id "com.android.application" version "8.9.1" apply false
+ id "org.jetbrains.kotlin.android" version "2.1.0" apply false
+}
+
+include ":app"<
\ No newline at end of file
M assets/article/article.html => assets/article/article.html +1 -1
@@ 5,7 5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="referrer" content="no-referrer">
<meta http-equiv="Content-Security-Policy"
- content="default-src 'none'; script-src 'self'; img-src http: https: data:; style-src 'self' 'unsafe-inline'; frame-src http: https:; media-src http: https:; connect-src https: http:">
+ content="default-src 'none'; script-src 'self'; img-src http: https: data:; style-src 'self' 'unsafe-inline'; font-src 'self' http://127.0.0.1:9000; frame-src http: https:; media-src http: https:; connect-src https: http:">
<title>Article</title>
<link rel="stylesheet" href="article.css" />
<script src="mercury.web.js"></script>
M assets/article/article.js => assets/article/article.js +21 -0
@@ 12,6 12,27 @@ async function getArticle(url) {
}
}
document.documentElement.style.fontSize = get("s") + "px"
+let fontFamily = get("f")
+if (fontFamily && fontFamily !== "System") {
+ let fontPath = get("fp")
+ if (!fontPath) {
+ // Built-in fonts served from bundled assets
+ var builtInFonts = {
+ 'OpenSans': 'fonts/OpenSans-Regular.ttf',
+ 'Roboto': 'fonts/Roboto-Regular.ttf',
+ 'SourceSerif': 'fonts/SourceSerif-Regular.ttf'
+ }
+ if (builtInFonts[fontFamily]) {
+ fontPath = builtInFonts[fontFamily]
+ }
+ }
+ if (fontPath) {
+ var style = document.createElement('style')
+ style.textContent = '@font-face { font-family: "' + fontFamily + '"; src: url("' + fontPath + '"); }'
+ document.head.appendChild(style)
+ }
+ document.documentElement.style.fontFamily = '"' + fontFamily + '", sans-serif'
+}
let theme = get("t")
if (theme !== null) document.documentElement.classList.add(theme === "1" ? "light" : "dark")
let url = get("u")
A ios/Flutter/ephemeral/flutter_lldb_helper.py => ios/Flutter/ephemeral/flutter_lldb_helper.py +32 -0
@@ 0,0 1,32 @@
+#
+# Generated file, do not edit.
+#
+
+import lldb
+
+def handle_new_rx_page(frame: lldb.SBFrame, bp_loc, extra_args, intern_dict):
+ """Intercept NOTIFY_DEBUGGER_ABOUT_RX_PAGES and touch the pages."""
+ base = frame.register["x0"].GetValueAsAddress()
+ page_len = frame.register["x1"].GetValueAsUnsigned()
+
+ # Note: NOTIFY_DEBUGGER_ABOUT_RX_PAGES will check contents of the
+ # first page to see if handled it correctly. This makes diagnosing
+ # misconfiguration (e.g. missing breakpoint) easier.
+ data = bytearray(page_len)
+ data[0:8] = b'IHELPED!'
+
+ error = lldb.SBError()
+ frame.GetThread().GetProcess().WriteMemory(base, data, error)
+ if not error.Success():
+ print(f'Failed to write into {base}[+{page_len}]', error)
+ return
+
+def __lldb_init_module(debugger: lldb.SBDebugger, _):
+ target = debugger.GetDummyTarget()
+ # Caveat: must use BreakpointCreateByRegEx here and not
+ # BreakpointCreateByName. For some reasons callback function does not
+ # get carried over from dummy target for the later.
+ bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$")
+ bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__))
+ bp.SetAutoContinue(True)
+ print("-- LLDB integration loaded --")
A ios/Flutter/ephemeral/flutter_lldbinit => ios/Flutter/ephemeral/flutter_lldbinit +5 -0
@@ 0,0 1,5 @@
+#
+# Generated file, do not edit.
+#
+
+command script import --relative-to-command-file flutter_lldb_helper.py
M lib/l10n/intl_de.arb => lib/l10n/intl_de.arb +10 -0
@@ 47,6 47,16 @@
"openMenu": "Menü offenen",
"openExternal": "Extern öffnen",
"fontSize": "Schriftgröße",
+ "fontFamily": "Schriftart",
+ "systemFont": "Systemschrift",
+ "uploadCustomFont": "Eigene Schrift hochladen",
+ "selectFontFamily": "Schriftart auswählen",
+ "fontUploaded": "Schrift hochgeladen",
+ "customFontUploaded": "Eigene Schrift wurde erfolgreich hochgeladen.",
+ "fontUploadError": "Fehler beim Hochladen der Schrift",
+ "fontSettings": "Schrift-Einstellungen",
+ "deleteFont": "Schrift löschen",
+ "deleteFontConfirm": "Sind Sie sicher, dass Sie diese Schrift löschen möchten?",
"edit": "Editieren",
"name": "Name",
"icon": "Icon",
M lib/l10n/intl_en.arb => lib/l10n/intl_en.arb +10 -0
@@ 47,6 47,16 @@
"openMenu": "Open menu",
"openExternal": "Open externally",
"fontSize": "Font size",
+ "fontFamily": "Font Family",
+ "systemFont": "System Font",
+ "uploadCustomFont": "Upload Custom Font",
+ "selectFontFamily": "Select Font Family",
+ "fontUploaded": "Font Uploaded",
+ "customFontUploaded": "Custom font has been uploaded successfully.",
+ "fontUploadError": "Failed to upload font",
+ "fontSettings": "Font Settings",
+ "deleteFont": "Delete Font",
+ "deleteFontConfirm": "Are you sure you want to delete this custom font?",
"edit": "Edit",
"name": "Name",
"icon": "Icon",
M lib/l10n/intl_es.arb => lib/l10n/intl_es.arb +10 -0
@@ 47,6 47,16 @@
"openMenu": "Abrir menú",
"openExternal": "Abrir externamente",
"fontSize": "Tamaño de fuente",
+ "fontFamily": "Familia de fuentes",
+ "systemFont": "Fuente del sistema",
+ "uploadCustomFont": "Subir fuente personalizada",
+ "selectFontFamily": "Seleccionar familia de fuentes",
+ "fontUploaded": "Fuente subida",
+ "customFontUploaded": "La fuente personalizada se ha subido correctamente.",
+ "fontUploadError": "Error al subir la fuente",
+ "fontSettings": "Configuración de fuentes",
+ "deleteFont": "Eliminar fuente",
+ "deleteFontConfirm": "¿Está seguro de que desea eliminar esta fuente personalizada?",
"edit": "Editar",
"name": "Nombre",
"icon": "Icono",
M lib/l10n/intl_fr.arb => lib/l10n/intl_fr.arb +10 -0
@@ 47,6 47,16 @@
"openMenu": "Ouvrir le menu",
"openExternal": "Ouvrir dans un nouvel onglet",
"fontSize": "Taille de police",
+ "fontFamily": "Police de caractères",
+ "systemFont": "Police système",
+ "uploadCustomFont": "Téléverser une police personnalisée",
+ "selectFontFamily": "Sélectionner la police",
+ "fontUploaded": "Police téléversée",
+ "customFontUploaded": "La police personnalisée a été téléversée avec succès.",
+ "fontUploadError": "Échec du téléversement de la police",
+ "fontSettings": "Paramètres de police",
+ "deleteFont": "Supprimer la police",
+ "deleteFontConfirm": "Êtes-vous sûr de vouloir supprimer cette police personnalisée ?",
"edit": "Editer",
"name": "Nom",
"icon": "Icon",
M lib/l10n/intl_hr.arb => lib/l10n/intl_hr.arb +10 -0
@@ 47,6 47,16 @@
"openMenu": "Otvori izbornik",
"openExternal": "Otvori u vanjskoj aplikaciji",
"fontSize": "Veličina fonta",
+ "fontFamily": "Obitelj fontova",
+ "systemFont": "Sistemski font",
+ "uploadCustomFont": "Učitaj prilagođeni font",
+ "selectFontFamily": "Odaberi obitelj fontova",
+ "fontUploaded": "Font učitan",
+ "customFontUploaded": "Prilagođeni font je uspješno učitan.",
+ "fontUploadError": "Pogreška pri učitavanju fonta",
+ "fontSettings": "Postavke fontova",
+ "deleteFont": "Izbriši font",
+ "deleteFontConfirm": "Jeste li sigurni da želite izbrisati ovaj prilagođeni font?",
"edit": "Uredi",
"name": "Naziv",
"icon": "Ikona",
M lib/l10n/intl_pt.arb => lib/l10n/intl_pt.arb +10 -0
@@ 47,6 47,16 @@
"openMenu": "Abrir menu",
"openExternal": "Abrir externamente",
"fontSize": "Tamanho da fonte",
+ "fontFamily": "Família da fonte",
+ "systemFont": "Fonte do sistema",
+ "uploadCustomFont": "Enviar fonte personalizada",
+ "selectFontFamily": "Selecionar família da fonte",
+ "fontUploaded": "Fonte enviada",
+ "customFontUploaded": "A fonte personalizada foi enviada com sucesso.",
+ "fontUploadError": "Falha ao enviar a fonte",
+ "fontSettings": "Configurações de fonte",
+ "deleteFont": "Excluir fonte",
+ "deleteFontConfirm": "Tem certeza de que deseja excluir esta fonte personalizada?",
"edit": "Editar",
"name": "Nome",
"icon": "ícone",
M lib/l10n/intl_tr.arb => lib/l10n/intl_tr.arb +10 -0
@@ 47,6 47,16 @@
"openMenu": "Menüyü aç",
"openExternal": "Harici olarak aç",
"fontSize": "Yazı boyutu",
+ "fontFamily": "Yazı tipi ailesi",
+ "systemFont": "Sistem yazı tipi",
+ "uploadCustomFont": "Özel yazı tipi yükle",
+ "selectFontFamily": "Yazı tipi ailesi seç",
+ "fontUploaded": "Yazı tipi yüklendi",
+ "customFontUploaded": "Özel yazı tipi başarıyla yüklendi.",
+ "fontUploadError": "Yazı tipi yüklenemedi",
+ "fontSettings": "Yazı tipi ayarları",
+ "deleteFont": "Yazı tipini sil",
+ "deleteFontConfirm": "Bu özel yazı tipini silmek istediğinizden emin misiniz?",
"edit": "Düzenle",
"name": "İsim",
"icon": "Simge",
M lib/l10n/intl_uk.arb => lib/l10n/intl_uk.arb +10 -0
@@ 47,6 47,16 @@
"openMenu": "Відкрити меню",
"openExternal": "Відкрити зовні",
"fontSize": "Розмір шрифту",
+ "fontFamily": "Сімейство шрифтів",
+ "systemFont": "Системний шрифт",
+ "uploadCustomFont": "Завантажити власний шрифт",
+ "selectFontFamily": "Обрати сімейство шрифтів",
+ "fontUploaded": "Шрифт завантажено",
+ "customFontUploaded": "Власний шрифт успішно завантажено.",
+ "fontUploadError": "Не вдалося завантажити шрифт",
+ "fontSettings": "Налаштування шрифтів",
+ "deleteFont": "Видалити шрифт",
+ "deleteFontConfirm": "Ви впевнені, що хочете видалити цей власний шрифт?",
"edit": "Редагувати",
"name": "Назва",
"icon": "Значок",
M lib/l10n/intl_zh.arb => lib/l10n/intl_zh.arb +10 -0
@@ 47,6 47,16 @@
"openMenu": "打开菜单",
"openExternal": "在外部打开",
"fontSize": "字体大小",
+ "fontFamily": "字体",
+ "systemFont": "系统字体",
+ "uploadCustomFont": "上传自定义字体",
+ "selectFontFamily": "选择字体",
+ "fontUploaded": "字体已上传",
+ "customFontUploaded": "自定义字体上传成功。",
+ "fontUploadError": "字体上传失败",
+ "fontSettings": "字体设置",
+ "deleteFont": "删除字体",
+ "deleteFontConfirm": "确定要删除此自定义字体吗?",
"edit": "编辑",
"name": "名称",
"icon": "图标",
M lib/models/global_model.dart => lib/models/global_model.dart +10 -0
@@ 14,6 14,7 @@ class GlobalModel with ChangeNotifier {
bool _syncOnStart = Store.sp.getBool(StoreKeys.SYNC_ON_START) ?? true;
bool _inAppBrowser = Store.sp.getBool(StoreKeys.IN_APP_BROWSER) ?? Platform.isIOS;
double _textScale = Store.sp.getDouble(StoreKeys.TEXT_SCALE);
+ String _fontFamily = Store.getFontFamily();
ThemeSetting get theme => _theme;
set theme(ThemeSetting value) {
@@ 67,4 68,13 @@ class GlobalModel with ChangeNotifier {
}
}
}
+
+ String get fontFamily => _fontFamily;
+ set fontFamily(String value) {
+ if (value != _fontFamily) {
+ _fontFamily = value;
+ notifyListeners();
+ Store.setFontFamily(value);
+ }
+ }
}=
\ No newline at end of file
M lib/pages/article_page.dart => lib/pages/article_page.dart +10 -1
@@ 7,6 7,7 @@ import 'package:fluent_reader_lite/models/items_model.dart';
import 'package:fluent_reader_lite/models/source.dart';
import 'package:fluent_reader_lite/models/sources_model.dart';
import 'package:fluent_reader_lite/utils/colors.dart';
+import 'package:fluent_reader_lite/utils/font_manager.dart';
import 'package:fluent_reader_lite/utils/global.dart';
import 'package:fluent_reader_lite/utils/store.dart';
import 'package:flutter/cupertino.dart';
@@ 94,7 95,15 @@ class ArticlePageState extends State<ArticlePage> {
h += '<article></article>';
h = Uri.encodeComponent(h);
var s = Store.getArticleFontSize();
- localUrl += "?a=$a&h=$h&s=$s&u=${item.link}&m=${loadFull ? 1 : 0}";
+ var f = Uri.encodeComponent(Global.globalModel.fontFamily);
+ localUrl += "?a=$a&h=$h&s=$s&f=$f&u=${item.link}&m=${loadFull ? 1 : 0}";
+ // Pass custom font URL for non-built-in fonts
+ if (!FontManager.builtInFonts.contains(Global.globalModel.fontFamily)) {
+ final fontUrl = await FontManager.getCustomFontUrl(Global.globalModel.fontFamily);
+ if (fontUrl != null) {
+ localUrl += "&fp=${Uri.encodeComponent(fontUrl)}";
+ }
+ }
if (Platform.isAndroid || Global.globalModel.getBrightness() != null) {
var brightness = Global.currentBrightness(context);
localUrl += "&t=${brightness.index}";
M lib/pages/settings/reading_page.dart => lib/pages/settings/reading_page.dart +198 -0
@@ 3,7 3,11 @@ import 'package:fluent_reader_lite/components/my_list_tile.dart';
import 'package:fluent_reader_lite/generated/l10n.dart';
import 'package:fluent_reader_lite/utils/colors.dart';
import 'package:fluent_reader_lite/utils/store.dart';
+import 'package:fluent_reader_lite/utils/global.dart';
+import 'package:fluent_reader_lite/utils/font_manager.dart';
import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:file_picker/file_picker.dart';
class ReadingPage extends StatefulWidget {
@override
@@ 12,6 16,178 @@ class ReadingPage extends StatefulWidget {
class _ReadingPageState extends State<ReadingPage> {
int _fontSize = Store.getArticleFontSize();
+ List<String> _customFontNames = [];
+ List<String> _availableFonts = [];
+
+ @override
+ void initState() {
+ super.initState();
+ _loadCustomFonts();
+ }
+
+ void _loadCustomFonts() async {
+ final customFonts = await FontManager.getInstalledCustomFonts();
+ setState(() {
+ _customFontNames = customFonts;
+ _availableFonts = [...FontManager.builtInFonts, ...customFonts];
+ });
+ }
+
+ void _showFontPicker(BuildContext context) {
+ showCupertinoModalPopup(
+ context: context,
+ builder: (context) => CupertinoActionSheet(
+ title: Text(S.of(context).selectFontFamily),
+ actions: _availableFonts.map((font) {
+ final isCustom = !FontManager.builtInFonts.contains(font);
+ final isSelected = Global.globalModel.fontFamily == font;
+ final isSystem = font == 'System';
+ return CupertinoActionSheetAction(
+ child: Column(
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Text(
+ FontManager.getFontDisplayName(font),
+ style: TextStyle(
+ fontFamily: isCustom || isSystem ? null : font,
+ fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
+ ),
+ ),
+ if (isSelected) Padding(
+ padding: EdgeInsets.only(left: 8),
+ child: Icon(Icons.done, size: 18, color: CupertinoColors.activeBlue),
+ ),
+ ],
+ ),
+ if (!isSystem && !isCustom)
+ Text(
+ 'The quick brown fox jumps over the lazy dog',
+ style: TextStyle(
+ fontFamily: font,
+ fontSize: 12,
+ color: CupertinoColors.systemGrey,
+ ),
+ ),
+ if (isCustom)
+ Text(
+ '(${S.of(context).uploadCustomFont})',
+ style: TextStyle(
+ fontSize: 12,
+ color: CupertinoColors.systemGrey,
+ ),
+ ),
+ ],
+ ),
+ onPressed: () {
+ Global.globalModel.fontFamily = font;
+ Navigator.pop(context);
+ setState(() {});
+ },
+ );
+ }).toList(),
+ cancelButton: CupertinoActionSheetAction(
+ child: Text(S.of(context).cancel),
+ onPressed: () => Navigator.pop(context),
+ ),
+ ),
+ );
+ }
+
+ void _uploadCustomFont() async {
+ FilePickerResult? result = await FilePicker.platform.pickFiles(
+ type: FileType.custom,
+ allowedExtensions: ['ttf', 'otf', 'woff', 'woff2'],
+ );
+
+ if (result != null && result.files.single.path != null) {
+ try {
+ final sourcePath = result.files.single.path!;
+ final fileName = result.files.single.name;
+
+ await FontManager.installCustomFont(sourcePath, fileName);
+
+ final fontName = fileName.split('.').first;
+ Global.globalModel.fontFamily = fontName;
+
+ _loadCustomFonts();
+
+ showCupertinoDialog(
+ context: context,
+ builder: (context) => CupertinoAlertDialog(
+ title: Text(S.of(context).fontUploaded),
+ content: Text(S.of(context).customFontUploaded),
+ actions: [
+ CupertinoDialogAction(
+ child: Text('OK'),
+ onPressed: () => Navigator.pop(context),
+ ),
+ ],
+ ),
+ );
+ } catch (e) {
+ showCupertinoDialog(
+ context: context,
+ builder: (context) => CupertinoAlertDialog(
+ title: Text(S.of(context).fontUploadError),
+ content: Text('$e'),
+ actions: [
+ CupertinoDialogAction(
+ child: Text('OK'),
+ onPressed: () => Navigator.pop(context),
+ ),
+ ],
+ ),
+ );
+ }
+ }
+ }
+
+ void _confirmDeleteFont(BuildContext context, String fontName) {
+ showCupertinoDialog(
+ context: context,
+ builder: (context) => CupertinoAlertDialog(
+ title: Text(S.of(context).deleteFont),
+ content: Text(S.of(context).deleteFontConfirm),
+ actions: [
+ CupertinoDialogAction(
+ child: Text(S.of(context).cancel),
+ onPressed: () => Navigator.pop(context),
+ ),
+ CupertinoDialogAction(
+ isDestructiveAction: true,
+ child: Text(S.of(context).confirm),
+ onPressed: () async {
+ Navigator.pop(context);
+ try {
+ await FontManager.removeCustomFont(fontName);
+ if (Global.globalModel.fontFamily == fontName) {
+ Global.globalModel.fontFamily = 'System';
+ Store.sp.remove(StoreKeys.CUSTOM_FONT_PATH);
+ }
+ _loadCustomFonts();
+ } catch (e) {
+ showCupertinoDialog(
+ context: context,
+ builder: (ctx) => CupertinoAlertDialog(
+ title: Text(S.of(ctx).fontUploadError),
+ content: Text('$e'),
+ actions: [
+ CupertinoDialogAction(
+ child: Text('OK'),
+ onPressed: () => Navigator.pop(ctx),
+ ),
+ ],
+ ),
+ );
+ }
+ },
+ ),
+ ],
+ ),
+ );
+ }
@override
Widget build(BuildContext context) {
@@ 41,6 217,28 @@ class _ReadingPageState extends State<ReadingPage> {
withDivider: false,
),
], title: S.of(context).preferences),
+ ListTileGroup([
+ MyListTile(
+ title: Text(S.of(context).fontFamily),
+ trailing: Text(FontManager.getFontDisplayName(Global.globalModel.fontFamily)),
+ onTap: () => _showFontPicker(context),
+ ),
+ MyListTile(
+ title: Text(S.of(context).uploadCustomFont),
+ trailing: Icon(CupertinoIcons.add),
+ onTap: _uploadCustomFont,
+ withDivider: _customFontNames.isNotEmpty,
+ ),
+ ..._customFontNames.map((fontName) => MyListTile(
+ title: Text(fontName),
+ trailing: GestureDetector(
+ onTap: () => _confirmDeleteFont(context, fontName),
+ child: Icon(CupertinoIcons.delete, color: CupertinoColors.destructiveRed, size: 20),
+ ),
+ trailingChevron: false,
+ withDivider: fontName != _customFontNames.last,
+ )),
+ ], title: S.of(context).fontSettings),
]),
);
}
A lib/utils/font_manager.dart => lib/utils/font_manager.dart +107 -0
@@ 0,0 1,107 @@
+import 'dart:io';
+import 'package:path_provider/path_provider.dart';
+
+class FontManager {
+ static const supportedExtensions = ['.ttf', '.otf', '.woff', '.woff2'];
+ static const builtInFonts = ['System', 'OpenSans', 'Roboto', 'SourceSerif'];
+
+ static Future<Directory> getFontsDirectory() async {
+ final directory = await getApplicationDocumentsDirectory();
+ final fontsDir = Directory('${directory.path}/fonts');
+ if (!await fontsDir.exists()) {
+ await fontsDir.create(recursive: true);
+ }
+ return fontsDir;
+ }
+
+ static Future<String> installCustomFont(String sourcePath, String fileName) async {
+ try {
+ final fontsDir = await getFontsDirectory();
+ final sourceFile = File(sourcePath);
+
+ // Basic validation: fonts should be > 1KB and < 50MB
+ final fileSize = await sourceFile.length();
+ if (fileSize < 1024 || fileSize > 50 * 1024 * 1024) {
+ throw Exception('Invalid font file size');
+ }
+
+ final targetPath = '${fontsDir.path}/$fileName';
+ final targetFile = await sourceFile.copy(targetPath);
+ return targetFile.path;
+ } catch (e) {
+ throw Exception('Failed to install custom font: $e');
+ }
+ }
+
+ static Future<List<String>> getInstalledCustomFonts() async {
+ try {
+ final fontsDir = await getFontsDirectory();
+ final files = await fontsDir.list().toList();
+ return files
+ .where((file) => file is File &&
+ supportedExtensions.any((ext) => file.path.toLowerCase().endsWith(ext)))
+ .map((file) {
+ final fileName = file.path.split(Platform.pathSeparator).last;
+ return fileName.split('.').first;
+ })
+ .toList();
+ } catch (e) {
+ return [];
+ }
+ }
+
+ /// Get the full file name (with extension) for a given font name
+ static Future<String?> getFontFileName(String fontName) async {
+ final fontsDir = await getFontsDirectory();
+ final files = await fontsDir.list().toList();
+ for (final file in files) {
+ if (file is File) {
+ final fileName = file.path.split(Platform.pathSeparator).last;
+ final nameWithoutExt = fileName.split('.').first;
+ if (nameWithoutExt == fontName) {
+ return fileName;
+ }
+ }
+ }
+ return null;
+ }
+
+ /// Get the local server URL for a custom font
+ static Future<String?> getCustomFontUrl(String fontName) async {
+ final fileName = await getFontFileName(fontName);
+ if (fileName == null) return null;
+ return 'http://127.0.0.1:9000/custom-fonts/${Uri.encodeComponent(fileName)}';
+ }
+
+ static Future<void> removeCustomFont(String fontName) async {
+ try {
+ final fontsDir = await getFontsDirectory();
+ final files = await fontsDir.list().toList();
+ for (final file in files) {
+ if (file is File) {
+ final fileName = file.path.split(Platform.pathSeparator).last;
+ final nameWithoutExt = fileName.split('.').first;
+ if (nameWithoutExt == fontName) {
+ await file.delete();
+ break;
+ }
+ }
+ }
+ } catch (e) {
+ throw Exception('Failed to remove custom font: $e');
+ }
+ }
+
+ static String getFontDisplayName(String fontFamily) {
+ switch (fontFamily) {
+ case 'System':
+ return 'System Font';
+ case 'OpenSans':
+ return 'Open Sans';
+ case 'SourceSerif':
+ return 'Source Serif';
+ default:
+ return fontFamily;
+ }
+ }
+}
M lib/utils/global.dart => lib/utils/global.dart +30 -1
@@ 1,3 1,5 @@
+import 'dart:io';
+
import 'package:fluent_reader_lite/models/feeds_model.dart';
import 'package:fluent_reader_lite/models/global_model.dart';
import 'package:fluent_reader_lite/models/groups_model.dart';
@@ 9,9 11,10 @@ import 'package:fluent_reader_lite/models/services/greader.dart';
import 'package:fluent_reader_lite/models/sources_model.dart';
import 'package:fluent_reader_lite/models/sync_model.dart';
import 'package:fluent_reader_lite/utils/db.dart';
+import 'package:fluent_reader_lite/utils/font_manager.dart';
import 'package:fluent_reader_lite/utils/store.dart';
import 'package:flutter/cupertino.dart';
-import 'package:jaguar/serve/server.dart';
+import 'package:jaguar/jaguar.dart';
import 'package:jaguar_flutter_asset/jaguar_flutter_asset.dart';
import 'package:sqflite/sqflite.dart';
@@ 68,6 71,32 @@ abstract class Global {
);
server = Jaguar(address: "127.0.0.1",port: 9000);
server.addRoute(serveFlutterAssets());
+
+ // Serve custom font files from app documents directory
+ final fontsDir = await FontManager.getFontsDirectory();
+ server.get('/custom-fonts/:filename*', (ctx) async {
+ final filename = ctx.pathParams['filename'];
+ if (filename == null || filename.isEmpty) {
+ return Response(statusCode: 404);
+ }
+ // Security: only allow font file extensions
+ final lowerFilename = filename.toLowerCase();
+ if (!FontManager.supportedExtensions.any((ext) => lowerFilename.endsWith(ext))) {
+ return Response(statusCode: 403);
+ }
+ final file = File('${fontsDir.path}/$filename');
+ if (!await file.exists()) {
+ return Response(statusCode: 404);
+ }
+ String mimeType = 'application/octet-stream';
+ if (lowerFilename.endsWith('.ttf')) mimeType = 'font/ttf';
+ else if (lowerFilename.endsWith('.otf')) mimeType = 'font/otf';
+ else if (lowerFilename.endsWith('.woff2')) mimeType = 'font/woff2';
+ else if (lowerFilename.endsWith('.woff')) mimeType = 'font/woff';
+ final bytes = await file.readAsBytes();
+ return ByteResponse(body: bytes, mimeType: mimeType);
+ });
+
await server.serve();
await sourcesModel.init();
await feedsModel.all.init();
M lib/utils/store.dart => lib/utils/store.dart +22 -0
@@ 30,6 30,8 @@ abstract class StoreKeys {
// Reading preferences
static const ARTICLE_FONT_SIZE = "articleFontSize";
+ static const FONT_FAMILY = "fontFamily";
+ static const CUSTOM_FONT_PATH = "customFontPath";
// Syncing
static const SYNC_SERVICE = "syncService";
@@ 124,4 126,24 @@ class Store {
static void setErrorLog(String value) {
sp.setString(StoreKeys.ERROR_LOG, value);
}
+
+ static String getFontFamily() {
+ return sp.getString(StoreKeys.FONT_FAMILY) ?? "System";
+ }
+
+ static void setFontFamily(String value) {
+ sp.setString(StoreKeys.FONT_FAMILY, value);
+ }
+
+ static String getCustomFontPath() {
+ return sp.getString(StoreKeys.CUSTOM_FONT_PATH);
+ }
+
+ static void setCustomFontPath(String value) {
+ if (value == null) {
+ sp.remove(StoreKeys.CUSTOM_FONT_PATH);
+ } else {
+ sp.setString(StoreKeys.CUSTOM_FONT_PATH, value);
+ }
+ }
}=
\ No newline at end of file
M pubspec.lock => pubspec.lock +74 -34
@@ 53,26 53,26 @@ packages:
dependency: transitive
description:
name: characters
- sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c
+ sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
url: "https://pub.dev"
source: hosted
- version: "1.2.1"
+ version: "1.4.0"
clock:
dependency: transitive
description:
name: clock
- sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
+ sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.dev"
source: hosted
- version: "1.1.1"
+ version: "1.1.2"
collection:
dependency: transitive
description:
name: collection
- sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0
+ sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev"
source: hosted
- version: "1.17.0"
+ version: "1.19.1"
crypto:
dependency: "direct main"
description:
@@ 101,10 101,10 @@ packages:
dependency: transitive
description:
name: fake_async
- sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
+ sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev"
source: hosted
- version: "1.3.1"
+ version: "1.3.3"
ffi:
dependency: transitive
description:
@@ 121,6 121,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.1.4"
+ file_picker:
+ dependency: "direct main"
+ description:
+ name: file_picker
+ sha256: "9d6e95ec73abbd31ec54d0e0df8a961017e165aba1395e462e5b31ea0c165daf"
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.3.1"
flutter:
dependency: "direct main"
description: flutter
@@ 147,6 155,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
+ flutter_plugin_android_lifecycle:
+ dependency: transitive
+ description:
+ name: flutter_plugin_android_lifecycle
+ sha256: b0694b7fb1689b0e6cc193b3f1fcac6423c4f93c74fb20b806c6b6f196db0c31
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.30"
flutter_test:
dependency: "direct dev"
description: flutter
@@ 193,10 209,10 @@ packages:
dependency: "direct main"
description:
name: intl
- sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91"
+ sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
url: "https://pub.dev"
source: hosted
- version: "0.17.0"
+ version: "0.20.2"
jaguar:
dependency: "direct main"
description:
@@ 221,14 237,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.0"
- js:
+ leak_tracker:
dependency: transitive
description:
- name: js
- sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7"
+ name: leak_tracker
+ sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
url: "https://pub.dev"
source: hosted
- version: "0.6.5"
+ version: "11.0.2"
+ leak_tracker_flutter_testing:
+ dependency: transitive
+ description:
+ name: leak_tracker_flutter_testing
+ sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.10"
+ leak_tracker_testing:
+ dependency: transitive
+ description:
+ name: leak_tracker_testing
+ sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.2"
logging:
dependency: transitive
description:
@@ 249,26 281,26 @@ packages:
dependency: transitive
description:
name: matcher
- sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72"
+ sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.dev"
source: hosted
- version: "0.12.13"
+ version: "0.12.17"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
- sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
+ sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
- version: "0.2.0"
+ version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
- sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42"
+ sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
url: "https://pub.dev"
source: hosted
- version: "1.8.0"
+ version: "1.16.0"
mime:
dependency: transitive
description:
@@ 329,12 361,12 @@ packages:
dependency: "direct main"
description:
name: path
- sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b
+ sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
source: hosted
- version: "1.8.2"
+ version: "1.9.1"
path_provider:
- dependency: transitive
+ dependency: "direct main"
description:
name: path_provider
sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa
@@ 497,7 529,7 @@ packages:
dependency: transitive
description: flutter
source: sdk
- version: "0.0.99"
+ version: "0.0.0"
source_span:
dependency: transitive
description:
@@ 534,18 566,18 @@ packages:
dependency: transitive
description:
name: stack_trace
- sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
+ sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.dev"
source: hosted
- version: "1.11.0"
+ version: "1.12.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
- sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
+ sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.dev"
source: hosted
- version: "2.1.1"
+ version: "2.1.4"
string_scanner:
dependency: transitive
description:
@@ 574,10 606,10 @@ packages:
dependency: transitive
description:
name: test_api
- sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206
+ sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
url: "https://pub.dev"
source: hosted
- version: "0.4.16"
+ version: "0.7.6"
tuple:
dependency: "direct main"
description:
@@ 702,10 734,18 @@ packages:
dependency: transitive
description:
name: vector_math
- sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
+ sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
url: "https://pub.dev"
source: hosted
- version: "2.1.4"
+ version: "2.2.0"
+ vm_service:
+ dependency: transitive
+ description:
+ name: vm_service
+ sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
+ url: "https://pub.dev"
+ source: hosted
+ version: "15.0.2"
webview_flutter:
dependency: "direct main"
description:
@@ 755,5 795,5 @@ packages:
source: hosted
version: "1.0.3"
sdks:
- dart: ">=2.19.0 <3.0.0"
- flutter: ">=3.7.0"
+ dart: ">=3.8.0-0 <4.0.0"
+ flutter: ">=3.29.0"
M pubspec.yaml => pubspec.yaml +21 -21
@@ 18,7 18,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.4+11
environment:
- sdk: ">=2.7.0 <3.0.0"
+ sdk: ">=3.0.0 <4.0.0"
dependencies:
flutter:
@@ 28,7 28,7 @@ dependencies:
provider: ^6.0.3
tuple: ^2.0.0
shared_preferences: ^2.0.15
- intl: ^0.17.0
+ intl: 0.20.2
http: ^0.13.4
html: ^0.15.0
webview_flutter: ^3.0.4
@@ 47,6 47,8 @@ dependencies:
uni_links: ^0.5.1
modal_bottom_sheet: ^3.0.0-pre
overlay_dialog: ^0.2.0
+ file_picker: ^5.0.0
+ path_provider: ^2.0.0
# The following adds the Cupertino Icons font to your application.
@@ 72,6 74,7 @@ flutter:
assets:
- assets/article/
- assets/icons/
+ - assets/fonts/
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.
@@ 79,25 82,22 @@ flutter:
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
- # To add custom fonts to your application, add a fonts section here,
- # in this "flutter" section. Each entry in this list should have a
- # "family" key with the font family name, and a "fonts" key with a
- # list giving the asset and other descriptors for the font. For
- # example:
- # fonts:
- # - family: Schyler
- # fonts:
- # - asset: fonts/Schyler-Regular.ttf
- # - asset: fonts/Schyler-Italic.ttf
- # style: italic
- # - family: Trajan Pro
- # fonts:
- # - asset: fonts/TrajanPro.ttf
- # - asset: fonts/TrajanPro_Bold.ttf
- # weight: 700
- #
- # For details regarding fonts from package dependencies,
- # see https://flutter.dev/custom-fonts/#from-packages
+ fonts:
+ - family: OpenSans
+ fonts:
+ - asset: assets/fonts/OpenSans-Regular.ttf
+ - asset: assets/fonts/OpenSans-Bold.ttf
+ weight: 700
+ - family: Roboto
+ fonts:
+ - asset: assets/fonts/Roboto-Regular.ttf
+ - asset: assets/fonts/Roboto-Bold.ttf
+ weight: 700
+ - family: SourceSerif
+ fonts:
+ - asset: assets/fonts/SourceSerif-Regular.ttf
+ - asset: assets/fonts/SourceSerif-Bold.ttf
+ weight: 700
flutter_intl:
enabled: true