82 Commits

Author SHA1 Message Date
659b3426e8 version upgrade 2024-11-01 20:16:46 +05:30
7b6394f6ad Fixed gradle error on build 2024-11-01 19:07:48 +05:30
2df0d1f655 Fixed grey screen on search and changed share button position 2024-10-30 21:36:27 +05:30
18723e1d78 Added book title and thumbnail sharing feature with image download and preview support (#126)
* Added Share button with minimal functionality at this moment.

* Feature: Add functionality to share book title with thumbnail

- Download image from URL and save it to a temporary directory.
- Share the book title along with the thumbnail using `Share.shareXFiles`.
- Improved sharing experience by combining text and image in a single share intent.
2024-10-25 18:25:02 +05:30
7316c64685 remove unused imports 2024-10-19 18:20:07 +05:30
43aba47453 Modified Home page design to contain both trending and genres page 2024-10-19 18:17:59 +05:30
e79042ebc1 Categories (#123)
* added categories

* added category info and thumbnails
2024-10-19 17:48:27 +05:30
1e84848d39 Fixed trending page request connection error 2024-10-13 20:36:54 +05:30
78a50e28ff Merge pull request #119 from Nav-jangra/trending
trending page error
2024-10-13 18:00:06 +05:30
fda962a46f git commit -m 'trending page is not working due to open lib not responding :FIXED' 2024-10-12 15:52:04 +05:30
a7ca206b10 Fixed Stuck on Solve Captcha Screen 2024-09-28 13:10:48 +05:30
6be9927b26 Updated build number in pubspec.yaml 2024-09-10 18:49:23 +05:30
ea86d76015 removed unused imports 2024-08-31 16:00:48 +05:30
f396f92463 Fixed "no mirror found" and "always redirected to captcha" issues 2024-08-31 14:17:50 +05:30
70e96b12e1 Merge pull request #100 from clovis-dugue/develop
Multiple features and updates
2024-08-27 12:49:40 +05:30
9a91cffb31 chore: update Podfile.lock 2024-08-26 19:32:44 +02:00
669e5f7a66 feat: move files when changing book directory in settings 2024-08-26 19:32:29 +02:00
6e1f75b11a Merge pull request #96 from clovis-dugue/fix/no-mirrors
fix: no mirrors found after anna's archive ui update
2024-08-24 11:11:29 +05:30
7f36ca98b7 feat: add a setting for the destination folder of downloaded books 2024-08-24 00:23:12 +02:00
1025552f2f chore: format README.md 2024-08-23 23:35:35 +02:00
11a7dca93c chore: organize imports 2024-08-23 23:35:09 +02:00
ccd60ac357 fix: macos internet permission 2024-08-23 20:09:55 +02:00
d0c8128631 fix: no mirrors found after anna's archive ui update 2024-08-22 22:26:55 +02:00
e1a42df7e7 chore: update macos build 2024-08-22 18:58:07 +02:00
50f907eff7 chore: update ios build 2024-08-22 18:57:56 +02:00
c0c0de58a5 fix: update dependencies to be compatible with the latest flutter version 2024-08-22 18:55:47 +02:00
f0940e2947 chore: remove in-app reader 2024-08-22 18:47:40 +02:00
62ff665b87 fix: no mirrors found after anna's archive ui update 2024-08-22 18:46:00 +02:00
67d894855a Update README.md 2024-03-31 22:16:19 +05:30
a3ca57772d Update README.md 2024-03-31 21:55:44 +05:30
d9b19a6ec3 Merge pull request #71 from inson1/patch-3
Update badges in Readme
2024-03-10 18:54:24 +05:30
e013713516 Update badges in Readme 2024-02-26 16:17:25 +01:00
d655a4bd32 fixed mirror not found error 2024-02-25 18:32:34 +05:30
9b696c7e9d updated about page 2024-02-15 22:53:41 +05:30
cbbe049ef8 reverted back to old version as the issue still exist 2024-02-15 22:52:33 +05:30
cddaeb553a updated version number 2024-02-15 22:31:16 +05:30
ced66d0d1c Merge branch 'main' of https://github.com/dstark5/Openlib 2024-02-15 22:26:27 +05:30
17dcbb05b3 Fixed no mirror and download failed error 2024-02-15 22:25:27 +05:30
27e43a358c Merge pull request #64 from inson1/patch-2
Fix dot in README.md
2024-02-07 15:06:02 +05:30
1715b73494 Fix dot in README.md 2024-02-06 10:37:42 +01:00
4c557ed28d fastlane description update 2024-02-05 20:32:12 +05:30
61a4e566df fastlane description update 2024-02-05 20:31:12 +05:30
2a2e5df118 Fixed grey screen while file downloading 2024-02-05 20:17:11 +05:30
aac2e807f0 Fixed gray screen error 2024-02-05 19:29:10 +05:30
95288271fa added warning for failed file checksum veification 2024-02-04 17:26:11 +05:30
73a294fb89 updated version details 2024-02-04 16:16:52 +05:30
c068714e52 added file verification with md5 checksum 2024-02-04 12:41:48 +05:30
5fa66ba42f Added F-droid link to README.md 2024-02-01 12:14:32 +05:30
034c3aaa5e Updated screenshots 2024-01-25 17:42:50 +05:30
c7ed1a3c4a Update README.md 2024-01-25 11:28:07 +05:30
7d550ea399 Merge pull request #61 from inson1/patch-1
Nicer Readme
2024-01-24 21:50:17 -08:00
dfc70cfce0 Update README.md 2024-01-24 18:17:26 +01:00
1aedffc919 Update README.md 2024-01-24 18:16:07 +01:00
7364f69b60 Update README.md 2024-01-24 18:14:25 +01:00
a342f80fee Merge pull request #56 from inson1/patch-1
Fixing Readme 2.0
2023-12-26 17:46:49 +05:30
3d56cd2c65 Fixing Readme 2.0
idk why the first PR wasnt merged
2023-12-25 14:59:38 +01:00
74793e5d65 Merge pull request #54 from inson1/fix-bad-badge-version
Fix bad badge version
2023-12-17 11:16:49 +05:30
88c1612184 Fixed result not found error 2023-12-16 21:45:51 -08:00
2924020eb7 Fix bad badge version 2023-12-17 06:04:22 +01:00
3bda1a7284 Fixed no result found and added new resources for trending books 2023-12-05 05:52:14 -08:00
b0579a5c15 updated version code 2023-11-21 21:44:49 -08:00
092f6029b6 Fixed unable to download due to change in annas archive 2023-11-20 23:47:40 -08:00
51283c3410 fixed no result found error 2023-11-03 06:05:03 -07:00
3a95d79c4a fixed bugs 2023-10-24 04:05:29 -07:00
9aeea38192 implemented mirror availabilty check 2023-10-24 03:42:05 -07:00
76c80be783 implemented cloudflare clearance 2023-10-23 23:59:38 -07:00
f71535438b added file type filter and fixed search not working 2023-10-16 06:55:32 -07:00
b536e1600a finished file type indicator 2023-10-12 23:41:24 -07:00
2ddc71d830 added file type indicator for book card 2023-10-12 06:00:59 -07:00
013cce078f removed ripple effect from bottom nav 2023-09-13 22:43:14 -07:00
7f3e255259 added systemNavigationBarColor to blend with bottom navbar 2023-09-12 00:21:50 -07:00
0825857231 Merge pull request #26 from basitali1509/feat/bottom-nav-bar-UI
FEEDBACK: Bottom Navigation Bar background color should be white in light mode
2023-09-12 08:44:34 +05:30
9255201049 Merge pull request #29 from basitali1509/fix/search-page
FIXED #28: Search process should not work when the search field is empty
2023-09-12 08:42:37 +05:30
ed8a6e01e9 Fixed Empty Search Field Bug 2023-09-12 02:11:19 +05:00
9def607737 bottom navbar background color update 2023-09-12 00:00:13 +05:00
6d0d417ce0 Merge branch 'main' of https://github.com/dstark5/Openlib into feat/bottom-nav-bar-UI 2023-09-11 23:35:52 +05:00
b127683003 Merge branch 'main' of https://github.com/basitali1509/Openlib into feat/bottom-nav-bar-UI 2023-09-11 23:32:27 +05:00
a6775b95a9 Merge remote-tracking branch 'original/main' 2023-09-11 23:30:27 +05:00
647687c0c9 Fixed download failing due to change in Anna's archive website 2023-09-11 23:29:23 +05:00
9dc7c67440 updated readme 2023-09-11 03:28:37 -07:00
ccd260d451 Fixed download failing due to change in Anna's archive website 2023-09-10 22:50:57 -07:00
6ac9f0c77a bottom navbar UI update 2023-09-09 22:31:06 +05:00
89 changed files with 3629 additions and 847 deletions

View File

@ -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
View File

@ -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 ([Annas Archive](https://annas-archive.org/)).
<img src="assets/icons/appIcon.png" width="150">
[![made-with-flutter](https://img.shields.io/badge/Made%20with-Flutter-4361ee.svg)](https://flutter.dev/) [![GPLv3 License](https://img.shields.io/badge/License-GPL%20v3-e63946.svg)](https://opensource.org/licenses/) ![version](https://img.shields.io/badge/version-1.0_beta-06d6a0)
# 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 ([Annas Archive](https://annas-archive.org/))
[![made-with-flutter](https://img.shields.io/badge/Made%20with-Flutter-4361ee.svg?style=for-the-badge)](https://flutter.dev/)
[![GPLv3 License](https://img.shields.io/badge/License-GPL%20v3-e63946.svg?style=for-the-badge)](https://opensource.org/licenses/)
[![Latest release](https://img.shields.io/github/release/dstark5/Openlib.svg?style=for-the-badge)](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 ([Annas Archive](https://annas-archive.org/)) . The App Has Built In Reader to Read Books.
## Description 📖
##### As [Annas Archive](https://annas-archive.org/) Doesn't Have An API.The App Works By Sending Request To Annas 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 ([Annas Archive](https://annas-archive.org/)). The App Has Built In Reader to Read Books
As [Annas Archive](https://annas-archive.org/) Doesn't Have An API. The App Works By Sending Request To Annas 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 Annas Archive](https://annas-archive.org/donate?tier=1).
## Donate 🎁
If you like Openlib, you're welcome to send a donation.
[Donate To Annas Archive.](https://annas-archive.org/donate?tier=1)
## License 📜
## License
[![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](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.

View File

@ -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 = "../.."
}

View File

@ -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"

View 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-----

View 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>

View File

@ -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
}
}

View File

@ -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

View File

@ -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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -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'>Annas Archive</a>). The App Has Built In Reader to Read Books.</p><p>As <i>Annas Archive</i> doesn't have an API, the app works by sending requests to <i>Annas 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'>Annas Archive</a>). The App Has Built In Reader to Read Books.
</p>
<p>
As <i>Annas Archive</i> doesn't have an API, the app works by sending requests to <i>Annas 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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>11.0</string>
<string>12.0</string>
</dict>
</plist>

View File

@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"

View File

@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"

44
ios/Podfile Normal file
View 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

View File

@ -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;

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1300"
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -4,4 +4,7 @@
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@ -1,7 +1,7 @@
import UIKit
import Flutter
@UIApplicationMain
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,

View File

@ -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,

View File

@ -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;

View File

@ -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 "";
}
}
}

View File

@ -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;
}
}

View File

@ -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) {

View 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;
}
}
}

View File

@ -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;
}
}

View 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 "";
}
}

View File

@ -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);

View File

@ -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;

View File

@ -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
View 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,
),
));
}));
}
}

View File

@ -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,
),
),
],
),
],
),

View File

@ -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) {

View File

@ -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;

View File

@ -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) {

View File

@ -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!');
}
}

View File

@ -1,3 +1,4 @@
// Flutter imports:
import 'package:flutter/material.dart';
class TitleText extends StatelessWidget {

View File

@ -1,3 +1,4 @@
// Flutter imports:
import 'package:flutter/material.dart';
void showSnackBar({required BuildContext context, required String message}) {

View File

@ -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;

View File

@ -1,3 +1,4 @@
// Flutter imports:
import 'package:flutter/material.dart';
extension ColorExtension on String {

154
lib/ui/home_page.dart Normal file
View 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),
),
),
),
),
),
],
),
);
}
}

View File

@ -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) {

View File

@ -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(

View File

@ -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;

View File

@ -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,
),
))
],

View File

@ -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';
},
),
),
),
],
),
),

View File

@ -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;

View File

@ -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,
),
);

View File

@ -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
View 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);
});
}
},
),
],
),
),
],
),
),
);
}
}

View File

@ -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);

View File

@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
open_file_linux
url_launcher_linux
)

View File

@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "ephemeral/Flutter-Generated.xcconfig"

View File

@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "ephemeral/Flutter-Generated.xcconfig"

View File

@ -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
View 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
View 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

View File

@ -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;

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1300"
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -4,4 +4,7 @@
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@ -1,7 +1,7 @@
import Cocoa
import FlutterMacOS
@NSApplicationMain
@main
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true

View File

@ -8,5 +8,7 @@
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>

View File

@ -4,5 +4,7 @@
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>

File diff suppressed because it is too large Load Diff

View File

@ -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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 447 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 536 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 378 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 227 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 379 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 231 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -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() {

View File

@ -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

View File

@ -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"));
}

View File

@ -3,7 +3,9 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
flutter_inappwebview_windows
permission_handler_windows
share_plus
url_launcher_windows
)

View File

@ -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;
}