Compare commits
82 Commits
v1.0.1-bet
...
v1.0.10-be
Author | SHA1 | Date | |
---|---|---|---|
659b3426e8 | |||
7b6394f6ad | |||
2df0d1f655 | |||
18723e1d78 | |||
7316c64685 | |||
43aba47453 | |||
e79042ebc1 | |||
1e84848d39 | |||
78a50e28ff | |||
fda962a46f | |||
a7ca206b10 | |||
6be9927b26 | |||
ea86d76015 | |||
f396f92463 | |||
70e96b12e1 | |||
9a91cffb31 | |||
669e5f7a66 | |||
6e1f75b11a | |||
7f36ca98b7 | |||
1025552f2f | |||
11a7dca93c | |||
ccd60ac357 | |||
d0c8128631 | |||
e1a42df7e7 | |||
50f907eff7 | |||
c0c0de58a5 | |||
f0940e2947 | |||
62ff665b87 | |||
67d894855a | |||
a3ca57772d | |||
d9b19a6ec3 | |||
e013713516 | |||
d655a4bd32 | |||
9b696c7e9d | |||
cbbe049ef8 | |||
cddaeb553a | |||
ced66d0d1c | |||
17dcbb05b3 | |||
27e43a358c | |||
1715b73494 | |||
4c557ed28d | |||
61a4e566df | |||
2a2e5df118 | |||
aac2e807f0 | |||
95288271fa | |||
73a294fb89 | |||
c068714e52 | |||
5fa66ba42f | |||
034c3aaa5e | |||
c7ed1a3c4a | |||
7d550ea399 | |||
dfc70cfce0 | |||
1aedffc919 | |||
7364f69b60 | |||
a342f80fee | |||
3d56cd2c65 | |||
74793e5d65 | |||
88c1612184 | |||
2924020eb7 | |||
3bda1a7284 | |||
b0579a5c15 | |||
092f6029b6 | |||
51283c3410 | |||
3a95d79c4a | |||
9aeea38192 | |||
76c80be783 | |||
f71535438b | |||
b536e1600a | |||
2ddc71d830 | |||
013cce078f | |||
7f3e255259 | |||
0825857231 | |||
9255201049 | |||
ed8a6e01e9 | |||
9def607737 | |||
6d0d417ce0 | |||
b127683003 | |||
a6775b95a9 | |||
647687c0c9 | |||
9dc7c67440 | |||
ccd260d451 | |||
6ac9f0c77a |
@ -1,24 +1,30 @@
|
||||
## Openlib Contribution guidelines
|
||||
|
||||
# Code contribution
|
||||
### Getting started
|
||||
|
||||
### Fork the repo
|
||||
- This project is powered by Flutter. Make sure you have the latest version of Flutter.
|
||||
|
||||
- Clone the Openlib repository with git clone https://github.com/dstark5/Openlib.git (or use the link from your own fork, if you want to open a PR)
|
||||
- If you want to fix bug or implement a new feature, that already mention in the [issues](https://github.com/dstark5/Openlib/issues), please, assign this issue to you and/or comment about it.
|
||||
|
||||
|
||||
### Make your update:
|
||||
Make your changes to the file(s) you'd like to update.
|
||||
- Whether you have to implement new feature, please, open an issue or discussion regarding it to ensure it will be accepted.
|
||||
|
||||
### Open a pull request
|
||||
|
||||
When you're done making changes and you'd like to propose them for review, open your PR (pull request). You can use the GitHub user interface for some small changes, like fixing a typo or updating a readme. You can also fork the repo and then clone it locally, to view changes and run your tests on your machine.
|
||||
|
||||
### Before PR Self Review
|
||||
|
||||
Always review your own PR first
|
||||
|
||||
- Confirm the changes doesn't break anything else.
|
||||
- Compare your pull request's source changes to staging to confirm that the output matches the source and that everything is rendering as expected. This helps spot issues like typos, content that doesn't follow the style guide, or content that isn't rendering due to versioning problems.
|
||||
|
||||
### Submit your PR & get it reviewed
|
||||
Once you Submit Your PR We Review The Code And Merge It.
|
||||
- Once you submit your PR, others from the Docs community will review it with you. The first thing you're going to want to do is a [self review](#self-review).
|
||||
- After that, we may have questions, check back on your PR to keep up with the conversation.
|
||||
- We may ask for changes to be made before a PR can be merged. You can make any other changes in your fork, then commit them to your branch.
|
||||
|
||||
### Self review
|
||||
You should always review your own PR first.
|
||||
|
||||
For content changes, make sure that you:
|
||||
- [ ] Confirm the changes doesn't break anything else.
|
||||
- [ ] Compare your pull request's source changes to staging to confirm that the output matches the source and that everything is rendering as expected. This helps spot issues like typos, content that doesn't follow the style guide, or content that isn't rendering due to versioning problems.
|
||||
- [ ] Review the content for technical accuracy.
|
||||
- [ ] Copy-edit the changes for grammar or spelling mistakes.
|
||||
|
||||
### Notes
|
||||
- Please, do not modify readme and other information files (except for typos).
|
||||
- Avoid adding new dependencies unless required. APK size is important.
|
115
README.md
@ -1,25 +1,34 @@
|
||||
<p align="center"><img src="assets/icons/appIcon.png" width="150"></p>
|
||||
<h1 align="center"><b>Openlib</b></h1>
|
||||
<div align="center">
|
||||
|
||||
#### An Open source app to download and read books from shadow library ([Anna’s Archive](https://annas-archive.org/)).
|
||||
<img src="assets/icons/appIcon.png" width="150">
|
||||
|
||||
[](https://flutter.dev/) [](https://opensource.org/licenses/) 
|
||||
# Openlib
|
||||
|
||||
[<img src="github_releases.png"
|
||||
alt="Download from GitHub"
|
||||
height="80">](https://github.com/dstark5/Openlib/releases) [<img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png"
|
||||
alt="Get it on IzzyDroid"
|
||||
height="80">](https://android.izzysoft.de/repo/apk/com.app.openlib)
|
||||
An Open source app to download and read books from shadow library ([Anna’s Archive](https://annas-archive.org/))
|
||||
|
||||
[](https://flutter.dev/)
|
||||
[](https://opensource.org/licenses/)
|
||||
[](https://github.com/dstark5/Openlib/releases)
|
||||
|
||||
## Note
|
||||
[<img src="github_releases.png"
|
||||
alt="Get it on GitHub"
|
||||
height="60">](https://github.com/dstark5/Openlib/releases)
|
||||
[<img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png"
|
||||
alt="Get it on IzzyOnDroid"
|
||||
height="60">](https://android.izzysoft.de/repo/apk/com.app.openlib)
|
||||
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
|
||||
alt="Get it on F-Droid"
|
||||
height="60">](https://f-droid.org/en/packages/com.app.openlib/)
|
||||
|
||||
</div>
|
||||
|
||||
## Note 📝
|
||||
|
||||
**WARNING:** This App Is In Beta Stage, So You May Encounter Bugs. If You Do, Open An Issue In Github Repository.
|
||||
|
||||
#### Publishing Openlib, Or Any Fork Of It In The Google Play Store Violates Their Terms And Conditions.
|
||||
**Publishing Openlib, Or Any Fork Of It In The Google Play Store Violates Their Terms And Conditions**
|
||||
|
||||
|
||||
## Screenshots
|
||||
## Screenshots 🖼️
|
||||
|
||||
[<img src="screenshots/Screenshot_1.png" width=160>](screenshots/Screenshot_1.png)
|
||||
[<img src="screenshots/Screenshot_2.png" width=160>](screenshots/Screenshot_2.png)
|
||||
@ -30,27 +39,27 @@
|
||||
[<img src="screenshots/Screenshot_7.png" width=160>](screenshots/Screenshot_7.png)
|
||||
[<img src="screenshots/Screenshot_8.png" width=160>](screenshots/Screenshot_8.png)
|
||||
|
||||
## Description
|
||||
##### Openlib Is An Open Source App To Download And Read Books From Shadow Library ([Anna’s Archive](https://annas-archive.org/)) . The App Has Built In Reader to Read Books.
|
||||
## Description 📖
|
||||
|
||||
##### As [Anna’s Archive](https://annas-archive.org/) Doesn't Have An API.The App Works By Sending Request To Anna’s Archive And Parses The Response To objects.The App Extracts The Mirrors From Response And Downloads The Book And Store It In The Application Document Directory.
|
||||
## Features
|
||||
- Trending Books
|
||||
Openlib Is An Open Source App To Download And Read Books From Shadow Library ([Anna’s Archive](https://annas-archive.org/)). The App Has Built In Reader to Read Books
|
||||
|
||||
As [Anna’s Archive](https://annas-archive.org/) Doesn't Have An API. The App Works By Sending Request To Anna’s Archive And Parses The Response To objects. The App Extracts The Mirrors From Response And Downloads The Book
|
||||
|
||||
## Features ✨
|
||||
|
||||
- Trending Books
|
||||
- Download And Read Books With In-Built Viewer
|
||||
- Supports Epub And Pdf Formats
|
||||
- Open Books In Your Favourite Ebook Reader
|
||||
- Filter Books
|
||||
- Sort Books
|
||||
|
||||
## Roadmap 🎯
|
||||
|
||||
## Roadmap
|
||||
|
||||
- Additing More Book Format supports (cbz,cbr,azw3,etc...)
|
||||
- Adding More Book Format supports (cbz,cbr,azw3,etc...)
|
||||
- Adding Support For Background Downloads
|
||||
- Adding Support For Multiple Downloads
|
||||
|
||||
## Installation and updates
|
||||
- Download the APK from [ IzzyOnDroid ](https://android.izzysoft.de/repo/apk/com.app.openlib) and install it.
|
||||
- Download the APK from [GitHub Releases](https://github.com/dstark5/Openlib/releases) and install it.
|
||||
- Move existing books when changing the storage path
|
||||
|
||||
## Building from Source
|
||||
|
||||
@ -58,39 +67,55 @@
|
||||
|
||||
- Git Clone The Repo
|
||||
|
||||
```
|
||||
git clone https://github.com/dstark5/Openlib.git
|
||||
```
|
||||
```sh
|
||||
git clone https://github.com/dstark5/Openlib.git
|
||||
```
|
||||
|
||||
- Run the app with Android Studio or VS Code. Or the command line:
|
||||
|
||||
```
|
||||
flutter pub get
|
||||
flutter run
|
||||
```
|
||||
```sh
|
||||
flutter pub get
|
||||
flutter run
|
||||
```
|
||||
|
||||
- To Build App Run:
|
||||
```
|
||||
flutter build
|
||||
```
|
||||
|
||||
```sh
|
||||
flutter build
|
||||
```
|
||||
|
||||
- The Build Will Be In './build/app/outputs/flutter-apk/app-release.apk'
|
||||
|
||||
## Contribution
|
||||
Whether you have ideas, design changes or even major code changes, help is always welcome. The app gets better and better with each contribution, no matter how big or small! If you'd like to get involved ,Please read our [CONTRIBUTING.md](https://github.com/dstark5/Openlib/blob/main/CONTRIBUTING.md)
|
||||
### Android
|
||||
|
||||
## Issues
|
||||
Make sure that `android/local.properties` has `flutter.minSdkVersion=21` or above
|
||||
|
||||
## Contributor required 🚧
|
||||
|
||||
We are actively seeking contributors. Whether you're a seasoned developer or just starting out, we welcome your contributions to help make this project even better!
|
||||
|
||||
## Contribution 💝
|
||||
|
||||
Whether you have ideas, design changes or even major code changes, help is always welcome. The app gets better and better with each contribution, no matter how big or small!
|
||||
|
||||
If you'd like to get involved See [CONTRIBUTING.md](./CONTRIBUTING.md) for the guidelines.
|
||||
|
||||
## Issues 🚩
|
||||
|
||||
Please report bugs via the [issue tracker](https://github.com/dstark5/Openlib/issues).
|
||||
|
||||
## Donate
|
||||
If you like Openlib, you're welcome to send a donation.
|
||||
#### [Donate To Anna’s Archive](https://annas-archive.org/donate?tier=1).
|
||||
## Donate 🎁
|
||||
|
||||
If you like Openlib, you're welcome to send a donation.
|
||||
|
||||
[Donate To Anna’s Archive.](https://annas-archive.org/donate?tier=1)
|
||||
|
||||
## License 📜
|
||||
|
||||
## License
|
||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
||||
Openlib is a free software licensed under GPL v3.0 It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY.[GNU General Public License](https://www.gnu.org/licenses/gpl.html) as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
Openlib is a free software licensed under GPL v3.0 It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY. [GNU General Public License](https://www.gnu.org/licenses/gpl.html) as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
|
||||
## Disclaimer
|
||||
## Disclaimer ⚠️
|
||||
|
||||
Openlib does not own or have any affiliation with the books available through the app.All books are the property of their respective owners and are protected by copyright law.Openlib is not responsible for any infringement of copyright or other intellectual property rights that may result from the use of the books available through the app.By using the app, you agree to use the books only for personal, non-commercial purposes and in compliance with all applicable laws and regulations.
|
||||
Openlib does not own or have any affiliation with the books available through the app. All books are the property of their respective owners and are protected by copyright law. Openlib is not responsible for any infringement of copyright or other intellectual property rights that may result from the use of the books available through the app. By using the app, you agree to use the books only for personal, non-commercial purposes and in compliance with all applicable laws and regulations.
|
||||
|
@ -1,75 +1,49 @@
|
||||
def localProperties = new Properties()
|
||||
def localPropertiesFile = rootProject.file('local.properties')
|
||||
if (localPropertiesFile.exists()) {
|
||||
localPropertiesFile.withReader('UTF-8') { reader ->
|
||||
localProperties.load(reader)
|
||||
}
|
||||
plugins {
|
||||
id "com.android.application"
|
||||
id "kotlin-android"
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id "dev.flutter.flutter-gradle-plugin"
|
||||
}
|
||||
|
||||
def flutterRoot = localProperties.getProperty('flutter.sdk')
|
||||
if (flutterRoot == null) {
|
||||
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
|
||||
}
|
||||
|
||||
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||
if (flutterVersionCode == null) {
|
||||
flutterVersionCode = '1'
|
||||
}
|
||||
|
||||
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
||||
if (flutterVersionName == null) {
|
||||
flutterVersionName = '1.0'
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||
|
||||
|
||||
def keystoreProperties = new Properties()
|
||||
def keystorePropertiesFile = rootProject.file('key.properties')
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
|
||||
}
|
||||
|
||||
|
||||
android {
|
||||
namespace "com.app.openlib"
|
||||
compileSdkVersion flutter.compileSdkVersion
|
||||
ndkVersion flutter.ndkVersion
|
||||
namespace = "com.app.openlib"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
jvmTarget = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "com.app.openlib"
|
||||
applicationId = "com.app.openlib"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
||||
minSdkVersion localProperties.getProperty('flutter.minSdkVersion').toInteger()
|
||||
targetSdkVersion flutter.targetSdkVersion
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
minSdk = flutter.minSdkVersion
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
release {
|
||||
keyAlias keystoreProperties['keyAlias']
|
||||
keyPassword keystoreProperties['keyPassword']
|
||||
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
|
||||
storePassword keystoreProperties['storePassword']
|
||||
v1SigningEnabled true
|
||||
v2SigningEnabled true
|
||||
keyAlias = keystoreProperties['keyAlias']
|
||||
keyPassword = keystoreProperties['keyPassword']
|
||||
storeFile = keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
|
||||
storePassword = keystoreProperties['storePassword']
|
||||
v1SigningEnabled = true
|
||||
v2SigningEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,18 +51,13 @@ android {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
// replace with "debug" when running on debug version
|
||||
signingConfig signingConfigs.release
|
||||
minifyEnabled false
|
||||
shrinkResources false
|
||||
signingConfig = signingConfigs.release
|
||||
minifyEnabled = false
|
||||
shrinkResources = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source '../..'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
source = "../.."
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
@ -17,11 +19,16 @@
|
||||
<data android:scheme="https" />
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
<application
|
||||
android:label="Openlib"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/launcher_icon">
|
||||
<activity
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:icon="@mipmap/launcher_icon"
|
||||
android:enableOnBackInvokedCallback="true">
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
@ -33,6 +40,7 @@
|
||||
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"
|
||||
|
32
android/app/src/main/res/raw/annas_archive
Normal file
@ -0,0 +1,32 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFfTCCBGWgAwIBAgIRAI0YQRVveHeTEYLPgc2CDXowDQYJKoZIhvcNAQELBQAw
|
||||
RjELMAkGA1UEBhMCVVMxIjAgBgNVBAoTGUdvb2dsZSBUcnVzdCBTZXJ2aWNlcyBM
|
||||
TEMxEzARBgNVBAMTCkdUUyBDQSAxUDUwHhcNMjMxMDMwMjMwOTExWhcNMjQwMTI4
|
||||
MjMwOTEwWjAcMRowGAYDVQQDExFhbm5hcy1hcmNoaXZlLm9yZzCCASIwDQYJKoZI
|
||||
hvcNAQEBBQADggEPADCCAQoCggEBAKl79Oj7GW10c1ikBC/sSkyr2tmmIl1/iszu
|
||||
9DN5w3lbX3q5hLeSB2xPaW6FWPB0u+YR8tgXyk6jG8jkzSe2cdkW0pjKaxSNiQgW
|
||||
rXipoonUbaceSDd3aFQFGhqe0urkM+84Sgspy39REdxXuQeL2hXH8fouRPA65/pn
|
||||
2nipwkpZHOezEQfE7BbUYwt4/YQaWXD3ScBLNx0PJuZdL4sfVC41IP8Ml/i5zzU7
|
||||
mupl6EGgw5IuXwyHN1AC1NHQBU5/8X062/NdVhW/letbsR52Z6DJ727+nWLpdwq4
|
||||
WgowHui6PthI6h+F4LCu+g3V5akAibsNffqVyWssxaMWFXjMuDECAwEAAaOCAo4w
|
||||
ggKKMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMB
|
||||
Af8EAjAAMB0GA1UdDgQWBBSIWJPHr1GutuUbnUApJ/vU3TQP9DAfBgNVHSMEGDAW
|
||||
gBTV/J4N3x7K3QiXl24rxV/FK/XsuDB4BggrBgEFBQcBAQRsMGowNQYIKwYBBQUH
|
||||
MAGGKWh0dHA6Ly9vY3NwLnBraS5nb29nL3MvZ3RzMXA1L1VfN25sRDJPX1JRMDEG
|
||||
CCsGAQUFBzAChiVodHRwOi8vcGtpLmdvb2cvcmVwby9jZXJ0cy9ndHMxcDUuZGVy
|
||||
MDEGA1UdEQQqMCiCEWFubmFzLWFyY2hpdmUub3JnghMqLmFubmFzLWFyY2hpdmUu
|
||||
b3JnMCEGA1UdIAQaMBgwCAYGZ4EMAQIBMAwGCisGAQQB1nkCBQMwPAYDVR0fBDUw
|
||||
MzAxoC+gLYYraHR0cDovL2NybHMucGtpLmdvb2cvZ3RzMXA1L19ZUS1xNlF1bEJB
|
||||
LmNybDCCAQUGCisGAQQB1nkCBAIEgfYEgfMA8QB2AHb/iD8KtvuVUcJhzPWHujS0
|
||||
pM27KdxoQgqf5mdMWjp0AAABi4MQGPAAAAQDAEcwRQIhALVAeepoK0WtUPs01yzZ
|
||||
XjAkdE7WYGw2QNzSkvywFRoIAiAnp1Lec4PqwGPbg/ppC8PgGxb8+yvSlVT0ChUt
|
||||
NMinWgB3ANq2v2s/tbYin5vCu1xr6HCRcWy7UYSFNL2kPTBI1/urAAABi4MQGP0A
|
||||
AAQDAEgwRgIhAKbAEYr9qDmiafcnkXIG7xObbI4fz3IsLSht8etA/jSlAiEAkQ7G
|
||||
t6x81Onpl4RcTtrXLptI0fDkrAZ66hAPWbIH8SAwDQYJKoZIhvcNAQELBQADggEB
|
||||
AGfvZYtIOPKRvVyfI4tJpetCJmU/DEMbCIyX05M+2P1n2uY1D4tweAMmYf4trh5Z
|
||||
cuTK3QDeoICus3WK08L3Ni/699QbQ0uonp0IIIOi2NlP2rLhvtETWpmLoX6jM55W
|
||||
cYGiQldhCgWYEXoANrJshUbjkyM81QMNzSrn33JPkzWUdgVoS/KfABaeymLekXO4
|
||||
ndLp4ktLlYQZr3JJU39FvwgN8IcmeLWUnpSWsekH+nHSW9e8vOsNQoZyHw0minqz
|
||||
ZzFbS10reX1kG56+AxDf5fOOM+C+MAozSUnXUjrkpXXakwUooMTklKtYbBiwR2R0
|
||||
wrcYmVHymn07AUliDOalu2I=
|
||||
-----END CERTIFICATE-----
|
17
android/app/src/main/res/xml/network_security_config.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<base-config cleartextTrafficPermitted="false">
|
||||
<trust-anchors>
|
||||
<certificates src="@raw/annas_archive"/>
|
||||
<certificates src="system"/>
|
||||
</trust-anchors>
|
||||
</base-config>
|
||||
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
<domain includeSubdomains="true">secure.example.com</domain>
|
||||
<domain includeSubdomains="true">127.0.0.1</domain>
|
||||
</domain-config>
|
||||
<domain-config cleartextTrafficPermitted="false">
|
||||
<domain includeSubdomains="true">annas-archive.org</domain>
|
||||
</domain-config>
|
||||
</network-security-config>
|
@ -1,16 +1,3 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.7.10'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.3.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
@ -18,14 +5,14 @@ allprojects {
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.buildDir = '../build'
|
||||
rootProject.buildDir = "../build"
|
||||
subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(':app')
|
||||
project.evaluationDependsOn(":app")
|
||||
}
|
||||
|
||||
tasks.register("clean", Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
}
|
@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip
|
@ -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.0.0" apply false
|
||||
id "org.jetbrains.kotlin.android" version "1.8.0" apply false
|
||||
}
|
||||
|
||||
include ":app"
|
1
assets/captcha.svg
Normal file
After Width: | Height: | Size: 11 KiB |
@ -1 +1,19 @@
|
||||
<p><i>Openlib</i> is an open source app to download and read books from shadow library (<a href='https://annas-archive.org/' target='_blank' rel='nofollow noopener'>Anna’s Archive</a>). The App Has Built In Reader to Read Books.</p><p>As <i>Anna’s Archive</i> doesn't have an API, the app works by sending requests to <i>Anna’s Archive</i> and parses the response to objects. The app extracts the mirrors from the responses, downloads the book and stores it in the application's document directory.</p><p>Features include a.o.:</p><ul><li>Trending Books</li><li>Download And Read Books With In-Built Viewer</li><li>Supports Epub And Pdf Formats</li><li>Filter Books</li><li>Sort Books</li></ul>
|
||||
<p>
|
||||
<b><i>Openlib</i></b> is an open source app to download and read books from shadow library (<a href='https://annas-archive.org/' target='_blank' rel='nofollow noopener'>Anna’s Archive</a>). The App Has Built In Reader to Read Books.
|
||||
</p>
|
||||
<p>
|
||||
As <i>Anna’s Archive</i> doesn't have an API, the app works by sending requests to <i>Anna’s Archive</i> and parses the response to objects. The app extracts the mirrors from the responses, downloads the book and stores it in the application's document directory.
|
||||
</p>
|
||||
<p>
|
||||
<b>Note :</b>
|
||||
The app requires VPN to function properly . Without VPN the might show the captcha required page even after completing the captcha
|
||||
</p>
|
||||
<b><p>Main Features:</p></b>
|
||||
<ul>
|
||||
<li>Trending Books</li>
|
||||
<li>Download And Read Books With In-Built Viewer</li>
|
||||
<li>Supports Epub And Pdf Formats</li>
|
||||
<li>Open Books With Your Favourite Ebooks Reader</li>
|
||||
<li>Filter Books</li>
|
||||
<li>Sort Books</li>
|
||||
</ul>
|
Before Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 447 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 106 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 81 KiB |
Before Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 271 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 28 KiB |
@ -21,6 +21,6 @@
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>11.0</string>
|
||||
<string>12.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -1 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
|
@ -1 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
|
44
ios/Podfile
Normal file
@ -0,0 +1,44 @@
|
||||
# Uncomment this line to define a global platform for your project
|
||||
platform :ios, '17.5'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
project 'Runner', {
|
||||
'Debug' => :debug,
|
||||
'Profile' => :release,
|
||||
'Release' => :release,
|
||||
}
|
||||
|
||||
def flutter_root
|
||||
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
|
||||
unless File.exist?(generated_xcode_build_settings_path)
|
||||
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
|
||||
end
|
||||
|
||||
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||
return matches[1].strip if matches
|
||||
end
|
||||
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
|
||||
end
|
||||
|
||||
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||
|
||||
flutter_ios_podfile_setup
|
||||
|
||||
target 'Runner' do
|
||||
use_frameworks!
|
||||
use_modular_headers!
|
||||
|
||||
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
||||
target 'RunnerTests' do
|
||||
inherit! :search_paths
|
||||
end
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
flutter_additional_ios_build_settings(target)
|
||||
end
|
||||
end
|
@ -8,12 +8,14 @@
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||
99F2EC05C18FD14DB9B78A3C /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92BCBB6C991B9E7E482BC7E7 /* Pods_RunnerTests.framework */; };
|
||||
F7A920F49894E8110281C91E /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9508DF1D557B53054087AA54 /* Pods_Runner.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@ -40,12 +42,20 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
04DC994617040B23FC529786 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||
174C5E8BD4E5C1060E857A18 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
4CDDAA3FD6EA8176EA604F15 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||
92BCBB6C991B9E7E482BC7E7 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
9427EBEA20C8F5217BB45B75 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
9508DF1D557B53054087AA54 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@ -53,21 +63,47 @@
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
A9A04AD6D8CB2C21763ACD37 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
AD0519E0F1BE5DC9B0769918 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
880098128544877A82294A2B /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
99F2EC05C18FD14DB9B78A3C /* Pods_RunnerTests.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F7A920F49894E8110281C91E /* Pods_Runner.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
331C8082294A63A400263BE5 /* RunnerTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */,
|
||||
);
|
||||
path = RunnerTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
38B0FC331CEB9CEE8EE8FB26 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9508DF1D557B53054087AA54 /* Pods_Runner.framework */,
|
||||
92BCBB6C991B9E7E482BC7E7 /* Pods_RunnerTests.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -79,14 +115,6 @@
|
||||
name = Flutter;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
331C8082294A63A400263BE5 /* RunnerTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */,
|
||||
);
|
||||
path = RunnerTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146E51CF9000F007C117D = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -94,6 +122,8 @@
|
||||
97C146F01CF9000F007C117D /* Runner */,
|
||||
97C146EF1CF9000F007C117D /* Products */,
|
||||
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||
FFF5F42F3FF5E81E78DBDB29 /* Pods */,
|
||||
38B0FC331CEB9CEE8EE8FB26 /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@ -121,6 +151,20 @@
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FFF5F42F3FF5E81E78DBDB29 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4CDDAA3FD6EA8176EA604F15 /* Pods-Runner.debug.xcconfig */,
|
||||
AD0519E0F1BE5DC9B0769918 /* Pods-Runner.release.xcconfig */,
|
||||
04DC994617040B23FC529786 /* Pods-Runner.profile.xcconfig */,
|
||||
174C5E8BD4E5C1060E857A18 /* Pods-RunnerTests.debug.xcconfig */,
|
||||
A9A04AD6D8CB2C21763ACD37 /* Pods-RunnerTests.release.xcconfig */,
|
||||
9427EBEA20C8F5217BB45B75 /* Pods-RunnerTests.profile.xcconfig */,
|
||||
);
|
||||
name = Pods;
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@ -128,9 +172,10 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||
buildPhases = (
|
||||
49BEA72170219CCC4D9C1F17 /* [CP] Check Pods Manifest.lock */,
|
||||
331C807D294A63A400263BE5 /* Sources */,
|
||||
331C807E294A63A400263BE5 /* Frameworks */,
|
||||
331C807F294A63A400263BE5 /* Resources */,
|
||||
880098128544877A82294A2B /* Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@ -146,12 +191,15 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
A43F89B6CC892B4CB7855C7C /* [CP] Check Pods Manifest.lock */,
|
||||
9740EEB61CF901F6004384FC /* Run Script */,
|
||||
97C146EA1CF9000F007C117D /* Sources */,
|
||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||
97C146EC1CF9000F007C117D /* Resources */,
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
3EF15D019E8D6678261BD001 /* [CP] Embed Pods Frameworks */,
|
||||
A2C021D5FEBCC83DD3A3B38A /* [CP] Copy Pods Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@ -168,7 +216,7 @@
|
||||
97C146E61CF9000F007C117D /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1300;
|
||||
LastUpgradeCheck = 1510;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
331C8080294A63A400263BE5 = {
|
||||
@ -238,6 +286,45 @@
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||
};
|
||||
3EF15D019E8D6678261BD001 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
49BEA72170219CCC4D9C1F17 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
@ -253,6 +340,45 @@
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||
};
|
||||
A2C021D5FEBCC83DD3A3B38A /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
A43F89B6CC892B4CB7855C7C /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
@ -344,7 +470,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
@ -376,7 +502,7 @@
|
||||
};
|
||||
331C8088294A63A400263BE5 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = AE0B7B92F70575B8D7E0D07E /* Pods-RunnerTests.debug.xcconfig */;
|
||||
baseConfigurationReference = 174C5E8BD4E5C1060E857A18 /* Pods-RunnerTests.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
@ -394,7 +520,7 @@
|
||||
};
|
||||
331C8089294A63A400263BE5 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 89B67EB44CE7B6631473024E /* Pods-RunnerTests.release.xcconfig */;
|
||||
baseConfigurationReference = A9A04AD6D8CB2C21763ACD37 /* Pods-RunnerTests.release.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
@ -410,7 +536,7 @@
|
||||
};
|
||||
331C808A294A63A400263BE5 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 640959BDD8F10B91D80A66BE /* Pods-RunnerTests.profile.xcconfig */;
|
||||
baseConfigurationReference = 9427EBEA20C8F5217BB45B75 /* Pods-RunnerTests.profile.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
@ -471,7 +597,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
@ -520,7 +646,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1300"
|
||||
LastUpgradeVersion = "1510"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
3
ios/Runner.xcworkspace/contents.xcworkspacedata
generated
@ -4,4 +4,7 @@
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import UIKit
|
||||
import Flutter
|
||||
|
||||
@UIApplicationMain
|
||||
@main
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
|
101
lib/main.dart
@ -1,15 +1,23 @@
|
||||
import 'package:flutter/material.dart';
|
||||
// Dart imports:
|
||||
import 'dart:io' show Platform;
|
||||
|
||||
// Flutter imports:
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
// Package imports:
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:google_nav_bar/google_nav_bar.dart';
|
||||
import 'package:openlib/ui/home_page.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
|
||||
import 'package:openlib/ui/themes.dart';
|
||||
import 'package:openlib/ui/trending_page.dart';
|
||||
import 'package:openlib/ui/search_page.dart';
|
||||
// Project imports:
|
||||
import 'package:openlib/services/database.dart' show MyLibraryDb;
|
||||
import 'package:openlib/ui/mylibrary_page.dart';
|
||||
import 'package:openlib/ui/search_page.dart';
|
||||
import 'package:openlib/ui/settings_page.dart';
|
||||
import 'package:openlib/services/database.dart' show Sqlite, MyLibraryDb;
|
||||
import 'package:openlib/ui/themes.dart';
|
||||
|
||||
import 'package:openlib/services/files.dart'
|
||||
show moveFilesToAndroidInternalStorage;
|
||||
import 'package:openlib/state/state.dart'
|
||||
@ -18,7 +26,8 @@ import 'package:openlib/state/state.dart'
|
||||
themeModeProvider,
|
||||
openPdfWithExternalAppProvider,
|
||||
openEpubWithExternalAppProvider,
|
||||
dbProvider;
|
||||
userAgentProvider,
|
||||
cookieProvider;
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
@ -28,28 +37,45 @@ void main() async {
|
||||
databaseFactory = databaseFactoryFfi;
|
||||
}
|
||||
|
||||
Database initDb = await Sqlite.initDb();
|
||||
MyLibraryDb dataBase = MyLibraryDb(dbInstance: initDb);
|
||||
bool isDarkMode = await dataBase.getPreference('darkMode');
|
||||
bool openPdfwithExternalapp =
|
||||
await dataBase.getPreference('openPdfwithExternalApp');
|
||||
bool openEpubwithExternalapp =
|
||||
await dataBase.getPreference('openEpubwithExternalApp');
|
||||
MyLibraryDb dataBase = MyLibraryDb.instance;
|
||||
bool isDarkMode =
|
||||
await dataBase.getPreference('darkMode') == 0 ? false : true;
|
||||
bool openPdfwithExternalapp = await dataBase
|
||||
.getPreference('openPdfwithExternalApp')
|
||||
.catchError((e) => print(e)) ==
|
||||
0
|
||||
? false
|
||||
: true;
|
||||
|
||||
bool openEpubwithExternalapp = await dataBase
|
||||
.getPreference('openEpubwithExternalApp')
|
||||
.catchError((e) => print(e)) ==
|
||||
0
|
||||
? false
|
||||
: true;
|
||||
|
||||
String browserUserAgent = await dataBase.getBrowserOptions('userAgent');
|
||||
String browserCookie = await dataBase.getBrowserOptions('cookie');
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
//[SystemChrome] Also change colors in settings page Theme colors if any change
|
||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||
systemNavigationBarColor:
|
||||
isDarkMode ? Colors.black : Colors.grey.shade200));
|
||||
await moveFilesToAndroidInternalStorage();
|
||||
}
|
||||
|
||||
runApp(
|
||||
ProviderScope(
|
||||
overrides: [
|
||||
dbProvider.overrideWithValue(dataBase),
|
||||
themeModeProvider.overrideWith(
|
||||
(ref) => isDarkMode ? ThemeMode.dark : ThemeMode.light),
|
||||
openPdfWithExternalAppProvider
|
||||
.overrideWith((ref) => openPdfwithExternalapp),
|
||||
openEpubWithExternalAppProvider
|
||||
.overrideWith((ref) => openEpubwithExternalapp)
|
||||
.overrideWith((ref) => openEpubwithExternalapp),
|
||||
userAgentProvider.overrideWith((ref) => browserUserAgent),
|
||||
cookieProvider.overrideWith((ref) => browserCookie),
|
||||
],
|
||||
child: const MyApp(),
|
||||
),
|
||||
@ -64,7 +90,7 @@ class MyApp extends ConsumerWidget {
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
return MediaQuery(
|
||||
data: MediaQuery.of(context).copyWith(
|
||||
textScaleFactor: 1.0,
|
||||
textScaler: const TextScaler.linear(1.0),
|
||||
),
|
||||
child: child!,
|
||||
);
|
||||
@ -74,21 +100,21 @@ class MyApp extends ConsumerWidget {
|
||||
theme: lightTheme,
|
||||
darkTheme: darkTheme,
|
||||
themeMode: ref.watch(themeModeProvider),
|
||||
home: const HomePage(),
|
||||
home: const MainScreen(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class HomePage extends ConsumerStatefulWidget {
|
||||
const HomePage({super.key});
|
||||
class MainScreen extends ConsumerStatefulWidget {
|
||||
const MainScreen({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<ConsumerStatefulWidget> createState() => _HomePageState();
|
||||
ConsumerState<ConsumerStatefulWidget> createState() => _MainScreenState();
|
||||
}
|
||||
|
||||
class _HomePageState extends ConsumerState<HomePage> {
|
||||
class _MainScreenState extends ConsumerState<MainScreen> {
|
||||
static const List<Widget> _widgetOptions = <Widget>[
|
||||
TrendingPage(),
|
||||
HomePage(),
|
||||
SearchPage(),
|
||||
MyLibraryPage(),
|
||||
SettingsPage()
|
||||
@ -96,39 +122,40 @@ class _HomePageState extends ConsumerState<HomePage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
final selectedIndex = ref.watch(selectedIndexProvider);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
title: const Text("Openlib"),
|
||||
titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
),
|
||||
body: _widgetOptions.elementAt(selectedIndex),
|
||||
bottomNavigationBar: SafeArea(
|
||||
child: GNav(
|
||||
rippleColor: Colors.redAccent,
|
||||
backgroundColor: Colors.black,
|
||||
backgroundColor: isDarkMode ? Colors.black : Colors.grey.shade200,
|
||||
haptic: true,
|
||||
tabBorderRadius: 50,
|
||||
tabActiveBorder: Border.all(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
tabMargin: const EdgeInsets.fromLTRB(13, 6, 13, 2.5),
|
||||
curve: Curves.easeIn,
|
||||
duration: const Duration(milliseconds: 125),
|
||||
curve: Curves.fastLinearToSlowEaseIn,
|
||||
duration: const Duration(milliseconds: 25),
|
||||
gap: 5,
|
||||
color: const Color.fromARGB(255, 255, 255, 255),
|
||||
activeColor: const Color.fromARGB(255, 255, 255, 255),
|
||||
iconSize: 19, // tab button icon size
|
||||
tabBackgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 13, vertical: 6.5),
|
||||
tabs: const [
|
||||
tabs: [
|
||||
GButton(
|
||||
icon: Icons.trending_up,
|
||||
text: 'Trending',
|
||||
iconColor: Colors.white,
|
||||
textStyle: TextStyle(
|
||||
text: 'Home',
|
||||
iconColor: isDarkMode ? Colors.white : Colors.black,
|
||||
textStyle: const TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.white,
|
||||
fontSize: 11,
|
||||
@ -137,8 +164,8 @@ class _HomePageState extends ConsumerState<HomePage> {
|
||||
GButton(
|
||||
icon: Icons.search,
|
||||
text: 'Search',
|
||||
iconColor: Colors.white,
|
||||
textStyle: TextStyle(
|
||||
iconColor: isDarkMode ? Colors.white : Colors.black,
|
||||
textStyle: const TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.white,
|
||||
fontSize: 11,
|
||||
@ -147,8 +174,8 @@ class _HomePageState extends ConsumerState<HomePage> {
|
||||
GButton(
|
||||
icon: Icons.collections_bookmark,
|
||||
text: 'My Library',
|
||||
iconColor: Colors.white,
|
||||
textStyle: TextStyle(
|
||||
iconColor: isDarkMode ? Colors.white : Colors.black,
|
||||
textStyle: const TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.white,
|
||||
fontSize: 11,
|
||||
@ -157,8 +184,8 @@ class _HomePageState extends ConsumerState<HomePage> {
|
||||
GButton(
|
||||
icon: Icons.build,
|
||||
text: 'Settings',
|
||||
iconColor: Colors.white,
|
||||
textStyle: TextStyle(
|
||||
iconColor: isDarkMode ? Colors.white : Colors.black,
|
||||
textStyle: const TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.white,
|
||||
fontSize: 11,
|
||||
|
@ -1,5 +1,8 @@
|
||||
import 'package:dio/dio.dart';
|
||||
// Flutter imports:
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// Package imports:
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:html/parser.dart' show parse;
|
||||
|
||||
class BookData {
|
||||
@ -22,49 +25,42 @@ class BookData {
|
||||
}
|
||||
|
||||
class BookInfoData extends BookData {
|
||||
final List<String>? mirrors;
|
||||
String? mirror;
|
||||
final String? description;
|
||||
final String? format;
|
||||
|
||||
BookInfoData(
|
||||
{required String title,
|
||||
required String? author,
|
||||
required String? thumbnail,
|
||||
required String? publisher,
|
||||
required String? info,
|
||||
required String link,
|
||||
required String md5,
|
||||
required this.mirrors,
|
||||
{required super.title,
|
||||
required super.author,
|
||||
required super.thumbnail,
|
||||
required super.publisher,
|
||||
required super.info,
|
||||
required super.link,
|
||||
required super.md5,
|
||||
required this.format,
|
||||
required this.description})
|
||||
: super(
|
||||
title: title,
|
||||
author: author,
|
||||
thumbnail: thumbnail,
|
||||
publisher: publisher,
|
||||
info: info,
|
||||
link: link,
|
||||
md5: md5);
|
||||
required this.mirror,
|
||||
required this.description});
|
||||
}
|
||||
|
||||
class AnnasArchieve {
|
||||
String baseUrl = "https://annas-archive.gs";
|
||||
static const String baseUrl = "https://annas-archive.org";
|
||||
|
||||
final Dio dio = Dio(BaseOptions(headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/7.0.4 Mobile/16B91 Safari/605.1.15'
|
||||
}));
|
||||
final Dio dio = Dio();
|
||||
|
||||
Map<String, dynamic> defaultDioHeaders = {
|
||||
"user-agent":
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36",
|
||||
};
|
||||
|
||||
String getMd5(String url) {
|
||||
String md5 = url.toString().split('/').last;
|
||||
return md5;
|
||||
}
|
||||
|
||||
List<BookData> _parser(resData) {
|
||||
List<BookData> _parser(resData, String fileType) {
|
||||
var document =
|
||||
parse(resData.toString().replaceAll(RegExp(r"<!--|-->"), ''));
|
||||
var books = document.querySelectorAll(
|
||||
'a[class="js-vim-focus custom-a flex items-center relative left-[-10px] w-[calc(100%+20px)] px-[10px] py-2 outline-offset-[-2px] outline-2 rounded-[3px] hover:bg-[#00000011] focus:outline "]');
|
||||
var books = document.querySelectorAll('a');
|
||||
|
||||
List<BookData> bookList = [];
|
||||
|
||||
@ -73,21 +69,32 @@ class AnnasArchieve {
|
||||
'title': element.querySelector('h3')?.text,
|
||||
'thumbnail': element.querySelector('img')?.attributes['src'],
|
||||
'link': element.attributes['href'],
|
||||
'author': element.querySelector('div[class="truncate italic"]')?.text ??
|
||||
'author': element
|
||||
.querySelector(
|
||||
'div[class="max-lg:line-clamp-[2] lg:truncate leading-[1.2] lg:leading-[1.35] max-lg:text-sm italic"]')
|
||||
?.text ??
|
||||
'unknown',
|
||||
'publisher':
|
||||
element.querySelector('div[class="truncate text-sm"]')?.text ??
|
||||
"unknown",
|
||||
'publisher': element
|
||||
.querySelector(
|
||||
'div[class="truncate leading-[1.2] lg:leading-[1.35] max-lg:text-xs"]')
|
||||
?.text ??
|
||||
"unknown",
|
||||
'info': element
|
||||
.querySelector('div[class="truncate text-xs text-gray-500"]')
|
||||
.querySelector(
|
||||
'div[class="line-clamp-[2] leading-[1.2] text-[10px] lg:text-xs text-gray-500"]')
|
||||
?.text ??
|
||||
''
|
||||
};
|
||||
|
||||
if ((data['title'] != null && data['title'] != '') &&
|
||||
(data['link'] != null && data['link'] != '') &&
|
||||
(data['info'] != null &&
|
||||
(data['info']!.contains('pdf') ||
|
||||
data['info']!.contains('epub')))) {
|
||||
((fileType == "") &&
|
||||
(data['info']!.contains('pdf') ||
|
||||
data['info']!.contains('epub') ||
|
||||
data['info']!.contains('cbr') ||
|
||||
data['info']!.contains('cbz')) ||
|
||||
((fileType != "") && data['info']!.contains(fileType))))) {
|
||||
String link = baseUrl + data['link']!;
|
||||
String publisher = ((data['publisher']?.contains('0') == true &&
|
||||
data['publisher']!.length < 2) ||
|
||||
@ -115,48 +122,88 @@ class AnnasArchieve {
|
||||
if (info.contains('pdf') == true) {
|
||||
return 'pdf';
|
||||
} else {
|
||||
return 'epub';
|
||||
if (info.contains('cbr')) return "cbr";
|
||||
if (info.contains('cbz')) return "cbz";
|
||||
return "epub";
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> _getMirrorLink(String url) async {
|
||||
try {
|
||||
final response = await dio.get(url);
|
||||
var document = parse(response.toString());
|
||||
var pTag = document.querySelector('p[class="mb-4"]');
|
||||
String? link = pTag?.querySelector('a')?.attributes['href'];
|
||||
return link;
|
||||
} catch (e) {
|
||||
// print(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// Future<String?> _getMirrorLink(
|
||||
// String url, String userAgent, String cookie) async {
|
||||
// try {
|
||||
// final response = await dio.get(url,
|
||||
// options: Options(extra: {
|
||||
// 'withCredentials': true
|
||||
// }, headers: {
|
||||
// "Host": "annas-archive.org",
|
||||
// "Origin": baseUrl,
|
||||
// "Upgrade-Insecure-Requests": "1",
|
||||
// "Sec-Fetch-Dest": "secure",
|
||||
// "Sec-Fetch-Mode": "navigate",
|
||||
// "Sec-Fetch-Site": "same-site",
|
||||
// "Cookie": cookie,
|
||||
// "User-Agent": userAgent
|
||||
// }));
|
||||
|
||||
// var document = parse(response.data.toString());
|
||||
|
||||
// var pTag = document.querySelectorAll('p[class="mb-4"]');
|
||||
// String? link = pTag[1].querySelector('a')?.attributes['href'];
|
||||
// return link;
|
||||
// } catch (e) {
|
||||
// // print('${url} ${e}');
|
||||
// if (e.toString().contains("403")) {
|
||||
// throw jsonEncode({"code": "403", "url": url});
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
|
||||
Future<BookInfoData?> _bookInfoParser(resData, url) async {
|
||||
var document = parse(resData.toString());
|
||||
var main = document.querySelector('main[class="main"]');
|
||||
var ul = main?.querySelector('ul[class="mb-4"]');
|
||||
var ul = main?.querySelectorAll('ul[class="list-inside mb-4 ml-1"]');
|
||||
|
||||
List<String> mirrors = [];
|
||||
// List<String> mirrors = [];
|
||||
|
||||
// if (ul != null) {
|
||||
// var anchorTags = [];
|
||||
|
||||
// for (var e in ul) {
|
||||
// anchorTags.insertAll(0, e.querySelectorAll('a'));
|
||||
// }
|
||||
|
||||
// for (var element in anchorTags) {
|
||||
// if (element.attributes['href'] != null &&
|
||||
// element.attributes['href']!.startsWith('/slow_download') &&
|
||||
// element.attributes['href']!.endsWith('/2')) {
|
||||
// String? url =
|
||||
// await _getMirrorLink('$baseUrl${element.attributes['href']!}');
|
||||
// if (url != null && url.isNotEmpty) {
|
||||
// mirrors.add(url);
|
||||
// }
|
||||
// } else if (element.attributes['href']!.startsWith('https://')) {
|
||||
// if (element.attributes['href'] != null &&
|
||||
// element.attributes['href'].contains('ipfs') == true) {
|
||||
// mirrors.add(element.attributes['href']!);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
String? mirror;
|
||||
var anchorTags = [];
|
||||
|
||||
if (ul != null) {
|
||||
var a = ul.querySelectorAll('a');
|
||||
for (var e in ul) {
|
||||
anchorTags.insertAll(0, e.querySelectorAll('a'));
|
||||
}
|
||||
}
|
||||
|
||||
for (var element in a) {
|
||||
if (element.attributes['href']!.startsWith('https://')) {
|
||||
if (element.attributes['href'] != null) {
|
||||
mirrors.add(element.attributes['href']!);
|
||||
}
|
||||
} else if (element.attributes['href'] != null &&
|
||||
element.attributes['href']!.startsWith('/slow_download')) {
|
||||
if (element.text.contains('Slow Partner Server #1') != true) {
|
||||
String? url =
|
||||
await _getMirrorLink('$baseUrl${element.attributes['href']!}');
|
||||
if (url != null && url.isNotEmpty) {
|
||||
mirrors.add(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var element in anchorTags) {
|
||||
if (element.attributes['href'] != null &&
|
||||
element.attributes['href']!.startsWith('/slow_download') &&
|
||||
element.attributes['href']!.endsWith('/2')) {
|
||||
mirror = '$baseUrl${element.attributes['href']}';
|
||||
}
|
||||
}
|
||||
|
||||
@ -172,9 +219,9 @@ class AnnasArchieve {
|
||||
'info':
|
||||
main?.querySelector('div[class="text-sm text-gray-500"]')?.text ?? '',
|
||||
'description': main
|
||||
?.querySelector(
|
||||
'div[class="mt-4 line-clamp-[5] js-md5-top-box-description"]')
|
||||
?.text ??
|
||||
?.querySelector('div[class="mb-1"]')
|
||||
?.text
|
||||
.replaceFirst("description", '') ??
|
||||
" "
|
||||
};
|
||||
|
||||
@ -199,7 +246,7 @@ class AnnasArchieve {
|
||||
link: data['link'],
|
||||
md5: getMd5(data['link'].toString()),
|
||||
format: getFormat(data['info']),
|
||||
mirrors: mirrors,
|
||||
mirror: mirror,
|
||||
description: data['description'],
|
||||
);
|
||||
} else {
|
||||
@ -207,15 +254,40 @@ class AnnasArchieve {
|
||||
}
|
||||
}
|
||||
|
||||
String urlEncoder(
|
||||
{required String searchQuery,
|
||||
required String content,
|
||||
required String sort,
|
||||
required String fileType,
|
||||
required bool enableFilters}) {
|
||||
searchQuery = searchQuery.replaceAll(" ", "+");
|
||||
if (enableFilters == false) return '$baseUrl/search?q=$searchQuery';
|
||||
if (content == "" && sort == "" && fileType == "") {
|
||||
return '$baseUrl/search?q=$searchQuery';
|
||||
}
|
||||
return '$baseUrl/search?index=&q=$searchQuery&content=$content&ext=$fileType&sort=$sort';
|
||||
}
|
||||
|
||||
Future<List<BookData>> searchBooks(
|
||||
{required String searchQuery,
|
||||
String content = "",
|
||||
String sort = ""}) async {
|
||||
String sort = "",
|
||||
String fileType = "",
|
||||
bool enableFilters = true}) async {
|
||||
try {
|
||||
final response = await dio.get(
|
||||
'$baseUrl/search?lang=&content=$content&ext=&sort=$sort&q=$searchQuery',
|
||||
);
|
||||
return _parser(response.data);
|
||||
final String encodedURL = urlEncoder(
|
||||
searchQuery: searchQuery,
|
||||
content: content,
|
||||
sort: sort,
|
||||
fileType: fileType,
|
||||
enableFilters: enableFilters);
|
||||
|
||||
final response = await dio.get(encodedURL,
|
||||
options: Options(headers: defaultDioHeaders));
|
||||
if (!enableFilters) {
|
||||
return _parser(response.data, "");
|
||||
}
|
||||
return _parser(response.data, fileType);
|
||||
} on DioException catch (e) {
|
||||
if (e.type == DioExceptionType.unknown) {
|
||||
throw "socketException";
|
||||
@ -226,7 +298,8 @@ class AnnasArchieve {
|
||||
|
||||
Future<BookInfoData> bookInfo({required String url}) async {
|
||||
try {
|
||||
final response = await dio.get(url);
|
||||
final response =
|
||||
await dio.get(url, options: Options(headers: defaultDioHeaders));
|
||||
BookInfoData? data = await _bookInfoParser(response.data, url);
|
||||
if (data != null) {
|
||||
return data;
|
||||
|
@ -1,43 +1,11 @@
|
||||
// Dart imports:
|
||||
import 'dart:io';
|
||||
|
||||
// Package imports:
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
|
||||
class Sqlite {
|
||||
static Future<Database> initDb() async {
|
||||
var databasesPath = await getDatabasesPath();
|
||||
String path = '$databasesPath/mylibrary.db';
|
||||
bool isMobile = Platform.isAndroid || Platform.isIOS;
|
||||
|
||||
Database dbInstance = await openDatabase(
|
||||
path,
|
||||
version: 3,
|
||||
onCreate: (Database db, int version) async {
|
||||
await db.execute(
|
||||
'CREATE TABLE mybooks (id TEXT PRIMARY KEY, title TEXT,author TEXT,thumbnail TEXT,link TEXT,publisher TEXT,info TEXT,format TEXT,description TEXT)');
|
||||
await db.execute(
|
||||
'CREATE TABLE preferences (name TEXT PRIMARY KEY,value BOOLEAN)');
|
||||
if (isMobile) {
|
||||
await db.execute(
|
||||
'CREATE TABLE bookposition (fileName TEXT PRIMARY KEY,position TEXT)');
|
||||
}
|
||||
},
|
||||
onUpgrade: (db, oldVersion, newVersion) async {
|
||||
List<dynamic> isTableExist = await db.query('sqlite_master',
|
||||
where: 'name = ?', whereArgs: ['bookposition']);
|
||||
List<dynamic> isPreferenceTableExist = await db.query('sqlite_master',
|
||||
where: 'name = ?', whereArgs: ['preferences']);
|
||||
if (isPreferenceTableExist.isEmpty) {
|
||||
await db.execute(
|
||||
'CREATE TABLE preferences (name TEXT PRIMARY KEY,value BOOLEAN)');
|
||||
}
|
||||
if (isMobile && isTableExist.isEmpty) {
|
||||
await db.execute(
|
||||
'CREATE TABLE bookposition (fileName TEXT PRIMARY KEY,position TEXT)');
|
||||
}
|
||||
},
|
||||
);
|
||||
return dbInstance;
|
||||
}
|
||||
}
|
||||
// Project imports:
|
||||
import 'package:openlib/services/files.dart';
|
||||
|
||||
class MyBook {
|
||||
final String id;
|
||||
@ -82,12 +50,79 @@ class MyBook {
|
||||
}
|
||||
|
||||
class MyLibraryDb {
|
||||
Database dbInstance;
|
||||
static final MyLibraryDb instance = MyLibraryDb._internal();
|
||||
static Database? _database;
|
||||
MyLibraryDb._internal();
|
||||
|
||||
Future<Database> get database async {
|
||||
if (_database != null) {
|
||||
return _database!;
|
||||
}
|
||||
_database = await _initDatabase();
|
||||
return _database!;
|
||||
}
|
||||
|
||||
Future<Database> _initDatabase() async {
|
||||
final databasePath = await getDatabasesPath();
|
||||
final path = '$databasePath/mylibrary.db';
|
||||
final bool isMobile = Platform.isAndroid || Platform.isIOS;
|
||||
|
||||
return await openDatabase(
|
||||
path,
|
||||
version: 5,
|
||||
onCreate: (Database db, int version) async {
|
||||
await db.execute(
|
||||
'CREATE TABLE mybooks (id TEXT PRIMARY KEY, title TEXT,author TEXT,thumbnail TEXT,link TEXT,publisher TEXT,info TEXT,format TEXT,description TEXT)');
|
||||
await db.execute(
|
||||
'CREATE TABLE preferences (name TEXT PRIMARY KEY,value TEXT)');
|
||||
if (isMobile || true) {
|
||||
// TODO: Breaks getBrowserOptions() on Mac
|
||||
await db.execute(
|
||||
'CREATE TABLE bookposition (fileName TEXT PRIMARY KEY,position TEXT)');
|
||||
await db.execute(
|
||||
'CREATE TABLE browserOptions (name TEXT PRIMARY KEY,value TEXT)');
|
||||
}
|
||||
},
|
||||
onUpgrade: (db, oldVersion, newVersion) async {
|
||||
List<dynamic> isTableExist = await db.query('sqlite_master',
|
||||
where: 'name = ?', whereArgs: ['bookposition']);
|
||||
List<dynamic> isPreferenceTableExist = await db.query('sqlite_master',
|
||||
where: 'name = ?', whereArgs: ['preferences']);
|
||||
List<dynamic> isbrowserOptionsExist = await db.query('sqlite_master',
|
||||
where: 'name = ?', whereArgs: ['browserOptions']);
|
||||
if (isPreferenceTableExist.isEmpty) {
|
||||
await db.execute(
|
||||
'CREATE TABLE preferences (name TEXT PRIMARY KEY,value TEXT)');
|
||||
}
|
||||
if (isMobile && isTableExist.isEmpty) {
|
||||
await db.execute(
|
||||
'CREATE TABLE bookposition (fileName TEXT PRIMARY KEY,position TEXT)');
|
||||
}
|
||||
if (isMobile && isbrowserOptionsExist.isEmpty) {
|
||||
await db.execute(
|
||||
'CREATE TABLE browserOptions (name TEXT PRIMARY KEY,value TEXT)');
|
||||
}
|
||||
},
|
||||
onOpen: (db) async {
|
||||
final bookStorageDefaultDirectory =
|
||||
await getBookStorageDefaultDirectory;
|
||||
await db.execute(
|
||||
"INSERT OR IGNORE INTO preferences (name, value) VALUES ('darkMode', 0)");
|
||||
await db.execute(
|
||||
"INSERT OR IGNORE INTO preferences (name, value) VALUES ('openPdfwithExternalApp', 0)");
|
||||
await db.execute(
|
||||
"INSERT OR IGNORE INTO preferences (name, value) VALUES ('openEpubwithExternalApp', 0)");
|
||||
await db.execute(
|
||||
"INSERT OR IGNORE INTO preferences (name, value) VALUES ('bookStorageDirectory', '$bookStorageDefaultDirectory')");
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Database dbInstance;
|
||||
String tableName = 'mybooks';
|
||||
|
||||
MyLibraryDb({required this.dbInstance});
|
||||
|
||||
Future<void> insert(MyBook book) async {
|
||||
final dbInstance = await instance.database;
|
||||
await dbInstance.insert(
|
||||
tableName,
|
||||
book.toMap(),
|
||||
@ -96,6 +131,7 @@ class MyLibraryDb {
|
||||
}
|
||||
|
||||
Future<void> delete(String id) async {
|
||||
final dbInstance = await instance.database;
|
||||
await dbInstance.delete(
|
||||
tableName,
|
||||
where: 'id = ?',
|
||||
@ -104,6 +140,7 @@ class MyLibraryDb {
|
||||
}
|
||||
|
||||
Future<MyBook?> getId(String id) async {
|
||||
final dbInstance = await instance.database;
|
||||
List<Map<String, dynamic>> data =
|
||||
await dbInstance.query(tableName, where: 'id = ?', whereArgs: [id]);
|
||||
List<MyBook> book = listMapToMyBook(data);
|
||||
@ -114,6 +151,7 @@ class MyLibraryDb {
|
||||
}
|
||||
|
||||
Future<bool> checkIdExists(String id) async {
|
||||
final dbInstance = await instance.database;
|
||||
List<Map<String, dynamic>> data =
|
||||
await dbInstance.query(tableName, where: 'id = ?', whereArgs: [id]);
|
||||
List<MyBook> book = listMapToMyBook(data);
|
||||
@ -124,6 +162,7 @@ class MyLibraryDb {
|
||||
}
|
||||
|
||||
Future<List<MyBook>> getAll() async {
|
||||
final dbInstance = await instance.database;
|
||||
final List<Map<String, dynamic>> maps = await dbInstance.query(tableName);
|
||||
return listMapToMyBook(maps);
|
||||
}
|
||||
@ -145,6 +184,7 @@ class MyLibraryDb {
|
||||
}
|
||||
|
||||
Future<void> saveBookState(String fileName, String position) async {
|
||||
final dbInstance = await instance.database;
|
||||
await dbInstance.insert(
|
||||
'bookposition',
|
||||
{'fileName': fileName, 'position': position},
|
||||
@ -153,6 +193,7 @@ class MyLibraryDb {
|
||||
}
|
||||
|
||||
Future<void> deleteBookState(String fileName) async {
|
||||
final dbInstance = await instance.database;
|
||||
await dbInstance.delete(
|
||||
'bookposition',
|
||||
where: 'fileName = ?',
|
||||
@ -161,6 +202,7 @@ class MyLibraryDb {
|
||||
}
|
||||
|
||||
Future<String?> getBookState(String fileName) async {
|
||||
final dbInstance = await instance.database;
|
||||
List<Map<String, dynamic>> data = await dbInstance
|
||||
.query('bookposition', where: 'fileName = ?', whereArgs: [fileName]);
|
||||
List<dynamic> dataList = List.generate(data.length, (i) {
|
||||
@ -173,25 +215,63 @@ class MyLibraryDb {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> savePreference(String name, bool value) async {
|
||||
int boolInt = value ? 1 : 0;
|
||||
Future<void> savePreference(String name, dynamic value) async {
|
||||
switch (value.runtimeType) {
|
||||
case bool:
|
||||
value = value ? 1 : 0;
|
||||
break;
|
||||
case int || String:
|
||||
break;
|
||||
default:
|
||||
throw 'Invalid type';
|
||||
}
|
||||
Database dbInstance = await instance.database;
|
||||
await dbInstance.insert(
|
||||
'preferences',
|
||||
{'name': name, 'value': boolInt},
|
||||
{'name': name, 'value': value},
|
||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> getPreference(String name) async {
|
||||
Future<dynamic> getPreference(String name) async {
|
||||
Database dbInstance = await instance.database;
|
||||
List<Map<String, dynamic>> data = await dbInstance
|
||||
.query('preferences', where: 'name = ?', whereArgs: [name]);
|
||||
List<dynamic> dataList = List.generate(data.length, (i) {
|
||||
return {'name': data[i]['name'], 'value': data[i]['value']};
|
||||
});
|
||||
if (dataList.isNotEmpty) {
|
||||
return dataList[0]['value'] == 0 ? false : true;
|
||||
// Convert to int if possible
|
||||
int? preference = int.tryParse(dataList[0]['value']);
|
||||
if (preference != null) {
|
||||
return preference;
|
||||
}
|
||||
// Return string value if not int
|
||||
return dataList[0]['value'];
|
||||
}
|
||||
throw "Preference $name not found";
|
||||
}
|
||||
|
||||
Future<void> setBrowserOptions(String name, String value) async {
|
||||
final dbInstance = await instance.database;
|
||||
await dbInstance.insert(
|
||||
'browserOptions',
|
||||
{'name': name, 'value': value},
|
||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||
);
|
||||
}
|
||||
|
||||
Future<String> getBrowserOptions(String name) async {
|
||||
final dbInstance = await instance.database;
|
||||
List<Map<String, dynamic>> data = await dbInstance
|
||||
.query('browserOptions', where: 'name = ?', whereArgs: [name]);
|
||||
List<dynamic> dataList = List.generate(data.length, (i) {
|
||||
return {'name': data[i]['name'], 'value': data[i]['value']};
|
||||
});
|
||||
if (dataList.isNotEmpty) {
|
||||
return dataList[0]['value'];
|
||||
} else {
|
||||
return false;
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,19 @@
|
||||
// Dart imports:
|
||||
import 'dart:io';
|
||||
|
||||
// Package imports:
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'files.dart';
|
||||
|
||||
// Project imports:
|
||||
import 'package:openlib/services/database.dart' show MyLibraryDb;
|
||||
|
||||
MyLibraryDb dataBase = MyLibraryDb.instance;
|
||||
|
||||
Future<String> _getFilePath(String fileName) async {
|
||||
final path = await getAppDirectoryPath;
|
||||
return '$path/$fileName';
|
||||
String bookStorageDirectory =
|
||||
await dataBase.getPreference('bookStorageDirectory');
|
||||
return '$bookStorageDirectory/$fileName';
|
||||
}
|
||||
|
||||
List<String> _reorderMirrors(List<String> mirrors) {
|
||||
@ -14,7 +24,8 @@ List<String> _reorderMirrors(List<String> mirrors) {
|
||||
if (element.contains('ipfs') == true) {
|
||||
ipfsMirrors.add(element);
|
||||
} else {
|
||||
if (element.startsWith('https://annas-archive.gs') != true) {
|
||||
if (element.startsWith('https://annas-archive.org') != true &&
|
||||
element.startsWith('https://1lib.sk') != true) {
|
||||
httpsMirrors.add(element);
|
||||
}
|
||||
}
|
||||
@ -22,45 +33,100 @@ List<String> _reorderMirrors(List<String> mirrors) {
|
||||
return [...ipfsMirrors, ...httpsMirrors];
|
||||
}
|
||||
|
||||
Future<String?> _getAliveMirror(List<String> mirrors) async {
|
||||
Dio dio = Dio();
|
||||
for (var url in mirrors) {
|
||||
try {
|
||||
final response = await dio.head(url,
|
||||
options: Options(receiveTimeout: const Duration(seconds: 10)));
|
||||
if (response.statusCode == 200) {
|
||||
dio.close();
|
||||
return url;
|
||||
}
|
||||
} catch (_) {
|
||||
// print("timeOut");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> downloadFile(
|
||||
{required List<String> mirrors,
|
||||
required String md5,
|
||||
required String format,
|
||||
required Function onStart,
|
||||
required Function onProgress,
|
||||
required Function cancelDownlaod,
|
||||
required Function mirrorStatus,
|
||||
required Function onDownlaodFailed}) async {
|
||||
Dio dio = Dio();
|
||||
String path = await _getFilePath('$md5.$format');
|
||||
List<String> orderedMirrors = _reorderMirrors(mirrors);
|
||||
if (mirrors.isEmpty) {
|
||||
onDownlaodFailed('No mirrors available!');
|
||||
} else {
|
||||
Dio dio = Dio();
|
||||
|
||||
// print(path);
|
||||
// print(orderedMirrors);
|
||||
String path = await _getFilePath('$md5.$format');
|
||||
List<String> orderedMirrors = _reorderMirrors(mirrors);
|
||||
|
||||
try {
|
||||
CancelToken cancelToken = CancelToken();
|
||||
String? workingMirror = await _getAliveMirror(orderedMirrors);
|
||||
|
||||
dio.download(
|
||||
orderedMirrors[0],
|
||||
path,
|
||||
options: Options(headers: {
|
||||
'Connection': 'Keep-Alive',
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36'
|
||||
}),
|
||||
onReceiveProgress: (rcv, total) {
|
||||
onProgress(rcv, total);
|
||||
},
|
||||
deleteOnError: true,
|
||||
cancelToken: cancelToken,
|
||||
).catchError((err) {
|
||||
if (err.type != DioExceptionType.cancel) {
|
||||
onDownlaodFailed();
|
||||
// print(workingMirror);
|
||||
// print(path);
|
||||
// print(orderedMirrors);
|
||||
// print(orderedMirrors[0]);
|
||||
|
||||
if (workingMirror != null) {
|
||||
onStart();
|
||||
try {
|
||||
CancelToken cancelToken = CancelToken();
|
||||
dio.download(
|
||||
workingMirror,
|
||||
path,
|
||||
options: Options(headers: {
|
||||
'Connection': 'Keep-Alive',
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36'
|
||||
}),
|
||||
onReceiveProgress: (rcv, total) {
|
||||
if (!(rcv.isNaN || rcv.isInfinite) &&
|
||||
!(total.isNaN || total.isInfinite)) {
|
||||
onProgress(rcv, total);
|
||||
}
|
||||
},
|
||||
deleteOnError: true,
|
||||
cancelToken: cancelToken,
|
||||
).catchError((err) {
|
||||
if (err.type != DioExceptionType.cancel) {
|
||||
onDownlaodFailed('downloaded Failed! try again...');
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
|
||||
mirrorStatus(true);
|
||||
|
||||
cancelDownlaod(cancelToken);
|
||||
} catch (_) {
|
||||
onDownlaodFailed('downloaded Failed! try again...');
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
|
||||
cancelDownlaod(cancelToken);
|
||||
} catch (e) {
|
||||
onDownlaodFailed();
|
||||
} else {
|
||||
onDownlaodFailed('No working mirrors available to download book!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> verifyFileCheckSum(
|
||||
{required String md5Hash, required String format}) async {
|
||||
try {
|
||||
final bookStorageDirectory =
|
||||
await dataBase.getPreference('bookStorageDirectory');
|
||||
final filePath = '$bookStorageDirectory/$md5Hash.$format';
|
||||
final file = File(filePath);
|
||||
final stream = file.openRead();
|
||||
final hash = await md5.bind(stream).first;
|
||||
if (md5Hash == hash.toString()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,17 @@
|
||||
// Dart imports:
|
||||
import 'dart:io';
|
||||
|
||||
// Package imports:
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:openlib/state/state.dart' show dbProvider, myLibraryProvider;
|
||||
|
||||
Future<String> get getAppDirectoryPath async {
|
||||
// Project imports:
|
||||
import 'package:openlib/services/database.dart';
|
||||
import 'package:openlib/state/state.dart' show myLibraryProvider;
|
||||
|
||||
MyLibraryDb dataBase = MyLibraryDb.instance;
|
||||
|
||||
Future<String> get getBookStorageDefaultDirectory async {
|
||||
if (Platform.isAndroid) {
|
||||
final directory = await getExternalStorageDirectory();
|
||||
return directory!.path;
|
||||
@ -32,6 +40,23 @@ Future<void> moveFilesToAndroidInternalStorage() async {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> moveFolderContents(
|
||||
String source_path, String destination_path) async {
|
||||
final source = Directory(source_path);
|
||||
source.listSync(recursive: false).forEach((var entity) {
|
||||
if (entity is Directory) {
|
||||
var newDirectory =
|
||||
Directory('${destination_path}/${entity.path.split('/').last}');
|
||||
newDirectory.createSync();
|
||||
moveFolderContents(entity.path, newDirectory.path);
|
||||
entity.deleteSync();
|
||||
} else if (entity is File) {
|
||||
entity.copySync('${destination_path}/${entity.path.split('/').last}');
|
||||
entity.deleteSync();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<bool> isFileExists(String filePath) async {
|
||||
return await File(filePath).exists();
|
||||
}
|
||||
@ -43,8 +68,9 @@ Future<void> deleteFile(String filePath) async {
|
||||
}
|
||||
|
||||
Future<String> getFilePath(String fileName) async {
|
||||
String appDirPath = await getAppDirectoryPath;
|
||||
String filePath = '$appDirPath/$fileName';
|
||||
final bookStorageDirectory =
|
||||
await dataBase.getPreference('bookStorageDirectory');
|
||||
String filePath = '$bookStorageDirectory/$fileName';
|
||||
bool isExists = await isFileExists(filePath);
|
||||
if (isExists == true) {
|
||||
return filePath;
|
||||
@ -56,10 +82,11 @@ Future<void> deleteFileWithDbData(
|
||||
FutureProviderRef ref, String md5, String format) async {
|
||||
try {
|
||||
String fileName = '$md5.$format';
|
||||
String appDirPath = await getAppDirectoryPath;
|
||||
await deleteFile('$appDirPath/$fileName');
|
||||
await ref.read(dbProvider).delete(md5);
|
||||
await ref.read(dbProvider).deleteBookState(fileName);
|
||||
final bookStorageDirectory =
|
||||
await dataBase.getPreference('bookStorageDirectory');
|
||||
await deleteFile('$bookStorageDirectory/$fileName');
|
||||
await dataBase.delete(md5);
|
||||
await dataBase.deleteBookState(fileName);
|
||||
// ignore: unused_result
|
||||
ref.refresh(myLibraryProvider);
|
||||
} catch (e) {
|
||||
|
77
lib/services/goodreads.dart
Normal file
@ -0,0 +1,77 @@
|
||||
// Package imports:
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:html/parser.dart' show parse;
|
||||
|
||||
class CategoryBookData {
|
||||
final String? title;
|
||||
final String? thumbnail;
|
||||
final String? link;
|
||||
CategoryBookData({this.title, this.thumbnail, this.link});
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other is CategoryBookData &&
|
||||
other.title == title &&
|
||||
other.thumbnail == thumbnail &&
|
||||
other.link == link;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return title.hashCode ^ thumbnail.hashCode ^ link.hashCode; // Using XOR to combine hash codes
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
String baseUrl = "https://www.goodreads.com/";
|
||||
|
||||
class SubCategoriesTypeList {
|
||||
List<CategoryBookData>_parser(data) {
|
||||
var document = parse(data.toString());
|
||||
var categoryList = document.querySelectorAll('.listImgs a');
|
||||
List<CategoryBookData> categoriesBooks = [];
|
||||
for (var element in categoryList) {
|
||||
if (element.querySelector('img"]')?.attributes['title'] != null &&
|
||||
element.querySelector('img')?.attributes['src'] != null) {
|
||||
String? title = element.querySelector('img')?.attributes['title'];
|
||||
String? thumbnail = element.querySelector('img')?.attributes['src'];
|
||||
String? link = element.querySelector('a')?.attributes['herf']!;
|
||||
categoriesBooks.add( CategoryBookData(
|
||||
title: title
|
||||
.toString()
|
||||
.trim(),
|
||||
thumbnail: thumbnail
|
||||
.toString()
|
||||
.replaceAll("._SY75_.", "._SY225_.")
|
||||
.replaceAll("._SX50_.", "._SX148_."),
|
||||
link : link
|
||||
.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
return categoriesBooks;
|
||||
}
|
||||
|
||||
Future<List<CategoryBookData>> categoriesBooks({required String url}) async {
|
||||
try {
|
||||
final dio = Dio();
|
||||
final response = await dio.get('$baseUrl$url',
|
||||
options: Options(
|
||||
sendTimeout: const Duration(seconds: 20),
|
||||
receiveTimeout: const Duration(seconds: 20)));
|
||||
final response1 = await dio.get('$baseUrl$url?page=2',
|
||||
options: Options(
|
||||
sendTimeout: const Duration(seconds: 20),
|
||||
receiveTimeout: const Duration(seconds: 20)));
|
||||
return _parser('${response.data.toString()}${response1.data.toString()}');
|
||||
} on DioException catch (e) {
|
||||
if (e.type == DioExceptionType.unknown) {
|
||||
throw "socketException";
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
// Package imports:
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:html/parser.dart' show parse;
|
||||
|
||||
@ -7,12 +8,35 @@ class TrendingBookData {
|
||||
TrendingBookData({this.title, this.thumbnail});
|
||||
}
|
||||
|
||||
class OpenLibrary {
|
||||
String url = "https://openlibrary.org/trending/daily";
|
||||
abstract class TrendingBooksImpl {
|
||||
String url = '';
|
||||
int timeOutDuration = 20;
|
||||
List<TrendingBookData> _parser(dynamic data);
|
||||
|
||||
Future<List<TrendingBookData>> trendingBooks() async {
|
||||
try {
|
||||
final dio = Dio();
|
||||
final response = await dio.get(url,
|
||||
options: Options(
|
||||
sendTimeout: Duration(seconds: timeOutDuration),
|
||||
receiveTimeout: Duration(seconds: timeOutDuration)));
|
||||
return _parser(response.data.toString());
|
||||
} on DioException catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class OpenLibrary extends TrendingBooksImpl {
|
||||
OpenLibrary() {
|
||||
super.url = "https://openlibrary.org/trending/daily";
|
||||
}
|
||||
|
||||
@override
|
||||
List<TrendingBookData> _parser(data) {
|
||||
var document = parse(data.toString());
|
||||
var bookList = document.querySelectorAll('li[class="searchResultItem"]');
|
||||
var bookList =
|
||||
document.querySelectorAll('li[class="searchResultItem sri--w-main"]');
|
||||
List<TrendingBookData> trendingBooks = [];
|
||||
for (var element in bookList) {
|
||||
if (element.querySelector('h3[class="booktitle"]')?.text != null &&
|
||||
@ -31,24 +55,123 @@ class OpenLibrary {
|
||||
return trendingBooks;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<TrendingBookData>> trendingBooks() async {
|
||||
try {
|
||||
final dio = Dio();
|
||||
const timeOutDuration = 5;
|
||||
final response = await dio.get(url,
|
||||
options: Options(
|
||||
sendTimeout: const Duration(seconds: 20),
|
||||
receiveTimeout: const Duration(seconds: 20)));
|
||||
sendTimeout: const Duration(seconds: timeOutDuration),
|
||||
receiveTimeout: const Duration(seconds: timeOutDuration)));
|
||||
final response2 = await dio.get(
|
||||
"https://openlibrary.org/trending/daily?page=2",
|
||||
options: Options(
|
||||
sendTimeout: const Duration(seconds: 20),
|
||||
receiveTimeout: const Duration(seconds: 20)));
|
||||
sendTimeout: const Duration(seconds: timeOutDuration),
|
||||
receiveTimeout: const Duration(seconds: timeOutDuration)));
|
||||
return _parser('${response.data.toString()}${response2.data.toString()}');
|
||||
} on DioException catch (e) {
|
||||
if (e.type == DioExceptionType.unknown) {
|
||||
throw "socketException";
|
||||
}
|
||||
rethrow;
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GoodReads extends TrendingBooksImpl {
|
||||
GoodReads() {
|
||||
super.url = "https://www.goodreads.com/shelf/show/trending";
|
||||
}
|
||||
|
||||
@override
|
||||
List<TrendingBookData> _parser(data) {
|
||||
var document = parse(data.toString());
|
||||
var bookList = document.querySelectorAll('div[class="elementList"]');
|
||||
List<TrendingBookData> trendingBooks = [];
|
||||
for (var element in bookList) {
|
||||
if (element
|
||||
.querySelector('a[class="leftAlignedImage"]')
|
||||
?.attributes['title'] !=
|
||||
null &&
|
||||
element.querySelector('img')?.attributes['src'] != null) {
|
||||
String? thumbnail = element.querySelector('img')?.attributes['src'];
|
||||
trendingBooks.add(
|
||||
TrendingBookData(
|
||||
title: element
|
||||
.querySelector('a[class="leftAlignedImage"]')
|
||||
?.attributes['title']
|
||||
.toString()
|
||||
.trim(),
|
||||
thumbnail: thumbnail
|
||||
.toString()
|
||||
.replaceAll("._SY75_.", "._SY225_.")
|
||||
.replaceAll("._SX50_.", "._SX148_.")),
|
||||
);
|
||||
}
|
||||
}
|
||||
return trendingBooks;
|
||||
}
|
||||
}
|
||||
|
||||
class PenguinRandomHouse extends TrendingBooksImpl {
|
||||
PenguinRandomHouse() {
|
||||
super.url =
|
||||
"https://www.penguinrandomhouse.com/ajaxc/categories/books/?from=0&to=50&contentId=&elClass=book&dataType=html&catFilter=best-sellers";
|
||||
}
|
||||
|
||||
@override
|
||||
List<TrendingBookData> _parser(data) {
|
||||
var document = parse(data.toString());
|
||||
var bookList = document.querySelectorAll('div[class="book"]');
|
||||
List<TrendingBookData> trendingBooks = [];
|
||||
for (var element in bookList) {
|
||||
if (element.querySelector('div[class="title"]')?.text != null &&
|
||||
element
|
||||
.querySelector('img[class="responsive_img"]')
|
||||
?.attributes['src'] !=
|
||||
null) {
|
||||
String? thumbnail = element
|
||||
.querySelector('img[class="responsive_img"]')
|
||||
?.attributes['src'];
|
||||
trendingBooks.add(
|
||||
TrendingBookData(
|
||||
title: element
|
||||
.querySelector('div[class="title"]')
|
||||
?.text
|
||||
.toString()
|
||||
.trim(),
|
||||
thumbnail: thumbnail.toString()),
|
||||
);
|
||||
}
|
||||
}
|
||||
return trendingBooks;
|
||||
}
|
||||
}
|
||||
|
||||
class BookDigits extends TrendingBooksImpl {
|
||||
BookDigits() {
|
||||
super.url = "https://bookdigits.com/fresh";
|
||||
}
|
||||
|
||||
@override
|
||||
List<TrendingBookData> _parser(data) {
|
||||
var document = parse(data.toString());
|
||||
var bookList = document.querySelectorAll('div[class="list-row"]');
|
||||
List<TrendingBookData> trendingBooks = [];
|
||||
for (var element in bookList) {
|
||||
if (element.querySelector('div[class="list-title link-reg"]')?.text !=
|
||||
null &&
|
||||
element.querySelector('img')?.attributes['src'] != null) {
|
||||
String? thumbnail = element.querySelector('img')?.attributes['src'];
|
||||
trendingBooks.add(
|
||||
TrendingBookData(
|
||||
title: element
|
||||
.querySelector('div[class="list-title link-reg"]')
|
||||
?.text
|
||||
.toString()
|
||||
.trim(),
|
||||
thumbnail: thumbnail.toString()),
|
||||
);
|
||||
}
|
||||
}
|
||||
return trendingBooks;
|
||||
}
|
||||
}
|
||||
|
67
lib/services/share_book.dart
Normal file
@ -0,0 +1,67 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
Future<void> shareBook(String title, String link, String path) async {
|
||||
try {
|
||||
String imagePath = await saveAndGetImagePath(path);
|
||||
String message = 'Discover this amazing book: "$title"\nRead more : $link';
|
||||
if (imagePath.isNotEmpty) {
|
||||
await Share.shareXFiles([XFile(imagePath)], text: message);
|
||||
} else {
|
||||
await Share.share(message);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Error sharing the book: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> saveAndGetImagePath(String url) async {
|
||||
if (url != null && url.isNotEmpty) {
|
||||
try {
|
||||
final imageProvider = CachedNetworkImageProvider(url);
|
||||
final imageStream = imageProvider.resolve(const ImageConfiguration());
|
||||
String? localFilePath;
|
||||
|
||||
final Completer<ByteData> completer = Completer();
|
||||
|
||||
imageStream.addListener(
|
||||
ImageStreamListener(
|
||||
(ImageInfo info, bool _) async {
|
||||
try {
|
||||
final ByteData? byteData =
|
||||
await info.image.toByteData(format: ImageByteFormat.png);
|
||||
if (byteData != null) {
|
||||
final Uint8List imageBytes = byteData.buffer.asUint8List();
|
||||
final Directory tempDir = await getTemporaryDirectory();
|
||||
final File imageFile = File('${tempDir.path}/image.jpg');
|
||||
|
||||
await imageFile.writeAsBytes(imageBytes);
|
||||
localFilePath = imageFile.path;
|
||||
completer.complete(byteData);
|
||||
} else {
|
||||
completer.completeError('Failed to get image bytes');
|
||||
}
|
||||
} catch (e) {
|
||||
completer.completeError(e);
|
||||
}
|
||||
},
|
||||
onError: (exception, stackTrace) {
|
||||
completer.completeError('Failed to get image bytes');
|
||||
},
|
||||
),
|
||||
);
|
||||
await completer.future;
|
||||
return localFilePath ?? "";
|
||||
} catch (e) {
|
||||
return "";
|
||||
}
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
@ -1,12 +1,21 @@
|
||||
// Dart imports:
|
||||
import 'dart:math';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:openlib/services/database.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import 'package:openlib/services/open_library.dart';
|
||||
// Flutter imports:
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// Package imports:
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
// Project imports:
|
||||
import 'package:openlib/services/annas_archieve.dart';
|
||||
import 'package:openlib/services/database.dart';
|
||||
import 'package:openlib/services/files.dart';
|
||||
import 'package:openlib/services/open_library.dart';
|
||||
import 'package:openlib/services/goodreads.dart';
|
||||
|
||||
MyLibraryDb dataBase = MyLibraryDb.instance;
|
||||
|
||||
//Provider for dropdownbutton in search page
|
||||
|
||||
@ -30,7 +39,10 @@ Map<String, String> sortValues = {
|
||||
'Smallest': 'smallest',
|
||||
};
|
||||
|
||||
List<String> fileType = ["All", "PDF", "Epub", "Cbr", "Cbz"];
|
||||
|
||||
final selectedIndexProvider = StateProvider<int>((ref) => 0);
|
||||
final homePageSelectedIndexProvider = StateProvider<int>((ref) => 0);
|
||||
|
||||
final themeModeProvider = StateProvider<ThemeMode>((ref) => ThemeMode.light);
|
||||
|
||||
@ -46,17 +58,56 @@ final getSortValue = Provider.autoDispose<String>((ref) {
|
||||
return sortValues[ref.read(selectedSortState)] ?? '';
|
||||
});
|
||||
|
||||
final selectedFileTypeState = StateProvider<String>((ref) => "All");
|
||||
|
||||
final getFileTypeValue = Provider.autoDispose<String>((ref) {
|
||||
if (ref.read(selectedFileTypeState) == "All") {
|
||||
return '';
|
||||
}
|
||||
return ref.read(selectedFileTypeState).toLowerCase();
|
||||
});
|
||||
|
||||
//searchQueryProvider
|
||||
|
||||
final searchQueryProvider = StateProvider<String>((ref) => "");
|
||||
|
||||
// Sub category type list providers
|
||||
|
||||
final getSubCategoryTypeList = FutureProvider.family
|
||||
.autoDispose<List<CategoryBookData>, String>((ref, url) async {
|
||||
SubCategoriesTypeList subCategoriesTypeList = SubCategoriesTypeList();
|
||||
List<CategoryBookData> subCategories =
|
||||
await subCategoriesTypeList.categoriesBooks(url: url);
|
||||
List<CategoryBookData> uniqueArray = subCategories.toSet().toList();
|
||||
uniqueArray.shuffle();
|
||||
return uniqueArray;
|
||||
});
|
||||
|
||||
//Provider for Trending Books
|
||||
|
||||
final getTrendingBooks = FutureProvider<List<TrendingBookData>>((ref) async {
|
||||
OpenLibrary openLibrary = OpenLibrary();
|
||||
return await openLibrary.trendingBooks();
|
||||
// OpenLibrary openLibrary = OpenLibrary();
|
||||
GoodReads goodReads = GoodReads();
|
||||
PenguinRandomHouse penguinTrending = PenguinRandomHouse();
|
||||
BookDigits bookDigits = BookDigits();
|
||||
List<TrendingBookData> trendingBooks =
|
||||
await Future.wait<List<TrendingBookData>>([
|
||||
goodReads.trendingBooks(),
|
||||
penguinTrending.trendingBooks(),
|
||||
// openLibrary.trendingBooks(),
|
||||
bookDigits.trendingBooks(),
|
||||
]).then((List<List<TrendingBookData>> listOfData) =>
|
||||
listOfData.expand((element) => element).toList());
|
||||
|
||||
if (trendingBooks.isEmpty) {
|
||||
throw 'Nothing Trending Today :(';
|
||||
}
|
||||
trendingBooks.shuffle();
|
||||
return trendingBooks;
|
||||
});
|
||||
|
||||
final enableFiltersState = StateProvider<bool>((ref) => true);
|
||||
|
||||
//Provider for Trending Books
|
||||
final searchProvider = FutureProvider.family
|
||||
.autoDispose<List<BookData>, String>((ref, searchQuery) async {
|
||||
@ -64,10 +115,17 @@ final searchProvider = FutureProvider.family
|
||||
List<BookData> data = await annasArchieve.searchBooks(
|
||||
searchQuery: searchQuery,
|
||||
content: ref.watch(getTypeValue),
|
||||
sort: ref.watch(getSortValue));
|
||||
sort: ref.watch(getSortValue),
|
||||
fileType: ref.watch(getFileTypeValue),
|
||||
enableFilters: ref.watch(enableFiltersState));
|
||||
return data;
|
||||
});
|
||||
|
||||
final cookieProvider = StateProvider<String>((ref) => "");
|
||||
final userAgentProvider = StateProvider<String>((ref) => "");
|
||||
|
||||
final webViewLoadingState = StateProvider.autoDispose<bool>((ref) => true);
|
||||
|
||||
//Provider for Book Info
|
||||
final bookInfoProvider =
|
||||
FutureProvider.family<BookInfoData, String>((ref, url) async {
|
||||
@ -79,6 +137,8 @@ final bookInfoProvider =
|
||||
final downloadProgressProvider =
|
||||
StateProvider.autoDispose<double>((ref) => 0.0);
|
||||
|
||||
final mirrorStatusProvider = StateProvider.autoDispose<bool>((ref) => false);
|
||||
|
||||
final totalFileSizeInBytes = StateProvider.autoDispose<int>((ref) => 0);
|
||||
final downloadedFileSizeInBytes = StateProvider.autoDispose<int>((ref) => 0);
|
||||
|
||||
@ -102,15 +162,22 @@ final cancelCurrentDownload = StateProvider<CancelToken>((ref) {
|
||||
return CancelToken();
|
||||
});
|
||||
|
||||
final dbProvider = Provider<MyLibraryDb>((ref) => throw UnimplementedError());
|
||||
enum ProcessState { waiting, running, complete }
|
||||
|
||||
enum CheckSumProcessState { waiting, running, failed, success }
|
||||
|
||||
final downloadState =
|
||||
StateProvider.autoDispose<ProcessState>((ref) => ProcessState.waiting);
|
||||
final checkSumState = StateProvider.autoDispose<CheckSumProcessState>(
|
||||
(ref) => CheckSumProcessState.waiting);
|
||||
|
||||
final myLibraryProvider = FutureProvider((ref) async {
|
||||
return await ref.read(dbProvider).getAll();
|
||||
return dataBase.getAll();
|
||||
});
|
||||
|
||||
final checkIdExists =
|
||||
FutureProvider.family.autoDispose<bool, String>((ref, id) async {
|
||||
return await ref.read(dbProvider).checkIdExists(id);
|
||||
return await dataBase.checkIdExists(id);
|
||||
});
|
||||
|
||||
class FileName {
|
||||
@ -130,18 +197,18 @@ final totalPdfPage = StateProvider.autoDispose<int>((ref) => 0);
|
||||
|
||||
Future<void> savePdfState(String fileName, WidgetRef ref) async {
|
||||
String position = ref.watch(pdfCurrentPage).toString();
|
||||
await ref.watch(dbProvider).saveBookState(fileName, position);
|
||||
await dataBase.saveBookState(fileName, position);
|
||||
}
|
||||
|
||||
Future<void> saveEpubState(
|
||||
String fileName, String? position, WidgetRef ref) async {
|
||||
String pos = position ?? '';
|
||||
await ref.watch(dbProvider).saveBookState(fileName, pos);
|
||||
await dataBase.saveBookState(fileName, pos);
|
||||
}
|
||||
|
||||
final getBookPosition =
|
||||
FutureProvider.family.autoDispose<String?, String>((ref, fileName) async {
|
||||
return await ref.read(dbProvider).getBookState(fileName);
|
||||
return await dataBase.getBookState(fileName);
|
||||
});
|
||||
|
||||
final openPdfWithExternalAppProvider = StateProvider<bool>((ref) => false);
|
||||
|
@ -1,17 +1,23 @@
|
||||
// Flutter imports:
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// Package imports:
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import 'package:openlib/ui/components/snack_bar_widget.dart';
|
||||
// Project imports:
|
||||
import 'package:openlib/ui/components/page_title_widget.dart';
|
||||
import 'package:openlib/ui/components/snack_bar_widget.dart';
|
||||
|
||||
class AboutPage extends StatelessWidget {
|
||||
const AboutPage({Key? key}) : super(key: key);
|
||||
const AboutPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const version = "1.0.10";
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
title: const Text("Openlib"),
|
||||
titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
),
|
||||
@ -43,7 +49,7 @@ class AboutPage extends StatelessWidget {
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 7, right: 7, top: 5),
|
||||
child: Text(
|
||||
"1.0.1",
|
||||
version,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.bold,
|
||||
@ -87,8 +93,7 @@ class AboutPage extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _UrlText extends StatelessWidget {
|
||||
const _UrlText({Key? key, required this.text, required this.url})
|
||||
: super(key: key);
|
||||
const _UrlText({required this.text, required this.url});
|
||||
|
||||
final String url;
|
||||
final String text;
|
||||
|
@ -1,11 +1,26 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
// Dart imports:
|
||||
// import 'dart:convert';
|
||||
|
||||
import 'package:openlib/services/database.dart';
|
||||
import 'package:openlib/ui/components/error_widget.dart';
|
||||
import 'package:openlib/services/download_file.dart';
|
||||
// Flutter imports:
|
||||
import 'package:flutter/material.dart';
|
||||
// import 'package:flutter/scheduler.dart';
|
||||
|
||||
// Package imports:
|
||||
import 'package:dio/dio.dart' show CancelToken;
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:openlib/services/share_book.dart';
|
||||
// import 'package:flutter_svg/svg.dart';
|
||||
|
||||
// Project imports:
|
||||
import 'package:openlib/services/annas_archieve.dart' show BookInfoData;
|
||||
import 'package:openlib/services/database.dart';
|
||||
import 'package:openlib/services/download_file.dart';
|
||||
import 'package:openlib/ui/components/book_info_widget.dart';
|
||||
import 'package:openlib/ui/components/error_widget.dart';
|
||||
import 'package:openlib/ui/components/file_buttons_widget.dart';
|
||||
import 'package:openlib/ui/components/snack_bar_widget.dart';
|
||||
import 'package:openlib/ui/webview_page.dart';
|
||||
|
||||
import 'package:openlib/state/state.dart'
|
||||
show
|
||||
bookInfoProvider,
|
||||
@ -15,15 +30,16 @@ import 'package:openlib/state/state.dart'
|
||||
getTotalFileSize,
|
||||
getDownloadedFileSize,
|
||||
cancelCurrentDownload,
|
||||
dbProvider,
|
||||
mirrorStatusProvider,
|
||||
ProcessState,
|
||||
CheckSumProcessState,
|
||||
downloadState,
|
||||
checkSumState,
|
||||
checkIdExists,
|
||||
myLibraryProvider;
|
||||
import 'package:openlib/ui/components/book_info_widget.dart';
|
||||
import 'package:openlib/ui/components/file_buttons_widget.dart';
|
||||
import 'package:openlib/ui/components/snack_bar_widget.dart';
|
||||
|
||||
class BookInfoPage extends ConsumerWidget {
|
||||
const BookInfoPage({Key? key, required this.url}) : super(key: key);
|
||||
const BookInfoPage({super.key, required this.url});
|
||||
|
||||
final String url;
|
||||
|
||||
@ -32,16 +48,109 @@ class BookInfoPage extends ConsumerWidget {
|
||||
final bookInfo = ref.watch(bookInfoProvider(url));
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
title: const Text("Openlib"),
|
||||
titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
actions: [
|
||||
bookInfo.maybeWhen(data: (data) {
|
||||
return IconButton(
|
||||
icon: Icon(
|
||||
Icons.share_sharp,
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
),
|
||||
iconSize: 19.0,
|
||||
onPressed: () async {
|
||||
await shareBook(data.title, data.link, data.thumbnail ?? '');
|
||||
},
|
||||
);
|
||||
}, orElse: () {
|
||||
return const SizedBox.shrink();
|
||||
})
|
||||
],
|
||||
),
|
||||
body: bookInfo.when(
|
||||
skipLoadingOnRefresh: false,
|
||||
data: (data) {
|
||||
return BookInfoWidget(
|
||||
data: data, child: ActionButtonWidget(data: data));
|
||||
},
|
||||
error: (err, _) {
|
||||
// if (err.toString().contains("403")) {
|
||||
// var errJson = jsonDecode(err.toString());
|
||||
|
||||
// if (SchedulerBinding.instance.schedulerPhase ==
|
||||
// SchedulerPhase.persistentCallbacks) {
|
||||
// SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
// Future.delayed(
|
||||
// const Duration(seconds: 3),
|
||||
// () => Navigator.pushReplacement(context,
|
||||
// MaterialPageRoute(builder: (BuildContext context) {
|
||||
// return Webview(url: errJson["url"]);
|
||||
// })));
|
||||
// });
|
||||
// }
|
||||
|
||||
// return Column(
|
||||
// mainAxisAlignment: MainAxisAlignment.center,
|
||||
// crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
// children: [
|
||||
// SizedBox(
|
||||
// width: 210,
|
||||
// child: SvgPicture.asset(
|
||||
// 'assets/captcha.svg',
|
||||
// width: 210,
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(
|
||||
// height: 30,
|
||||
// ),
|
||||
// Text(
|
||||
// "Captcha required",
|
||||
// textAlign: TextAlign.center,
|
||||
// style: TextStyle(
|
||||
// fontSize: 18,
|
||||
// fontWeight: FontWeight.bold,
|
||||
// color: Theme.of(context).textTheme.headlineMedium?.color,
|
||||
// overflow: TextOverflow.ellipsis,
|
||||
// ),
|
||||
// ),
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.all(8.0),
|
||||
// child: Text(
|
||||
// "you will be redirected to solve captcha",
|
||||
// textAlign: TextAlign.center,
|
||||
// style: TextStyle(
|
||||
// fontSize: 13,
|
||||
// fontWeight: FontWeight.bold,
|
||||
// color: Theme.of(context).textTheme.headlineSmall?.color,
|
||||
// overflow: TextOverflow.ellipsis,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.fromLTRB(30, 15, 30, 10),
|
||||
// child: Container(
|
||||
// width: double.infinity,
|
||||
// decoration: BoxDecoration(
|
||||
// color: const Color.fromARGB(255, 255, 186, 186),
|
||||
// borderRadius: BorderRadius.circular(5)),
|
||||
// child: const Padding(
|
||||
// padding: EdgeInsets.all(10.0),
|
||||
// child: Text(
|
||||
// "If you have solved the captcha then you will be automatically redirected to the results page . In case you seeing this page even after completing try using a VPN .",
|
||||
// textAlign: TextAlign.start,
|
||||
// style: TextStyle(
|
||||
// fontSize: 13,
|
||||
// fontWeight: FontWeight.bold,
|
||||
// color: Colors.black,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// )
|
||||
// ],
|
||||
// );
|
||||
// } else {
|
||||
return CustomErrorWidget(
|
||||
error: err,
|
||||
stackTrace: _,
|
||||
@ -50,6 +159,7 @@ class BookInfoPage extends ConsumerWidget {
|
||||
ref.refresh(bookInfoProvider(url));
|
||||
},
|
||||
);
|
||||
// }
|
||||
},
|
||||
loading: () {
|
||||
return Center(
|
||||
@ -58,6 +168,7 @@ class BookInfoPage extends ConsumerWidget {
|
||||
height: 25,
|
||||
child: CircularProgressIndicator(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
strokeCap: StrokeCap.round,
|
||||
),
|
||||
));
|
||||
},
|
||||
@ -95,21 +206,36 @@ class _ActionButtonWidgetState extends ConsumerState<ActionButtonWidget> {
|
||||
} else {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 21, bottom: 21),
|
||||
child: TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.white,
|
||||
)),
|
||||
onPressed: () async {
|
||||
await downloadFileWidget(ref, context, widget.data);
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Text('Add To My Library'),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.start, // Aligns buttons properly
|
||||
children: [
|
||||
// Button for "Add To My Library"
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () async {
|
||||
final result = await Navigator.push(context,
|
||||
MaterialPageRoute(builder: (BuildContext context) {
|
||||
return Webview(url: widget.data.mirror ?? '');
|
||||
}));
|
||||
|
||||
if (result != null) {
|
||||
widget.data.mirror = result;
|
||||
await downloadFileWidget(ref, context, widget.data);
|
||||
}
|
||||
},
|
||||
child: const Text('Add To My Library'),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -120,6 +246,7 @@ class _ActionButtonWidgetState extends ConsumerState<ActionButtonWidget> {
|
||||
loading: () {
|
||||
return CircularProgressIndicator(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
strokeCap: StrokeCap.round,
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -127,19 +254,34 @@ class _ActionButtonWidgetState extends ConsumerState<ActionButtonWidget> {
|
||||
}
|
||||
|
||||
Future<void> downloadFileWidget(
|
||||
WidgetRef ref, BuildContext context, dynamic data) async {
|
||||
WidgetRef ref, BuildContext context, BookInfoData data) async {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return _ShowDialog(title: data.title);
|
||||
});
|
||||
|
||||
List<String> mirrors = [data.mirror!];
|
||||
// print(mirrors);
|
||||
downloadFile(
|
||||
mirrors: data.mirrors!,
|
||||
mirrors: mirrors,
|
||||
md5: data.md5,
|
||||
format: data.format!,
|
||||
onStart: () {
|
||||
ref.read(downloadState.notifier).state = ProcessState.running;
|
||||
},
|
||||
onProgress: (int rcv, int total) async {
|
||||
if (ref.read(totalFileSizeInBytes) != total) {
|
||||
ref.read(totalFileSizeInBytes.notifier).state = total;
|
||||
}
|
||||
ref.read(downloadedFileSizeInBytes.notifier).state = rcv;
|
||||
ref.read(downloadProgressProvider.notifier).state = rcv / total;
|
||||
|
||||
if (rcv / total == 1.0) {
|
||||
await ref.read(dbProvider).insert(MyBook(
|
||||
MyLibraryDb dataBase = MyLibraryDb.instance;
|
||||
|
||||
await dataBase.insert(MyBook(
|
||||
id: data.md5,
|
||||
title: data.title,
|
||||
author: data.author,
|
||||
@ -150,6 +292,23 @@ Future<void> downloadFileWidget(
|
||||
format: data.format,
|
||||
description: data.description));
|
||||
|
||||
ref.read(downloadState.notifier).state = ProcessState.complete;
|
||||
ref.read(checkSumState.notifier).state = CheckSumProcessState.running;
|
||||
|
||||
try {
|
||||
final checkSum = await verifyFileCheckSum(
|
||||
md5Hash: data.md5, format: data.format!);
|
||||
if (checkSum == true) {
|
||||
ref.read(checkSumState.notifier).state =
|
||||
CheckSumProcessState.success;
|
||||
} else {
|
||||
ref.read(checkSumState.notifier).state =
|
||||
CheckSumProcessState.failed;
|
||||
}
|
||||
} catch (_) {
|
||||
ref.read(checkSumState.notifier).state =
|
||||
CheckSumProcessState.failed;
|
||||
}
|
||||
// ignore: unused_result
|
||||
ref.refresh(checkIdExists(data.md5));
|
||||
// ignore: unused_result
|
||||
@ -161,17 +320,12 @@ Future<void> downloadFileWidget(
|
||||
cancelDownlaod: (CancelToken downloadToken) {
|
||||
ref.read(cancelCurrentDownload.notifier).state = downloadToken;
|
||||
},
|
||||
onDownlaodFailed: () {
|
||||
mirrorStatus: (val) {
|
||||
ref.read(mirrorStatusProvider.notifier).state = val;
|
||||
},
|
||||
onDownlaodFailed: (msg) {
|
||||
Navigator.of(context).pop();
|
||||
showSnackBar(
|
||||
context: context, message: 'downloaded Failed! try again...');
|
||||
});
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return _ShowDialog(title: data.title);
|
||||
showSnackBar(context: context, message: msg.toString());
|
||||
});
|
||||
}
|
||||
|
||||
@ -185,9 +339,19 @@ class _ShowDialog extends ConsumerWidget {
|
||||
final downloadProgress = ref.watch(downloadProgressProvider);
|
||||
final fileSize = ref.watch(getTotalFileSize);
|
||||
final downloadedFileSize = ref.watch(getDownloadedFileSize);
|
||||
final mirrorStatus = ref.watch(mirrorStatusProvider);
|
||||
final downloadProcessState = ref.watch(downloadState);
|
||||
final checkSumVerifyState = ref.watch(checkSumState);
|
||||
|
||||
if (downloadProgress == 1.0) {
|
||||
Navigator.of(context).pop();
|
||||
if (downloadProgress == 1.0 &&
|
||||
(checkSumVerifyState == CheckSumProcessState.failed ||
|
||||
checkSumVerifyState == CheckSumProcessState.success)) {
|
||||
Future.delayed(const Duration(seconds: 1), () {
|
||||
Navigator.of(context).pop();
|
||||
if (checkSumVerifyState == CheckSumProcessState.failed) {
|
||||
_showWarningFileDialog(context);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return Stack(
|
||||
@ -197,7 +361,7 @@ class _ShowDialog extends ConsumerWidget {
|
||||
padding: const EdgeInsets.all(15.0),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: 255,
|
||||
height: 345,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
color: Theme.of(context).colorScheme.tertiaryContainer,
|
||||
@ -236,6 +400,149 @@ class _ShowDialog extends ConsumerWidget {
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
mirrorStatus
|
||||
? const Icon(
|
||||
Icons.check_circle,
|
||||
size: 15,
|
||||
color: Colors.green,
|
||||
)
|
||||
: SizedBox(
|
||||
width: 9,
|
||||
height: 9,
|
||||
child: CircularProgressIndicator(
|
||||
color:
|
||||
Theme.of(context).colorScheme.secondary,
|
||||
strokeWidth: 2.5,
|
||||
strokeCap: StrokeCap.round,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 3,
|
||||
),
|
||||
Text(
|
||||
"Checking mirror availability",
|
||||
style: TextStyle(
|
||||
fontSize: 11.5,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.tertiary
|
||||
.withAlpha(140),
|
||||
decoration: TextDecoration.none),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
]),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
switch (downloadProcessState) {
|
||||
ProcessState.waiting => Icon(
|
||||
Icons.timer_sharp,
|
||||
size: 15,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.tertiary
|
||||
.withAlpha(140),
|
||||
),
|
||||
ProcessState.running => SizedBox(
|
||||
width: 9,
|
||||
height: 9,
|
||||
child: CircularProgressIndicator(
|
||||
color:
|
||||
Theme.of(context).colorScheme.secondary,
|
||||
strokeWidth: 2.5,
|
||||
strokeCap: StrokeCap.round,
|
||||
),
|
||||
),
|
||||
ProcessState.complete => const Icon(
|
||||
Icons.check_circle,
|
||||
size: 15,
|
||||
color: Colors.green,
|
||||
),
|
||||
},
|
||||
const SizedBox(
|
||||
width: 3,
|
||||
),
|
||||
Text(
|
||||
"Downloading",
|
||||
style: TextStyle(
|
||||
fontSize: 11.5,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.tertiary
|
||||
.withAlpha(140),
|
||||
decoration: TextDecoration.none),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
]),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
switch (checkSumVerifyState) {
|
||||
CheckSumProcessState.waiting => Icon(
|
||||
Icons.timer_sharp,
|
||||
size: 15,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.tertiary
|
||||
.withAlpha(140),
|
||||
),
|
||||
CheckSumProcessState.running => SizedBox(
|
||||
width: 9,
|
||||
height: 9,
|
||||
child: CircularProgressIndicator(
|
||||
color:
|
||||
Theme.of(context).colorScheme.secondary,
|
||||
strokeWidth: 2.5,
|
||||
strokeCap: StrokeCap.round,
|
||||
),
|
||||
),
|
||||
CheckSumProcessState.failed => const Icon(
|
||||
Icons.close,
|
||||
size: 15,
|
||||
color: Colors.red,
|
||||
),
|
||||
CheckSumProcessState.success => const Icon(
|
||||
Icons.check_circle,
|
||||
size: 15,
|
||||
color: Colors.green,
|
||||
),
|
||||
},
|
||||
const SizedBox(
|
||||
width: 3,
|
||||
),
|
||||
Text(
|
||||
"Verifying file checksum",
|
||||
style: TextStyle(
|
||||
fontSize: 11.5,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.tertiary
|
||||
.withAlpha(140),
|
||||
decoration: TextDecoration.none),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
]),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
@ -306,3 +613,52 @@ class _ShowDialog extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showWarningFileDialog(BuildContext context) async {
|
||||
return showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Checksum failed!',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
decoration: TextDecoration.none,
|
||||
letterSpacing: 1),
|
||||
),
|
||||
content: SingleChildScrollView(
|
||||
child: ListBody(
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'The downloaded book may be malicious. Delete it and get the same book from another source, or use the book at your own risk.',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.tertiary.withAlpha(170),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: Text(
|
||||
'Okay',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
decoration: TextDecoration.none,
|
||||
letterSpacing: 1),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
530
lib/ui/categories_page.dart
Normal file
@ -0,0 +1,530 @@
|
||||
// Flutter imports:
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
|
||||
// Package imports:
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
// Project imports:
|
||||
import 'package:openlib/ui/components/page_title_widget.dart';
|
||||
import 'package:openlib/ui/extensions.dart';
|
||||
import 'package:openlib/ui/results_page.dart';
|
||||
import 'package:openlib/ui/components/error_widget.dart';
|
||||
import 'package:openlib/state/state.dart'
|
||||
show getSubCategoryTypeList, enableFiltersState;
|
||||
|
||||
class CategoryBook {
|
||||
final String title;
|
||||
final String thumbnail;
|
||||
final String tag;
|
||||
final String info;
|
||||
CategoryBook(
|
||||
{required this.title,
|
||||
required this.thumbnail,
|
||||
required this.tag,
|
||||
required this.info});
|
||||
}
|
||||
|
||||
List<CategoryBook> categoriesTypeValues = [
|
||||
CategoryBook(
|
||||
info:
|
||||
"Timeless literary works often revered for their artistic merit and cultural significance.",
|
||||
thumbnail:
|
||||
"https://raw.githubusercontent.com/Nav-jangra/images/refs/heads/main/1%20classic.jpeg",
|
||||
title: "Classics",
|
||||
tag: "list/tag/classics"),
|
||||
CategoryBook(
|
||||
info:
|
||||
"Stories focused on romantic relationships, exploring love, passion, and emotional connections.",
|
||||
thumbnail:
|
||||
"https://raw.githubusercontent.com/Nav-jangra/images/refs/heads/main/2%20romance.jpeg",
|
||||
title: "Romance",
|
||||
tag: "list/tag/romance"),
|
||||
CategoryBook(
|
||||
info:
|
||||
"Narrative literature created from the imagination, not based on real events.",
|
||||
thumbnail:
|
||||
"https://raw.githubusercontent.com/Nav-jangra/images/refs/heads/main/3%20fiction.jpeg",
|
||||
title: "Fiction",
|
||||
tag: "list/tag/fiction"),
|
||||
CategoryBook(
|
||||
info:
|
||||
"Books targeted at teenage readers, addressing themes relevant to adolescence.",
|
||||
thumbnail:
|
||||
"https://raw.githubusercontent.com/Nav-jangra/images/refs/heads/main/4%20young%20adult.jpeg",
|
||||
title: "Young Adult",
|
||||
tag: "list/tag/young-adult"),
|
||||
CategoryBook(
|
||||
info:
|
||||
"A genre featuring magical elements, mythical creatures, and fantastical worlds.",
|
||||
thumbnail:
|
||||
"https://raw.githubusercontent.com/Nav-jangra/images/refs/heads/main/5%20fantasy%20book.jpeg",
|
||||
title: "Fantasy",
|
||||
tag: "list/tag/fantasy"),
|
||||
CategoryBook(
|
||||
info:
|
||||
"Literature that explores futuristic concepts, advanced technology, and space exploration.",
|
||||
thumbnail:
|
||||
"https://raw.githubusercontent.com/Nav-jangra/images/refs/heads/main/6%20science%20fiction.jpeg",
|
||||
title: "Science Fiction",
|
||||
tag: "list/tag/science-fiction"),
|
||||
CategoryBook(
|
||||
info:
|
||||
"Works based on factual information, including essays, biographies, and documentaries.",
|
||||
thumbnail:
|
||||
"https://raw.githubusercontent.com/Nav-jangra/images/refs/heads/main/7%20non%20fiction.jpeg",
|
||||
title: "Nonfiction",
|
||||
tag: "list/tag/non-fiction"),
|
||||
CategoryBook(
|
||||
info:
|
||||
"Books aimed at young readers, often with illustrations and simple narratives.",
|
||||
thumbnail:
|
||||
"https://raw.githubusercontent.com/Nav-jangra/images/refs/heads/main/8%20children.jpeg",
|
||||
title: "Children",
|
||||
tag: "list/tag/children"),
|
||||
CategoryBook(
|
||||
info:
|
||||
"Literature that examines past events, cultures, and significant historical figures.",
|
||||
thumbnail:
|
||||
"https://raw.githubusercontent.com/Nav-jangra/images/refs/heads/main/9%20history.jpeg",
|
||||
title: "History",
|
||||
tag: "list/tag/history"),
|
||||
CategoryBook(
|
||||
info:
|
||||
"Stories centered around suspenseful plots, often involving crime or puzzles to solve.",
|
||||
thumbnail:
|
||||
"https://raw.githubusercontent.com/Nav-jangra/images/refs/heads/main/10%20mystery.jpeg",
|
||||
title: "Mystery",
|
||||
tag: "list/tag/mystery"),
|
||||
CategoryBook(
|
||||
info:
|
||||
"Refers to the artwork and design that visually represents a book, influencing reader interest.",
|
||||
thumbnail:
|
||||
"https://raw.githubusercontent.com/Nav-jangra/images/refs/heads/main/11%20covers.jpeg",
|
||||
title: "Covers",
|
||||
tag: "list/tag/covers"),
|
||||
CategoryBook(
|
||||
info:
|
||||
"A genre designed to evoke fear, dread, or terror in the reader through suspenseful storytelling.",
|
||||
thumbnail:
|
||||
"https://raw.githubusercontent.com/Nav-jangra/images/refs/heads/main/12%20horror.jpeg",
|
||||
title: "Horror",
|
||||
tag: "list/tag/horror"),
|
||||
CategoryBook(
|
||||
info:
|
||||
"Novels set in a specific historical period, blending factual events with fictional characters.",
|
||||
thumbnail:
|
||||
"https://raw.githubusercontent.com/Nav-jangra/images/refs/heads/main/13%20historical%20fiction.jpeg",
|
||||
title: "Historical Fiction",
|
||||
tag: "list/tag/historical-fiction"),
|
||||
CategoryBook(
|
||||
info:
|
||||
"Often refers to critically acclaimed or popular books, usually categorized by rankings or awards.",
|
||||
thumbnail:
|
||||
"https://raw.githubusercontent.com/Nav-jangra/images/refs/heads/main/15%20best%20.jpeg",
|
||||
title: "Best",
|
||||
tag: "list/tag/best"),
|
||||
CategoryBook(
|
||||
info:
|
||||
"Refers to the names of books, which often reflect their themes or subject matter.",
|
||||
thumbnail:
|
||||
"https://raw.githubusercontent.com/Nav-jangra/images/refs/heads/main/16%20titles.jpeg",
|
||||
title: "Titles",
|
||||
tag: "list/tag/titles"),
|
||||
CategoryBook(
|
||||
info:
|
||||
"Books intended for readers aged 8-12, featuring age-appropriate themes and characters.",
|
||||
thumbnail:
|
||||
"https://raw.githubusercontent.com/Nav-jangra/images/refs/heads/main/17%20middle%20grade.jpeg",
|
||||
title: "Middle Grade",
|
||||
tag: "list/tag/middle-grade"),
|
||||
CategoryBook(
|
||||
info:
|
||||
"Stories that incorporate supernatural elements, including ghosts, vampires, and otherworldly beings.",
|
||||
thumbnail:
|
||||
"https://raw.githubusercontent.com/Nav-jangra/images/refs/heads/main/18%20paranormal.jpeg",
|
||||
title: "Paranormal",
|
||||
tag: "list/tag/paranormal"),
|
||||
CategoryBook(
|
||||
info:
|
||||
"A theme exploring the complexities and nuances of love in various forms and relationships.",
|
||||
thumbnail:
|
||||
"https://raw.githubusercontent.com/Nav-jangra/images/refs/heads/main/19%20love.jpeg",
|
||||
title: "Love",
|
||||
tag: "list/tag/love"),
|
||||
CategoryBook(
|
||||
info:
|
||||
"Literature that represents diverse sexual orientations and gender identities within the LGBTQ+ spectrum.",
|
||||
thumbnail:
|
||||
"https://raw.githubusercontent.com/Nav-jangra/images/refs/heads/main/20%20queer.jpeg",
|
||||
title: "Queer",
|
||||
tag: "list/tag/queer"),
|
||||
CategoryBook(
|
||||
info:
|
||||
"Works based on factual information, including essays, biographies, and documentaries.",
|
||||
thumbnail:
|
||||
"https://raw.githubusercontent.com/Nav-jangra/images/refs/heads/main/21%20nonfictino.jpeg",
|
||||
title: "Nonfiction",
|
||||
tag: "list/tag/nonfiction"),
|
||||
CategoryBook(
|
||||
info:
|
||||
"Novels combining romantic plots set against a historical backdrop.",
|
||||
thumbnail:
|
||||
"https://raw.githubusercontent.com/Nav-jangra/images/refs/heads/main/22%20historical%20romance.jpeg",
|
||||
title: "Historical Romance",
|
||||
tag: "list/tag/historical-romance"),
|
||||
CategoryBook(
|
||||
info:
|
||||
"Works set in modern times, often addressing current social issues and relatable characters.",
|
||||
thumbnail:
|
||||
"https://raw.githubusercontent.com/Nav-jangra/images/refs/heads/main/24%20contemporary.jpeg",
|
||||
title: "Contemporary",
|
||||
tag: "list/tag/contemporary"),
|
||||
CategoryBook(
|
||||
info:
|
||||
"A suspenseful genre focused on excitement, tension, and unexpected twists.",
|
||||
thumbnail:
|
||||
"https://raw.githubusercontent.com/Nav-jangra/images/refs/heads/main/25%20thriller.jpeg",
|
||||
title: "Thriller",
|
||||
tag: "list/tag/thriller"),
|
||||
CategoryBook(
|
||||
info:
|
||||
"Literature that explores women's experiences, perspectives, and empowerment.",
|
||||
thumbnail:
|
||||
"https://raw.githubusercontent.com/Nav-jangra/images/refs/heads/main/26%20women.jpeg",
|
||||
title: "Women",
|
||||
tag: "list/tag/women"),
|
||||
CategoryBook(
|
||||
info:
|
||||
"Nonfiction works detailing the life story of an individual, highlighting their achievements and challenges.",
|
||||
thumbnail:
|
||||
"https://raw.githubusercontent.com/Nav-jangra/images/refs/heads/main/27%20biography.jpeg",
|
||||
title: "Biography",
|
||||
tag: "list/tag/biography"),
|
||||
CategoryBook(
|
||||
info:
|
||||
"Inclusive literature representing the experiences of the lesbian, gay, bisexual, transgender, and queer community.",
|
||||
thumbnail:
|
||||
"https://raw.githubusercontent.com/Nav-jangra/images/refs/heads/main/28%20lgbtq.jpeg",
|
||||
title: "LGBTQ",
|
||||
tag: "list/tag/lgbtq"),
|
||||
CategoryBook(
|
||||
info:
|
||||
"A collection of related books that follow a common storyline or set of characters.",
|
||||
thumbnail:
|
||||
"https://raw.githubusercontent.com/Nav-jangra/images/refs/heads/main/29%20series%20.jpeg",
|
||||
title: "Series",
|
||||
tag: "list/tag/series"),
|
||||
CategoryBook(
|
||||
info:
|
||||
"Refers to prompts or contests encouraging creative thinking about book titles.",
|
||||
thumbnail:
|
||||
"https://raw.githubusercontent.com/Nav-jangra/images/refs/heads/main/30%20title%20chhallenge.jpeg",
|
||||
title: "Title Challenge",
|
||||
tag: "list/tag/title-challenge"),
|
||||
];
|
||||
|
||||
class GenresPage extends ConsumerWidget {
|
||||
const GenresPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final categories = categoriesTypeValues;
|
||||
return Scaffold(
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.only(left: 5, right: 5, top: 10),
|
||||
child: CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.only(left: 5, right: 5, top: 10),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
final category = categories[index];
|
||||
return BookInfoCard(
|
||||
title: category.title,
|
||||
thumbnail: category.thumbnail,
|
||||
info: category.info,
|
||||
onClick: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return CategoryListingPage(
|
||||
url: category.tag,
|
||||
title: category.title,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
childCount: categories.length,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class BookInfoCard extends StatelessWidget {
|
||||
const BookInfoCard(
|
||||
{super.key,
|
||||
required this.title,
|
||||
required this.thumbnail,
|
||||
required this.info,
|
||||
required this.onClick});
|
||||
|
||||
final String title;
|
||||
final String thumbnail;
|
||||
final String info;
|
||||
final VoidCallback onClick;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onClick,
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: 120,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
margin: const EdgeInsets.only(bottom: 10),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
CachedNetworkImage(
|
||||
height: 120,
|
||||
width: 90,
|
||||
imageUrl: thumbnail,
|
||||
imageBuilder: (context, imageProvider) => Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(5)),
|
||||
image: DecorationImage(
|
||||
image: imageProvider,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
),
|
||||
placeholder: (context, url) => Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
color: "#F8C0C8".toColor(),
|
||||
),
|
||||
height: 120,
|
||||
width: 90,
|
||||
),
|
||||
errorWidget: (context, url, error) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
color: "#F8C0C8".toColor(),
|
||||
),
|
||||
height: 120,
|
||||
width: 90,
|
||||
child: const Center(
|
||||
child: Icon(Icons.image_rounded),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(5),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 19,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
),
|
||||
Text(
|
||||
info,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color:
|
||||
Theme.of(context).textTheme.headlineMedium?.color,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 3,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
))
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CategoryListingPage extends ConsumerWidget {
|
||||
const CategoryListingPage(
|
||||
{super.key, required this.url, required this.title});
|
||||
final double imageHeight = 145;
|
||||
final double imageWidth = 105;
|
||||
final String url;
|
||||
final String title;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final booksBasedOnGenre = ref.watch(getSubCategoryTypeList(url));
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
title: const Text("Openlib"),
|
||||
titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
),
|
||||
body: booksBasedOnGenre.when(
|
||||
skipLoadingOnRefresh: false,
|
||||
data: (data) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 5, right: 5, top: 10),
|
||||
child: CustomScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: TitleText(title),
|
||||
),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.all(5),
|
||||
sliver: SliverGrid(
|
||||
gridDelegate:
|
||||
const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 10.0,
|
||||
crossAxisSpacing: 13.0,
|
||||
mainAxisExtent: 205,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
ref.read(enableFiltersState.notifier).state =
|
||||
false;
|
||||
Navigator.push(context, MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return ResultPage(
|
||||
searchQuery: data[index].title!);
|
||||
}));
|
||||
},
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.center,
|
||||
children: [
|
||||
CachedNetworkImage(
|
||||
height: imageHeight,
|
||||
width: imageWidth,
|
||||
imageUrl: data[index].thumbnail!,
|
||||
imageBuilder:
|
||||
(context, imageProvider) =>
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Colors.grey,
|
||||
spreadRadius: 0.1,
|
||||
blurRadius: 1)
|
||||
],
|
||||
borderRadius:
|
||||
const BorderRadius.all(
|
||||
Radius.circular(5)),
|
||||
image: DecorationImage(
|
||||
image: imageProvider,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
),
|
||||
placeholder: (context, url) =>
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
BorderRadius.circular(5),
|
||||
color: "#E3E8E9".toColor(),
|
||||
),
|
||||
height: imageHeight,
|
||||
width: imageWidth,
|
||||
),
|
||||
errorWidget: (context, url, error) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
BorderRadius.circular(5),
|
||||
color: Colors.grey,
|
||||
),
|
||||
height: imageHeight,
|
||||
width: imageWidth,
|
||||
child: const Center(
|
||||
child: Icon(Icons.image_rounded),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
child: SizedBox(
|
||||
width: imageWidth,
|
||||
child: Text(
|
||||
data[index].title!,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.displayMedium,
|
||||
maxLines: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
);
|
||||
},
|
||||
childCount: data.length,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
error: (error, _) {
|
||||
return CustomErrorWidget(
|
||||
error: error,
|
||||
stackTrace: _,
|
||||
// onRefresh: () {
|
||||
// // ignore: unused_result
|
||||
// ref.refresh(getbooksBasedOnGenre);
|
||||
// },
|
||||
);
|
||||
},
|
||||
loading: () {
|
||||
return Center(
|
||||
child: SizedBox(
|
||||
width: 25,
|
||||
height: 25,
|
||||
child: CircularProgressIndicator(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
strokeCap: StrokeCap.round,
|
||||
),
|
||||
));
|
||||
}));
|
||||
}
|
||||
}
|
@ -1,27 +1,47 @@
|
||||
// Flutter imports:
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:openlib/ui/extensions.dart';
|
||||
|
||||
// Package imports:
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
|
||||
// Project imports:
|
||||
import 'package:openlib/ui/extensions.dart';
|
||||
|
||||
String? getFileType(String? info) {
|
||||
if (info != null && info.isNotEmpty) {
|
||||
info = info.toLowerCase();
|
||||
if (info.contains('pdf')) return "PDF";
|
||||
if (info.contains('epub')) return "Epub";
|
||||
if (info.contains('cbr')) return "Cbr";
|
||||
if (info.contains('cbz')) return "Cbz";
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class BookInfoCard extends StatelessWidget {
|
||||
const BookInfoCard(
|
||||
{Key? key,
|
||||
{super.key,
|
||||
required this.title,
|
||||
required this.author,
|
||||
required this.publisher,
|
||||
required this.thumbnail,
|
||||
required this.info,
|
||||
required this.link,
|
||||
required this.onClick})
|
||||
: super(key: key);
|
||||
required this.onClick});
|
||||
|
||||
final String title;
|
||||
final String author;
|
||||
final String publisher;
|
||||
final String? thumbnail;
|
||||
final String? info;
|
||||
final String link;
|
||||
final VoidCallback onClick;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String? fileType = getFileType(info);
|
||||
|
||||
return InkWell(
|
||||
onTap: onClick,
|
||||
child: Container(
|
||||
@ -100,15 +120,50 @@ class BookInfoCard extends StatelessWidget {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
Text(
|
||||
author,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).textTheme.headlineSmall?.color,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
if (fileType != null)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: "#a5a5a5".toColor(),
|
||||
borderRadius: BorderRadius.circular(2.5),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(3, 2, 3, 2),
|
||||
child: Text(
|
||||
fileType,
|
||||
style: const TextStyle(
|
||||
fontSize: 8.5,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (fileType != null)
|
||||
const SizedBox(
|
||||
width: 3,
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
author,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineSmall
|
||||
?.color,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -1,12 +1,14 @@
|
||||
// Flutter imports:
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// Package imports:
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
|
||||
class BookInfoWidget extends StatelessWidget {
|
||||
final Widget child;
|
||||
final dynamic data;
|
||||
|
||||
const BookInfoWidget({Key? key, required this.child, required this.data})
|
||||
: super(key: key);
|
||||
const BookInfoWidget({super.key, required this.child, required this.data});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -145,9 +147,7 @@ class _TopPaddedText extends StatelessWidget {
|
||||
required this.fontSize,
|
||||
required this.topPadding,
|
||||
required this.color,
|
||||
required this.maxLines,
|
||||
Key? key})
|
||||
: super(key: key);
|
||||
required this.maxLines});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -1,7 +1,12 @@
|
||||
// Flutter imports:
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// Package imports:
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:openlib/ui/components/snack_bar_widget.dart';
|
||||
|
||||
// Project imports:
|
||||
import 'package:openlib/state/state.dart' show FileName, deleteFileFromMyLib;
|
||||
import 'package:openlib/ui/components/snack_bar_widget.dart';
|
||||
|
||||
class ShowDeleteDialog extends ConsumerWidget {
|
||||
final String id;
|
||||
|
@ -1,7 +1,12 @@
|
||||
// Flutter imports:
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:openlib/ui/extensions.dart';
|
||||
|
||||
// Package imports:
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
// Project imports:
|
||||
import 'package:openlib/ui/extensions.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class CustomErrorWidget extends StatelessWidget {
|
||||
final Object error;
|
||||
@ -9,8 +14,10 @@ class CustomErrorWidget extends StatelessWidget {
|
||||
VoidCallback? onRefresh;
|
||||
|
||||
CustomErrorWidget(
|
||||
{Key? key, required this.error, required this.stackTrace, this.onRefresh})
|
||||
: super(key: key);
|
||||
{super.key,
|
||||
required this.error,
|
||||
required this.stackTrace,
|
||||
this.onRefresh});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -1,7 +1,14 @@
|
||||
// Flutter imports:
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
// Package imports:
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:open_file/open_file.dart';
|
||||
|
||||
// Project imports:
|
||||
import 'package:openlib/services/files.dart' show getFilePath;
|
||||
import 'package:openlib/ui/components/delete_dialog_widget.dart';
|
||||
import 'package:openlib/ui/components/snack_bar_widget.dart';
|
||||
import 'package:openlib/ui/epub_viewer.dart' show launchEpubViewer;
|
||||
import 'package:openlib/ui/pdf_viewer.dart' show launchPdfViewer;
|
||||
|
||||
@ -11,11 +18,10 @@ class FileOpenAndDeleteButtons extends ConsumerWidget {
|
||||
final Function onDelete;
|
||||
|
||||
const FileOpenAndDeleteButtons(
|
||||
{Key? key,
|
||||
{super.key,
|
||||
required this.id,
|
||||
required this.format,
|
||||
required this.onDelete})
|
||||
: super(key: key);
|
||||
required this.onDelete});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@ -37,9 +43,11 @@ class FileOpenAndDeleteButtons extends ConsumerWidget {
|
||||
if (format == 'pdf') {
|
||||
await launchPdfViewer(
|
||||
fileName: '$id.$format', context: context, ref: ref);
|
||||
} else {
|
||||
} else if (format == 'epub') {
|
||||
await launchEpubViewer(
|
||||
fileName: '$id.$format', context: context, ref: ref);
|
||||
} else {
|
||||
await openCbrAndCbz(fileName: '$id.$format', context: context);
|
||||
}
|
||||
},
|
||||
child: const Padding(
|
||||
@ -89,3 +97,16 @@ class FileOpenAndDeleteButtons extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> openCbrAndCbz(
|
||||
{required String fileName, required BuildContext context}) async {
|
||||
try {
|
||||
String path = await getFilePath(fileName);
|
||||
await OpenFile.open(path, linuxByProcess: true);
|
||||
} catch (e) {
|
||||
// ignore: avoid_print
|
||||
// print(e);
|
||||
// ignore: use_build_context_synchronously
|
||||
showSnackBar(context: context, message: 'Unable to open file!');
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
// Flutter imports:
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TitleText extends StatelessWidget {
|
||||
|
@ -1,3 +1,4 @@
|
||||
// Flutter imports:
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void showSnackBar({required BuildContext context, required String message}) {
|
||||
|
@ -1,18 +1,24 @@
|
||||
import 'dart:io';
|
||||
// Dart imports:
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
// Flutter imports:
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// Package imports:
|
||||
import 'package:epub_view/epub_view.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:vocsy_epub_viewer/epub_viewer.dart';
|
||||
import 'package:open_file/open_file.dart';
|
||||
import 'package:openlib/services/database.dart';
|
||||
import 'package:vocsy_epub_viewer/epub_viewer.dart';
|
||||
|
||||
// Project imports:
|
||||
import 'package:openlib/services/files.dart' show getFilePath;
|
||||
import 'package:openlib/ui/components/snack_bar_widget.dart';
|
||||
import 'package:openlib/state/state.dart'
|
||||
show
|
||||
filePathProvider,
|
||||
saveEpubState,
|
||||
dbProvider,
|
||||
getBookPosition,
|
||||
openEpubWithExternalAppProvider;
|
||||
|
||||
@ -21,17 +27,18 @@ Future<void> launchEpubViewer(
|
||||
required BuildContext context,
|
||||
required WidgetRef ref}) async {
|
||||
if (Platform.isAndroid || Platform.isIOS) {
|
||||
MyLibraryDb dataBase = MyLibraryDb.instance;
|
||||
String path = await getFilePath(fileName);
|
||||
String? epubConfig = await ref.read(dbProvider).getBookState(fileName);
|
||||
bool openWithExternalApp = ref.watch(openEpubWithExternalAppProvider);
|
||||
String? epubConfig = await dataBase.getBookState(fileName);
|
||||
|
||||
if (openWithExternalApp) {
|
||||
await OpenFile.open(path);
|
||||
await OpenFile.open(path, linuxByProcess: true);
|
||||
} else {
|
||||
try {
|
||||
VocsyEpub.setConfig(
|
||||
// ignore: use_build_context_synchronously
|
||||
themeColor: const Color.fromARGB(255, 210, 15, 1),
|
||||
themeColor: Theme.of(context).colorScheme.secondary,
|
||||
identifier: "iosBook",
|
||||
scrollDirection: EpubScrollDirection.HORIZONTAL,
|
||||
);
|
||||
@ -50,8 +57,18 @@ Future<void> launchEpubViewer(
|
||||
// convert locator from string to json and save to your database to be retrieved later
|
||||
});
|
||||
} catch (e) {
|
||||
// ignore: use_build_context_synchronously
|
||||
showSnackBar(context: context, message: 'Unable to open pdf!');
|
||||
try {
|
||||
// ignore: use_build_context_synchronously
|
||||
Navigator.push(context,
|
||||
MaterialPageRoute(builder: (BuildContext context) {
|
||||
return EpubViewerWidget(
|
||||
fileName: fileName,
|
||||
);
|
||||
}));
|
||||
} catch (e) {
|
||||
// ignore: use_build_context_synchronously
|
||||
showSnackBar(context: context, message: 'Unable to open pdf!');
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -109,8 +126,7 @@ class _EpubViewState extends ConsumerState<EpubViewerWidget> {
|
||||
}
|
||||
|
||||
class EpubViewer extends ConsumerStatefulWidget {
|
||||
const EpubViewer({Key? key, required this.filePath, required this.fileName})
|
||||
: super(key: key);
|
||||
const EpubViewer({super.key, required this.filePath, required this.fileName});
|
||||
|
||||
final String filePath;
|
||||
final String fileName;
|
||||
|
@ -1,3 +1,4 @@
|
||||
// Flutter imports:
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
extension ColorExtension on String {
|
||||
|
154
lib/ui/home_page.dart
Normal file
@ -0,0 +1,154 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:openlib/state/state.dart';
|
||||
import 'package:openlib/ui/categories_page.dart';
|
||||
import 'package:openlib/ui/components/page_title_widget.dart';
|
||||
import 'package:openlib/ui/trending_page.dart';
|
||||
|
||||
class HomePage extends ConsumerStatefulWidget {
|
||||
const HomePage({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<ConsumerStatefulWidget> createState() => _HomePageState();
|
||||
}
|
||||
|
||||
class _HomePageState extends ConsumerState<HomePage> {
|
||||
final List<Widget> _pages = const [
|
||||
TrendingPage(),
|
||||
GenresPage(),
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final selectedIndex = ref.watch(homePageSelectedIndexProvider);
|
||||
return Scaffold(
|
||||
body: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 5, right: 5, top: 10),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
TitleText(selectedIndex == 0 ? "Trending" : "Genres"),
|
||||
PelletContainer(
|
||||
selectedIndex: selectedIndex,
|
||||
onTrendingSelected: () => {
|
||||
ref.read(homePageSelectedIndexProvider.notifier).state = 0
|
||||
},
|
||||
onCategoriesSelected: () => {
|
||||
ref.read(homePageSelectedIndexProvider.notifier).state = 1
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(child: _pages[selectedIndex]), // Display the selected page
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PelletContainer extends StatelessWidget {
|
||||
final int selectedIndex;
|
||||
final VoidCallback onTrendingSelected;
|
||||
final VoidCallback onCategoriesSelected;
|
||||
|
||||
const PelletContainer({
|
||||
super.key,
|
||||
required this.selectedIndex,
|
||||
required this.onTrendingSelected,
|
||||
required this.onCategoriesSelected,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 30,
|
||||
width: 105,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
border: Border.all(color: Theme.of(context).colorScheme.secondary),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Left Button (Trending)
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: onTrendingSelected,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
decoration: BoxDecoration(
|
||||
color: selectedIndex == 0
|
||||
? Theme.of(context).colorScheme.secondary
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(25),
|
||||
bottomLeft: Radius.circular(25),
|
||||
topRight: Radius.circular(0),
|
||||
bottomRight: Radius.circular(0),
|
||||
),
|
||||
),
|
||||
child: TextButton.icon(
|
||||
onPressed: null, // Disable direct onPressed to avoid conflict
|
||||
icon: Icon(
|
||||
Icons.trending_up,
|
||||
color: selectedIndex == 0
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.tertiary,
|
||||
),
|
||||
label: const Text(''), // Empty label
|
||||
style: TextButton.styleFrom(
|
||||
backgroundColor: Colors.transparent,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const VerticalDivider(
|
||||
width: 0,
|
||||
thickness: 1,
|
||||
color: Colors.grey,
|
||||
),
|
||||
// Right Button (Categories)
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: onCategoriesSelected,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
decoration: BoxDecoration(
|
||||
color: selectedIndex == 1
|
||||
? Theme.of(context).colorScheme.secondary
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(0),
|
||||
bottomLeft: Radius.circular(0),
|
||||
topRight: Radius.circular(25),
|
||||
bottomRight: Radius.circular(25),
|
||||
),
|
||||
),
|
||||
child: TextButton.icon(
|
||||
onPressed: null,
|
||||
icon: Icon(
|
||||
Icons.dashboard_rounded,
|
||||
color: selectedIndex == 1
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.tertiary,
|
||||
),
|
||||
label: const Text(''), // Empty label
|
||||
style: TextButton.styleFrom(
|
||||
backgroundColor: Colors.transparent,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,28 +1,56 @@
|
||||
// Flutter imports:
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:openlib/services/database.dart';
|
||||
import 'package:openlib/state/state.dart' show dbProvider;
|
||||
|
||||
// Package imports:
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
// Project imports:
|
||||
import 'package:openlib/services/database.dart';
|
||||
import 'package:openlib/services/share_book.dart';
|
||||
import 'package:openlib/ui/components/book_info_widget.dart';
|
||||
import 'package:openlib/ui/components/file_buttons_widget.dart';
|
||||
|
||||
class BookPage extends StatelessWidget {
|
||||
const BookPage({Key? key, required this.id}) : super(key: key);
|
||||
const BookPage({super.key, required this.id});
|
||||
|
||||
final String id;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
MyLibraryDb dataBase = MyLibraryDb.instance;
|
||||
final bookInfo = dataBase.getId(id);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
title: const Text("Openlib"),
|
||||
titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
actions: [
|
||||
FutureBuilder(
|
||||
future: bookInfo,
|
||||
builder: (BuildContext context, AsyncSnapshot<MyBook?> snapshot) {
|
||||
if (snapshot.hasData &&
|
||||
snapshot.data?.title != null &&
|
||||
snapshot.data?.link != null) {
|
||||
return IconButton(
|
||||
icon: Icon(
|
||||
Icons.share_sharp,
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
),
|
||||
iconSize: 19.0,
|
||||
onPressed: () async {
|
||||
await shareBook(snapshot.data!.title, snapshot.data!.link,
|
||||
snapshot.data?.thumbnail ?? '');
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
})
|
||||
],
|
||||
),
|
||||
body: Consumer(
|
||||
builder: (BuildContext context, WidgetRef ref, _) {
|
||||
final bookInfo = ref.read(dbProvider).getId(id);
|
||||
|
||||
return FutureBuilder(
|
||||
future: bookInfo,
|
||||
builder: (BuildContext context, AsyncSnapshot<MyBook?> snapshot) {
|
||||
|
@ -1,13 +1,17 @@
|
||||
// Flutter imports:
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:openlib/ui/extensions.dart';
|
||||
import 'package:openlib/ui/mybook_page.dart';
|
||||
// Package imports:
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
// Project imports:
|
||||
import 'package:openlib/state/state.dart' show myLibraryProvider;
|
||||
import 'package:openlib/ui/components/book_card_widget.dart';
|
||||
import 'package:openlib/ui/components/error_widget.dart';
|
||||
import 'package:openlib/ui/components/page_title_widget.dart';
|
||||
import 'package:openlib/ui/components/book_card_widget.dart';
|
||||
import 'package:openlib/state/state.dart' show myLibraryProvider;
|
||||
import 'package:openlib/ui/extensions.dart';
|
||||
import 'package:openlib/ui/mybook_page.dart';
|
||||
|
||||
class MyLibraryPage extends ConsumerWidget {
|
||||
const MyLibraryPage({super.key});
|
||||
@ -35,6 +39,7 @@ class MyLibraryPage extends ConsumerWidget {
|
||||
author: i.author ?? "",
|
||||
publisher: i.publisher ?? "",
|
||||
thumbnail: i.thumbnail,
|
||||
info: i.info,
|
||||
link: i.link,
|
||||
onClick: () {
|
||||
Navigator.push(context, MaterialPageRoute(
|
||||
|
@ -1,6 +1,18 @@
|
||||
// Dart imports:
|
||||
import 'dart:io' show Platform;
|
||||
|
||||
// Flutter imports:
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// Package imports:
|
||||
import 'package:flutter_pdfview/flutter_pdfview.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:open_file/open_file.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
// Project imports:
|
||||
import 'package:openlib/services/files.dart' show getFilePath;
|
||||
|
||||
import 'package:openlib/state/state.dart'
|
||||
show
|
||||
filePathProvider,
|
||||
@ -9,11 +21,6 @@ import 'package:openlib/state/state.dart'
|
||||
savePdfState,
|
||||
openPdfWithExternalAppProvider,
|
||||
getBookPosition;
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:open_file/open_file.dart';
|
||||
import 'dart:io' show Platform;
|
||||
|
||||
import 'package:openlib/services/files.dart' show getFilePath;
|
||||
|
||||
Future<void> launchPdfViewer(
|
||||
{required String fileName,
|
||||
@ -22,7 +29,7 @@ Future<void> launchPdfViewer(
|
||||
bool openWithExternalApp = ref.watch(openPdfWithExternalAppProvider);
|
||||
if (openWithExternalApp) {
|
||||
String path = await getFilePath(fileName);
|
||||
await OpenFile.open(path);
|
||||
await OpenFile.open(path, linuxByProcess: true);
|
||||
} else {
|
||||
Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) {
|
||||
return PdfView(
|
||||
@ -77,8 +84,7 @@ class _PdfViewState extends ConsumerState<PdfView> {
|
||||
}
|
||||
|
||||
class PdfViewer extends ConsumerStatefulWidget {
|
||||
const PdfViewer({Key? key, required this.filePath, required this.fileName})
|
||||
: super(key: key);
|
||||
const PdfViewer({super.key, required this.filePath, required this.fileName});
|
||||
|
||||
final String filePath;
|
||||
final String fileName;
|
||||
|
@ -1,19 +1,20 @@
|
||||
// Flutter imports:
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// Package imports:
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
import 'package:openlib/ui/extensions.dart';
|
||||
// Project imports:
|
||||
import 'package:openlib/state/state.dart' show searchProvider;
|
||||
import 'package:openlib/ui/book_info_page.dart';
|
||||
import 'package:openlib/ui/components/book_card_widget.dart';
|
||||
import 'package:openlib/ui/components/error_widget.dart';
|
||||
import 'package:openlib/ui/components/page_title_widget.dart';
|
||||
import 'package:openlib/ui/components/book_card_widget.dart';
|
||||
import 'package:openlib/state/state.dart' show searchProvider;
|
||||
import 'package:openlib/ui/extensions.dart';
|
||||
|
||||
class ResultPage extends ConsumerWidget {
|
||||
const ResultPage({
|
||||
super.key,
|
||||
required this.searchQuery,
|
||||
});
|
||||
const ResultPage({super.key, required this.searchQuery});
|
||||
|
||||
final String searchQuery;
|
||||
|
||||
@ -23,7 +24,7 @@ class ResultPage extends ConsumerWidget {
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
title: const Text("Openlib"),
|
||||
titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
),
|
||||
@ -45,7 +46,8 @@ class ResultPage extends ConsumerWidget {
|
||||
title: i.title,
|
||||
author: i.author ?? "unknown",
|
||||
publisher: i.publisher ?? "unknown",
|
||||
thumbnail: i.thumbnail!,
|
||||
thumbnail: i.thumbnail ?? '',
|
||||
info: i.info ?? '',
|
||||
link: i.link,
|
||||
onClick: () {
|
||||
Navigator.push(context, MaterialPageRoute(
|
||||
@ -118,6 +120,7 @@ class ResultPage extends ConsumerWidget {
|
||||
height: 25,
|
||||
child: CircularProgressIndicator(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
strokeCap: StrokeCap.round,
|
||||
),
|
||||
))
|
||||
],
|
||||
|
@ -1,30 +1,45 @@
|
||||
// Flutter imports:
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// Package imports:
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:openlib/ui/results_page.dart';
|
||||
|
||||
// Project imports:
|
||||
import 'package:openlib/ui/components/page_title_widget.dart';
|
||||
import 'package:openlib/ui/results_page.dart';
|
||||
import 'components/snack_bar_widget.dart';
|
||||
|
||||
import 'package:openlib/state/state.dart'
|
||||
show
|
||||
searchQueryProvider,
|
||||
selectedTypeState,
|
||||
selectedSortState,
|
||||
selectedFileTypeState,
|
||||
typeValues,
|
||||
sortValues;
|
||||
fileType,
|
||||
sortValues,
|
||||
enableFiltersState;
|
||||
|
||||
class SearchPage extends ConsumerWidget {
|
||||
const SearchPage({Key? key}) : super(key: key);
|
||||
const SearchPage({super.key});
|
||||
|
||||
void onSubmit(BuildContext context, WidgetRef ref) {
|
||||
Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) {
|
||||
return ResultPage(
|
||||
searchQuery: ref.read(searchQueryProvider),
|
||||
);
|
||||
}));
|
||||
if (ref.read(searchQueryProvider).isNotEmpty) {
|
||||
ref.read(enableFiltersState.notifier).state = true;
|
||||
Navigator.push(context,
|
||||
MaterialPageRoute(builder: (BuildContext context) {
|
||||
return ResultPage(searchQuery: ref.read(searchQueryProvider));
|
||||
}));
|
||||
} else {
|
||||
showSnackBar(context: context, message: 'Search field is empty');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final dropdownTypeValue = ref.watch(selectedTypeState);
|
||||
final dropdownSortValue = ref.watch(selectedSortState);
|
||||
final dropDownFileTypeValue = ref.watch(selectedFileTypeState);
|
||||
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
@ -161,7 +176,48 @@ class SearchPage extends ConsumerWidget {
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 7, right: 7, top: 19),
|
||||
child: SizedBox(
|
||||
width: 165,
|
||||
child: DropdownButtonFormField(
|
||||
decoration: InputDecoration(
|
||||
labelText: 'File type',
|
||||
labelStyle: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
enabledBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.grey, width: 2),
|
||||
borderRadius: BorderRadius.all(Radius.circular(50)),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
width: 2),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(50)),
|
||||
),
|
||||
),
|
||||
value: dropDownFileTypeValue,
|
||||
items: fileType.map<DropdownMenuItem<String>>((String value) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: value,
|
||||
child: Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
fontSize: 12, fontWeight: FontWeight.bold),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (String? val) {
|
||||
ref.read(selectedFileTypeState.notifier).state =
|
||||
val ?? 'All';
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -1,20 +1,62 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
// Dart imports:
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:openlib/ui/components/page_title_widget.dart';
|
||||
// Flutter imports:
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
// Package imports:
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:openlib/services/files.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
// Project imports:
|
||||
import 'package:openlib/services/database.dart';
|
||||
import 'package:openlib/ui/about_page.dart';
|
||||
import 'package:openlib/ui/components/page_title_widget.dart';
|
||||
|
||||
import 'package:openlib/state/state.dart'
|
||||
show
|
||||
themeModeProvider,
|
||||
openPdfWithExternalAppProvider,
|
||||
openEpubWithExternalAppProvider,
|
||||
dbProvider;
|
||||
openEpubWithExternalAppProvider;
|
||||
|
||||
Future<void> requestStoragePermission() async {
|
||||
bool permissionGranted = false;
|
||||
// Check whether the device is running Android 11 or higher
|
||||
DeviceInfoPlugin plugin = DeviceInfoPlugin();
|
||||
AndroidDeviceInfo android = await plugin.androidInfo;
|
||||
// Android < 11
|
||||
if (android.version.sdkInt < 33) {
|
||||
if (await Permission.storage.request().isGranted) {
|
||||
permissionGranted = true;
|
||||
} else if (await Permission.storage.request().isPermanentlyDenied) {
|
||||
await openAppSettings();
|
||||
}
|
||||
}
|
||||
// Android > 11
|
||||
else {
|
||||
if (await Permission.manageExternalStorage.request().isGranted) {
|
||||
permissionGranted = true;
|
||||
} else if (await Permission.manageExternalStorage
|
||||
.request()
|
||||
.isPermanentlyDenied) {
|
||||
await openAppSettings();
|
||||
} else if (await Permission.manageExternalStorage.request().isDenied) {
|
||||
permissionGranted = false;
|
||||
}
|
||||
}
|
||||
print("Storage permission status: $permissionGranted");
|
||||
}
|
||||
|
||||
class SettingsPage extends ConsumerWidget {
|
||||
const SettingsPage({Key? key}) : super(key: key);
|
||||
const SettingsPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
MyLibraryDb dataBase = MyLibraryDb.instance;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 5, right: 5, top: 10),
|
||||
child: SingleChildScrollView(
|
||||
@ -41,7 +83,12 @@ class SettingsPage extends ConsumerWidget {
|
||||
onChanged: (bool value) {
|
||||
ref.read(themeModeProvider.notifier).state =
|
||||
value == true ? ThemeMode.dark : ThemeMode.light;
|
||||
ref.read(dbProvider).savePreference('darkMode', value);
|
||||
dataBase.savePreference('darkMode', value);
|
||||
if (Platform.isAndroid) {
|
||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||
systemNavigationBarColor:
|
||||
value ? Colors.black : Colors.grey.shade200));
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
@ -63,9 +110,7 @@ class SettingsPage extends ConsumerWidget {
|
||||
onChanged: (bool value) {
|
||||
ref.read(openPdfWithExternalAppProvider.notifier).state =
|
||||
value;
|
||||
ref
|
||||
.read(dbProvider)
|
||||
.savePreference('openPdfwithExternalApp', value);
|
||||
dataBase.savePreference('openPdfwithExternalApp', value);
|
||||
},
|
||||
)
|
||||
],
|
||||
@ -89,13 +134,37 @@ class SettingsPage extends ConsumerWidget {
|
||||
onChanged: (bool value) {
|
||||
ref.read(openEpubWithExternalAppProvider.notifier).state =
|
||||
value;
|
||||
ref
|
||||
.read(dbProvider)
|
||||
.savePreference('openEpubwithExternalApp', value);
|
||||
dataBase.savePreference('openEpubwithExternalApp', value);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
_PaddedContainer(
|
||||
onClick: () async {
|
||||
final currentDirectory =
|
||||
await dataBase.getPreference('bookStorageDirectory');
|
||||
String? pickedDirectory =
|
||||
await FilePicker.platform.getDirectoryPath();
|
||||
if (pickedDirectory == null) {
|
||||
return;
|
||||
}
|
||||
await requestStoragePermission();
|
||||
// Attempt moving existing books to the new directory
|
||||
moveFolderContents(currentDirectory, pickedDirectory);
|
||||
dataBase.savePreference(
|
||||
'bookStorageDirectory', pickedDirectory);
|
||||
},
|
||||
children: [
|
||||
Text(
|
||||
"Change storage path",
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
),
|
||||
),
|
||||
Icon(Icons.folder),
|
||||
]),
|
||||
_PaddedContainer(
|
||||
onClick: () {
|
||||
Navigator.push(context,
|
||||
@ -122,8 +191,7 @@ class SettingsPage extends ConsumerWidget {
|
||||
}
|
||||
|
||||
class _PaddedContainer extends StatelessWidget {
|
||||
const _PaddedContainer({Key? key, this.onClick, required this.children})
|
||||
: super(key: key);
|
||||
const _PaddedContainer({this.onClick, required this.children});
|
||||
|
||||
final VoidCallback? onClick;
|
||||
final List<Widget> children;
|
||||
|
@ -1,12 +1,19 @@
|
||||
// Flutter imports:
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:openlib/ui/extensions.dart';
|
||||
|
||||
// Package imports:
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
// Project imports:
|
||||
import 'package:openlib/ui/extensions.dart';
|
||||
|
||||
final secondaryColor = '#FB0101'.toColor();
|
||||
|
||||
ThemeData lightTheme = ThemeData(
|
||||
primaryColor: Colors.white,
|
||||
colorScheme: ColorScheme.light(
|
||||
primary: Colors.white,
|
||||
secondary: '#FB0101'.toColor(),
|
||||
secondary: secondaryColor,
|
||||
tertiary: Colors.black,
|
||||
tertiaryContainer: '#F2F2F2'.toColor(),
|
||||
),
|
||||
@ -31,18 +38,20 @@ ThemeData lightTheme = ThemeData(
|
||||
fontFamily: GoogleFonts.nunito().fontFamily,
|
||||
useMaterial3: true,
|
||||
textSelectionTheme: TextSelectionThemeData(
|
||||
selectionColor: '#FB0101'.toColor(),
|
||||
selectionHandleColor: '#FB0101'.toColor(),
|
||||
selectionColor: secondaryColor,
|
||||
selectionHandleColor: secondaryColor,
|
||||
),
|
||||
);
|
||||
|
||||
ThemeData darkTheme = ThemeData(
|
||||
primaryColor: Colors.black,
|
||||
scaffoldBackgroundColor: Colors.black,
|
||||
colorScheme: ColorScheme.dark(
|
||||
primary: Colors.black,
|
||||
secondary: '#FB0101'.toColor(),
|
||||
secondary: secondaryColor,
|
||||
tertiary: Colors.white,
|
||||
tertiaryContainer: '#2B2B2B'.toColor(),
|
||||
tertiaryContainer: '#141414'.toColor(),
|
||||
surface: Colors.black,
|
||||
),
|
||||
textTheme: TextTheme(
|
||||
displayLarge: const TextStyle(
|
||||
@ -66,7 +75,7 @@ ThemeData darkTheme = ThemeData(
|
||||
fontFamily: GoogleFonts.nunito().fontFamily,
|
||||
useMaterial3: true,
|
||||
textSelectionTheme: TextSelectionThemeData(
|
||||
selectionColor: '#FB0101'.toColor(),
|
||||
selectionHandleColor: '#FB0101'.toColor(),
|
||||
selectionColor: secondaryColor,
|
||||
selectionHandleColor: secondaryColor,
|
||||
),
|
||||
);
|
||||
|
@ -1,12 +1,17 @@
|
||||
// Flutter imports:
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
|
||||
import 'extensions.dart';
|
||||
import 'package:openlib/ui/components/page_title_widget.dart';
|
||||
// Package imports:
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
// Project imports:
|
||||
import 'package:openlib/ui/components/error_widget.dart';
|
||||
import 'package:openlib/ui/results_page.dart';
|
||||
import 'package:openlib/state/state.dart' show getTrendingBooks;
|
||||
import 'extensions.dart';
|
||||
|
||||
import 'package:openlib/state/state.dart'
|
||||
show getTrendingBooks, enableFiltersState;
|
||||
|
||||
class TrendingPage extends ConsumerWidget {
|
||||
const TrendingPage({super.key});
|
||||
@ -17,127 +22,131 @@ class TrendingPage extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final trendingBooks = ref.watch(getTrendingBooks);
|
||||
return trendingBooks.when(data: (data) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 5, right: 5, top: 10),
|
||||
child: CustomScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
slivers: [
|
||||
const SliverToBoxAdapter(
|
||||
child: TitleText("Trending"),
|
||||
),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.all(5),
|
||||
sliver: SliverGrid(
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 10.0,
|
||||
crossAxisSpacing: 13.0,
|
||||
mainAxisExtent: 205,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
Navigator.push(context,
|
||||
MaterialPageRoute(builder: (BuildContext context) {
|
||||
return ResultPage(
|
||||
searchQuery: data[index].title!,
|
||||
);
|
||||
}));
|
||||
},
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
CachedNetworkImage(
|
||||
height: imageHeight,
|
||||
width: imageWidth,
|
||||
imageUrl: data[index].thumbnail!,
|
||||
imageBuilder: (context, imageProvider) =>
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Colors.grey,
|
||||
spreadRadius: 0.1,
|
||||
blurRadius: 1)
|
||||
],
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(5)),
|
||||
image: DecorationImage(
|
||||
image: imageProvider,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
),
|
||||
placeholder: (context, url) => Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
color: "#E3E8E9".toColor(),
|
||||
),
|
||||
height: imageHeight,
|
||||
width: imageWidth,
|
||||
),
|
||||
errorWidget: (context, url, error) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
color: Colors.grey,
|
||||
),
|
||||
return trendingBooks.when(
|
||||
skipLoadingOnRefresh: false,
|
||||
data: (data) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 5, right: 5, top: 10),
|
||||
child: CustomScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.all(5),
|
||||
sliver: SliverGrid(
|
||||
gridDelegate:
|
||||
const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 10.0,
|
||||
crossAxisSpacing: 13.0,
|
||||
mainAxisExtent: 205,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
ref.read(enableFiltersState.notifier).state = false;
|
||||
Navigator.push(context, MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return ResultPage(
|
||||
searchQuery: data[index].title!);
|
||||
}));
|
||||
},
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
CachedNetworkImage(
|
||||
height: imageHeight,
|
||||
width: imageWidth,
|
||||
child: const Center(
|
||||
child: Icon(Icons.image_rounded),
|
||||
imageUrl: data[index].thumbnail!,
|
||||
imageBuilder: (context, imageProvider) =>
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Colors.grey,
|
||||
spreadRadius: 0.1,
|
||||
blurRadius: 1)
|
||||
],
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(5)),
|
||||
image: DecorationImage(
|
||||
image: imageProvider,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
child: SizedBox(
|
||||
width: imageWidth,
|
||||
child: Text(
|
||||
data[index].title!,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.displayMedium,
|
||||
maxLines: 2,
|
||||
placeholder: (context, url) => Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
color: "#E3E8E9".toColor(),
|
||||
),
|
||||
height: imageHeight,
|
||||
width: imageWidth,
|
||||
),
|
||||
errorWidget: (context, url, error) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
BorderRadius.circular(5),
|
||||
color: Colors.grey,
|
||||
),
|
||||
height: imageHeight,
|
||||
width: imageWidth,
|
||||
child: const Center(
|
||||
child: Icon(Icons.image_rounded),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
);
|
||||
},
|
||||
childCount: data.length,
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
child: SizedBox(
|
||||
width: imageWidth,
|
||||
child: Text(
|
||||
data[index].title!,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.displayMedium,
|
||||
maxLines: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
);
|
||||
},
|
||||
childCount: data.length,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}, error: (error, _) {
|
||||
return CustomErrorWidget(
|
||||
error: error,
|
||||
stackTrace: _,
|
||||
onRefresh: () {
|
||||
// ignore: unused_result
|
||||
ref.refresh(getTrendingBooks);
|
||||
);
|
||||
},
|
||||
);
|
||||
}, loading: () {
|
||||
return Center(
|
||||
child: SizedBox(
|
||||
width: 25,
|
||||
height: 25,
|
||||
child: CircularProgressIndicator(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
));
|
||||
});
|
||||
error: (error, _) {
|
||||
return CustomErrorWidget(
|
||||
error: error,
|
||||
stackTrace: _,
|
||||
onRefresh: () {
|
||||
// ignore: unused_result
|
||||
ref.refresh(getTrendingBooks);
|
||||
},
|
||||
);
|
||||
},
|
||||
loading: () {
|
||||
return Center(
|
||||
child: SizedBox(
|
||||
width: 25,
|
||||
height: 25,
|
||||
child: CircularProgressIndicator(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
strokeCap: StrokeCap.round,
|
||||
),
|
||||
));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
81
lib/ui/webview_page.dart
Normal file
@ -0,0 +1,81 @@
|
||||
// Flutter imports:
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// Package imports:
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
|
||||
class Webview extends ConsumerStatefulWidget {
|
||||
const Webview({super.key, required this.url});
|
||||
final String url;
|
||||
@override
|
||||
// ignore: library_private_types_in_public_api
|
||||
_WebviewState createState() => _WebviewState();
|
||||
}
|
||||
|
||||
class _WebviewState extends ConsumerState<Webview> {
|
||||
final GlobalKey webViewKey = GlobalKey();
|
||||
|
||||
InAppWebViewController? webViewController;
|
||||
|
||||
final urlController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
title: const Text("Solve Captcha"),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
InAppWebView(
|
||||
key: webViewKey,
|
||||
initialUrlRequest: URLRequest(url: WebUri(widget.url)),
|
||||
onWebViewCreated: (controller) {
|
||||
webViewController = controller;
|
||||
},
|
||||
onLoadStart: (controller, url) {},
|
||||
onLoadStop: (controller, url) async {
|
||||
String query =
|
||||
"""var paragraphTag=document.querySelector('p[class="mb-4 text-xl font-bold"]');var anchorTagHref=paragraphTag.querySelector('a').href;var url=()=>{return anchorTagHref};url();""";
|
||||
String? mirrorLink = await webViewController
|
||||
?.evaluateJavascript(source: query);
|
||||
// final ipfsUrl = widget.url
|
||||
// .replaceAll("slow_download", "ipfs_downloads")
|
||||
// .replaceAll("/0/2", "");
|
||||
|
||||
// await webViewController?.loadUrl(
|
||||
// urlRequest: URLRequest(
|
||||
// url: WebUri('https://example.com/new-page')));
|
||||
if (mirrorLink != null) {
|
||||
Future.delayed(const Duration(milliseconds: 70), () {
|
||||
// ignore: use_build_context_synchronously
|
||||
Navigator.pop(context, mirrorLink);
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -6,9 +6,13 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <open_file_linux/open_file_linux_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) open_file_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "OpenFileLinuxPlugin");
|
||||
open_file_linux_plugin_register_with_registrar(open_file_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||
|
@ -3,6 +3,7 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
open_file_linux
|
||||
url_launcher_linux
|
||||
)
|
||||
|
||||
|
@ -1 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||
|
@ -1 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||
|
@ -5,12 +5,20 @@
|
||||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import device_info_plus
|
||||
import flutter_inappwebview_macos
|
||||
import open_file_mac
|
||||
import path_provider_foundation
|
||||
import sqflite
|
||||
import share_plus
|
||||
import sqflite_darwin
|
||||
import url_launcher_macos
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||
InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin"))
|
||||
OpenFilePlugin.register(with: registry.registrar(forPlugin: "OpenFilePlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||
}
|
||||
|
43
macos/Podfile
Normal file
@ -0,0 +1,43 @@
|
||||
platform :osx, '10.14'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
project 'Runner', {
|
||||
'Debug' => :debug,
|
||||
'Profile' => :release,
|
||||
'Release' => :release,
|
||||
}
|
||||
|
||||
def flutter_root
|
||||
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
|
||||
unless File.exist?(generated_xcode_build_settings_path)
|
||||
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
|
||||
end
|
||||
|
||||
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||
return matches[1].strip if matches
|
||||
end
|
||||
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
|
||||
end
|
||||
|
||||
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||
|
||||
flutter_macos_podfile_setup
|
||||
|
||||
target 'Runner' do
|
||||
use_frameworks!
|
||||
use_modular_headers!
|
||||
|
||||
flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
|
||||
target 'RunnerTests' do
|
||||
inherit! :search_paths
|
||||
end
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
flutter_additional_macos_build_settings(target)
|
||||
end
|
||||
end
|
61
macos/Podfile.lock
Normal file
@ -0,0 +1,61 @@
|
||||
PODS:
|
||||
- device_info_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- flutter_inappwebview_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- OrderedSet (~> 5.0)
|
||||
- FlutterMacOS (1.0.0)
|
||||
- open_file_mac (0.0.1):
|
||||
- FlutterMacOS
|
||||
- OrderedSet (5.0.0)
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- sqflite (0.0.3):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- url_launcher_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
|
||||
DEPENDENCIES:
|
||||
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
|
||||
- flutter_inappwebview_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos`)
|
||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||
- open_file_mac (from `Flutter/ephemeral/.symlinks/plugins/open_file_mac/macos`)
|
||||
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/darwin`)
|
||||
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- OrderedSet
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
device_info_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos
|
||||
flutter_inappwebview_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos
|
||||
FlutterMacOS:
|
||||
:path: Flutter/ephemeral
|
||||
open_file_mac:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/open_file_mac/macos
|
||||
path_provider_foundation:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
|
||||
sqflite:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/sqflite/darwin
|
||||
url_launcher_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
device_info_plus: ce1b7762849d3ec103d0e0517299f2db7ad60720
|
||||
flutter_inappwebview_macos: 9600c9df9fdb346aaa8933812009f8d94304203d
|
||||
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||
open_file_mac: 0e554648e2a87ce59e9438e3e5ca3e552e90d89a
|
||||
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
|
||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
|
||||
url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399
|
||||
|
||||
PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367
|
||||
|
||||
COCOAPODS: 1.15.2
|
@ -27,6 +27,8 @@
|
||||
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
|
||||
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
|
||||
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
|
||||
94DC8CF5FF5142DBE4D793D5 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0F4FBA8BD682AFDC387908E5 /* Pods_Runner.framework */; };
|
||||
C7F7B62346249DBF21B09589 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 597839FED6E06332F68C6ADA /* Pods_RunnerTests.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@ -60,11 +62,15 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
026D244AA1C7FAEB6738F1E2 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
0C77C89AD60C9098EEDED45D /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
0F4FBA8BD682AFDC387908E5 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
1A522043CF646715895EA782 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||
331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
|
||||
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
|
||||
33CC10ED2044A3C60003C045 /* libgen.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "libgen.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
33CC10ED2044A3C60003C045 /* libgen.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = libgen.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
|
||||
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
|
||||
@ -76,8 +82,12 @@
|
||||
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
|
||||
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
|
||||
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
|
||||
4CC42A086113EDD186C59134 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
597839FED6E06332F68C6ADA /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
|
||||
B3FD517C4809A4AF4328C1B7 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
BE75E83973607FF4034DF300 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -85,6 +95,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C7F7B62346249DBF21B09589 /* Pods_RunnerTests.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -92,6 +103,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
94DC8CF5FF5142DBE4D793D5 /* Pods_Runner.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -125,6 +137,7 @@
|
||||
331C80D6294CF71000263BE5 /* RunnerTests */,
|
||||
33CC10EE2044A3C60003C045 /* Products */,
|
||||
D73912EC22F37F3D000D13A0 /* Frameworks */,
|
||||
F9274C19F91F0638504DB3B3 /* Pods */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@ -175,10 +188,26 @@
|
||||
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0F4FBA8BD682AFDC387908E5 /* Pods_Runner.framework */,
|
||||
597839FED6E06332F68C6ADA /* Pods_RunnerTests.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F9274C19F91F0638504DB3B3 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B3FD517C4809A4AF4328C1B7 /* Pods-Runner.debug.xcconfig */,
|
||||
1A522043CF646715895EA782 /* Pods-Runner.release.xcconfig */,
|
||||
0C77C89AD60C9098EEDED45D /* Pods-Runner.profile.xcconfig */,
|
||||
026D244AA1C7FAEB6738F1E2 /* Pods-RunnerTests.debug.xcconfig */,
|
||||
BE75E83973607FF4034DF300 /* Pods-RunnerTests.release.xcconfig */,
|
||||
4CC42A086113EDD186C59134 /* Pods-RunnerTests.profile.xcconfig */,
|
||||
);
|
||||
name = Pods;
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@ -186,6 +215,7 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||
buildPhases = (
|
||||
EAFD152091A84CC666BC9C95 /* [CP] Check Pods Manifest.lock */,
|
||||
331C80D1294CF70F00263BE5 /* Sources */,
|
||||
331C80D2294CF70F00263BE5 /* Frameworks */,
|
||||
331C80D3294CF70F00263BE5 /* Resources */,
|
||||
@ -204,11 +234,13 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
B03B73F3042AD92BAE2F1724 /* [CP] Check Pods Manifest.lock */,
|
||||
33CC10E92044A3C60003C045 /* Sources */,
|
||||
33CC10EA2044A3C60003C045 /* Frameworks */,
|
||||
33CC10EB2044A3C60003C045 /* Resources */,
|
||||
33CC110E2044A8840003C045 /* Bundle Framework */,
|
||||
3399D490228B24CF009A79C7 /* ShellScript */,
|
||||
A920CB9F33F83AF0095EB7FC /* [CP] Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@ -227,7 +259,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0920;
|
||||
LastUpgradeCheck = 1300;
|
||||
LastUpgradeCheck = 1510;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
331C80D4294CF70F00263BE5 = {
|
||||
@ -328,6 +360,67 @@
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
|
||||
};
|
||||
A920CB9F33F83AF0095EB7FC /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
B03B73F3042AD92BAE2F1724 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
EAFD152091A84CC666BC9C95 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
@ -379,6 +472,7 @@
|
||||
/* Begin XCBuildConfiguration section */
|
||||
331C80DB294CF71000263BE5 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 026D244AA1C7FAEB6738F1E2 /* Pods-RunnerTests.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
@ -393,6 +487,7 @@
|
||||
};
|
||||
331C80DC294CF71000263BE5 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = BE75E83973607FF4034DF300 /* Pods-RunnerTests.release.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
@ -407,6 +502,7 @@
|
||||
};
|
||||
331C80DD294CF71000263BE5 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 4CC42A086113EDD186C59134 /* Pods-RunnerTests.profile.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1300"
|
||||
LastUpgradeVersion = "1510"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@ -4,4 +4,7 @@
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
@NSApplicationMain
|
||||
@main
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
|
@ -8,5 +8,7 @@
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -4,5 +4,7 @@
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
590
pubspec.lock
32
pubspec.yaml
@ -1,8 +1,8 @@
|
||||
name: openlib
|
||||
description: An Open source app to download and read books from shadow library (Anna`s Archive)
|
||||
description: An Open source app to download and read books from shadow library (Anna`s Archive)
|
||||
# The following line prevents the package from being accidentally published to
|
||||
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
|
||||
# The following defines the version and build number for your application.
|
||||
# A version number is three numbers separated by dots, like 1.2.43
|
||||
@ -16,10 +16,10 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 1.0.1+1
|
||||
version: 1.0.10+13
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.5 <4.0.0'
|
||||
sdk: ">=3.3.0 <4.0.0"
|
||||
|
||||
# Dependencies specify other packages that your package needs in order to work.
|
||||
# To automatically upgrade your package dependencies to the latest versions
|
||||
@ -34,24 +34,24 @@ dependencies:
|
||||
|
||||
google_nav_bar: ^5.0.6
|
||||
|
||||
epub_view: ^3.2.0
|
||||
flutter_pdfview: ^1.2.7
|
||||
vocsy_epub_viewer: ^2.0.0
|
||||
epub_view: ^3.2.0
|
||||
vocsy_epub_viewer: ^3.0.0
|
||||
# syncfusion_flutter_pdfviewer: ^22.2.5
|
||||
# pdfx: ^2.4.0
|
||||
|
||||
dio: ^5.3.0
|
||||
dio: ^5.4.0
|
||||
html: ^0.15.4
|
||||
|
||||
sqflite: ^2.3.0
|
||||
path_provider: ^2.0.15
|
||||
permission_handler: ^10.4.3
|
||||
permission_handler: ^11.2.0
|
||||
open_file: ^3.3.2
|
||||
|
||||
flutter_svg: ^2.0.7
|
||||
google_fonts: ^5.1.0
|
||||
google_fonts: ^6.1.0
|
||||
|
||||
cached_network_image: ^3.2.3
|
||||
cached_network_image: 3.3.0
|
||||
|
||||
sqflite_common_ffi: ^2.3.0+2
|
||||
url_launcher: ^6.1.12
|
||||
@ -59,8 +59,16 @@ dependencies:
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.2
|
||||
dev: ^1.0.0
|
||||
crypto: ^3.0.3
|
||||
file_picker: ^8.1.2
|
||||
device_info_plus: ^10.1.2
|
||||
flutter_inappwebview: ^6.0.0
|
||||
http: ^1.2.2
|
||||
path: ^1.9.0
|
||||
share_plus: ^10.1.0
|
||||
|
||||
dev_dependencies:
|
||||
import_sorter: ^4.6.0
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
@ -70,8 +78,7 @@ dev_dependencies:
|
||||
# activated in the `analysis_options.yaml` file located at the root of your
|
||||
# package. See that file for information about deactivating specific lint
|
||||
# rules and activating additional ones.
|
||||
flutter_lints: ^2.0.0
|
||||
|
||||
flutter_lints: ^3.0.1
|
||||
|
||||
flutter_icons:
|
||||
android: "launcher_icon"
|
||||
@ -83,7 +90,6 @@ flutter_icons:
|
||||
|
||||
# The following section is specific to Flutter packages.
|
||||
flutter:
|
||||
|
||||
# The following line ensures that the Material Icons font is
|
||||
# included with your application, so that you can use the icons in
|
||||
# the material Icons class.
|
||||
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 447 KiB |
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 536 KiB After Width: | Height: | Size: 106 KiB |
Before Width: | Height: | Size: 378 KiB After Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 227 KiB After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 379 KiB After Width: | Height: | Size: 81 KiB |
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 271 KiB |
Before Width: | Height: | Size: 231 KiB After Width: | Height: | Size: 28 KiB |
@ -5,9 +5,13 @@
|
||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
// Flutter imports:
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// Package imports:
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
// Project imports:
|
||||
import 'package:openlib/main.dart';
|
||||
|
||||
void main() {
|
||||
|
@ -10,6 +10,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake)
|
||||
# https://github.com/flutter/flutter/issues/57146.
|
||||
set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
|
||||
|
||||
# Set fallback configurations for older versions of the flutter tool.
|
||||
if (NOT DEFINED FLUTTER_TARGET_PLATFORM)
|
||||
set(FLUTTER_TARGET_PLATFORM "windows-x64")
|
||||
endif()
|
||||
|
||||
# === Flutter Library ===
|
||||
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
|
||||
|
||||
@ -92,7 +97,7 @@ add_custom_command(
|
||||
COMMAND ${CMAKE_COMMAND} -E env
|
||||
${FLUTTER_TOOL_ENVIRONMENT}
|
||||
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
|
||||
windows-x64 $<CONFIG>
|
||||
${FLUTTER_TARGET_PLATFORM} $<CONFIG>
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_target(flutter_assemble DEPENDS
|
||||
|
@ -6,12 +6,18 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h>
|
||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||
#include <share_plus/share_plus_windows_plugin_c_api.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi"));
|
||||
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
|
||||
SharePlusWindowsPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||
}
|
||||
|
@ -3,7 +3,9 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
flutter_inappwebview_windows
|
||||
permission_handler_windows
|
||||
share_plus
|
||||
url_launcher_windows
|
||||
)
|
||||
|
||||
|
@ -31,6 +31,11 @@ bool FlutterWindow::OnCreate() {
|
||||
this->Show();
|
||||
});
|
||||
|
||||
// Flutter can complete the first frame before the "show window" callback is
|
||||
// registered. The following call ensures a frame is pending to ensure the
|
||||
// window is shown. It is a no-op if the first frame hasn't completed yet.
|
||||
flutter_controller_->ForceRedraw();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|