Compare commits
49 Commits
v1.0.2-bet
...
v1.0.6-bet
Author | SHA1 | Date | |
---|---|---|---|
d655a4bd32 | |||
9b696c7e9d | |||
cbbe049ef8 | |||
cddaeb553a | |||
ced66d0d1c | |||
17dcbb05b3 | |||
27e43a358c | |||
1715b73494 | |||
4c557ed28d | |||
61a4e566df | |||
2a2e5df118 | |||
aac2e807f0 | |||
95288271fa | |||
73a294fb89 | |||
c068714e52 | |||
5fa66ba42f | |||
034c3aaa5e | |||
c7ed1a3c4a | |||
7d550ea399 | |||
dfc70cfce0 | |||
1aedffc919 | |||
7364f69b60 | |||
a342f80fee | |||
3d56cd2c65 | |||
74793e5d65 | |||
88c1612184 | |||
2924020eb7 | |||
3bda1a7284 | |||
b0579a5c15 | |||
092f6029b6 | |||
51283c3410 | |||
3a95d79c4a | |||
9aeea38192 | |||
76c80be783 | |||
f71535438b | |||
b536e1600a | |||
2ddc71d830 | |||
013cce078f | |||
7f3e255259 | |||
0825857231 | |||
9255201049 | |||
ed8a6e01e9 | |||
9def607737 | |||
6d0d417ce0 | |||
b127683003 | |||
a6775b95a9 | |||
647687c0c9 | |||
9dc7c67440 | |||
6ac9f0c77a |
@ -1,24 +1,30 @@
|
||||
## Openlib Contribution guidelines
|
||||
|
||||
# Code contribution
|
||||
### Getting started
|
||||
|
||||
### Fork the repo
|
||||
- This project is powered by Flutter. Make sure you have the latest version of Flutter.
|
||||
|
||||
- Clone the Openlib repository with git clone https://github.com/dstark5/Openlib.git (or use the link from your own fork, if you want to open a PR)
|
||||
- If you want to fix bug or implement a new feature, that already mention in the [issues](https://github.com/dstark5/Openlib/issues), please, assign this issue to you and/or comment about it.
|
||||
|
||||
|
||||
### Make your update:
|
||||
Make your changes to the file(s) you'd like to update.
|
||||
- Whether you have to implement new feature, please, open an issue or discussion regarding it to ensure it will be accepted.
|
||||
|
||||
### Open a pull request
|
||||
|
||||
When you're done making changes and you'd like to propose them for review, open your PR (pull request). You can use the GitHub user interface for some small changes, like fixing a typo or updating a readme. You can also fork the repo and then clone it locally, to view changes and run your tests on your machine.
|
||||
|
||||
### Before PR Self Review
|
||||
|
||||
Always review your own PR first
|
||||
|
||||
- Confirm the changes doesn't break anything else.
|
||||
- Compare your pull request's source changes to staging to confirm that the output matches the source and that everything is rendering as expected. This helps spot issues like typos, content that doesn't follow the style guide, or content that isn't rendering due to versioning problems.
|
||||
|
||||
### Submit your PR & get it reviewed
|
||||
Once you Submit Your PR We Review The Code And Merge It.
|
||||
- Once you submit your PR, others from the Docs community will review it with you. The first thing you're going to want to do is a [self review](#self-review).
|
||||
- After that, we may have questions, check back on your PR to keep up with the conversation.
|
||||
- We may ask for changes to be made before a PR can be merged. You can make any other changes in your fork, then commit them to your branch.
|
||||
|
||||
### Self review
|
||||
You should always review your own PR first.
|
||||
|
||||
For content changes, make sure that you:
|
||||
- [ ] Confirm the changes doesn't break anything else.
|
||||
- [ ] Compare your pull request's source changes to staging to confirm that the output matches the source and that everything is rendering as expected. This helps spot issues like typos, content that doesn't follow the style guide, or content that isn't rendering due to versioning problems.
|
||||
- [ ] Review the content for technical accuracy.
|
||||
- [ ] Copy-edit the changes for grammar or spelling mistakes.
|
||||
|
||||
### Notes
|
||||
- Please, do not modify readme and other information files (except for typos).
|
||||
- Avoid adding new dependencies unless required. APK size is important.
|
54
README.md
@ -1,16 +1,25 @@
|
||||
<p align="center"><img src="assets/icons/appIcon.png" width="150"></p>
|
||||
<h1 align="center"><b>Openlib</b></h1>
|
||||
<div align="center">
|
||||
|
||||
<img src="assets/icons/appIcon.png" width="150">
|
||||
|
||||
# Openlib
|
||||
|
||||
#### An Open source app to download and read books from shadow library ([Anna’s Archive](https://annas-archive.org/)).
|
||||
|
||||
[](https://flutter.dev/) [](https://opensource.org/licenses/) 
|
||||
[](https://flutter.dev/)
|
||||
[](https://opensource.org/licenses/)
|
||||
[](https://github.com/dstark5/Openlib/releases)
|
||||
|
||||
[<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 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 IzzyDroid"
|
||||
height="80">](https://android.izzysoft.de/repo/apk/com.app.openlib)
|
||||
|
||||
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
|
||||
|
||||
@ -18,7 +27,6 @@
|
||||
|
||||
#### Publishing Openlib, Or Any Fork Of It In The Google Play Store Violates Their Terms And Conditions.
|
||||
|
||||
|
||||
## Screenshots
|
||||
|
||||
[<img src="screenshots/Screenshot_1.png" width=160>](screenshots/Screenshot_1.png)
|
||||
@ -31,27 +39,27 @@
|
||||
[<img src="screenshots/Screenshot_8.png" width=160>](screenshots/Screenshot_8.png)
|
||||
|
||||
## Description
|
||||
##### Openlib Is An Open Source App To Download And Read Books From Shadow Library ([Anna’s Archive](https://annas-archive.org/)) . The App Has Built In Reader to Read Books.
|
||||
|
||||
##### As [Anna’s Archive](https://annas-archive.org/) Doesn't Have An API.The App Works By Sending Request To Anna’s Archive And Parses The Response To objects.The App Extracts The Mirrors From Response And Downloads The Book And Store It In The Application Document Directory.
|
||||
##### Openlib Is An Open Source App To Download And Read Books From Shadow Library ([Anna’s Archive](https://annas-archive.org/)). The App Has Built In Reader to Read Books.
|
||||
|
||||
##### As [Anna’s Archive](https://annas-archive.org/) Doesn't Have An API. The App Works By Sending Request To Anna’s Archive And Parses The Response To objects. The App Extracts The Mirrors From Response And Downloads The Book.
|
||||
|
||||
## Features
|
||||
- Trending Books
|
||||
|
||||
- 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
|
||||
|
||||
- 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.
|
||||
|
||||
## Building from Source
|
||||
|
||||
- If you don't have Flutter SDK installed, please visit official [Flutter](https://flutter.dev) site.
|
||||
@ -76,21 +84,27 @@ 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)
|
||||
|
||||
Whether you have ideas, design changes or even major code changes, help is always welcome. The app gets better and better with each contribution, no matter how big or small!
|
||||
|
||||
If you'd like to get involved See [CONTRIBUTING.md](./CONTRIBUTING.md) for the guidelines.
|
||||
|
||||
## Issues
|
||||
|
||||
Please report bugs via the [issue tracker](https://github.com/dstark5/Openlib/issues).
|
||||
|
||||
## Donate
|
||||
|
||||
If you like Openlib, you're welcome to send a donation.
|
||||
#### [Donate To Anna’s Archive](https://annas-archive.org/donate?tier=1).
|
||||
|
||||
#### [Donate To Anna’s Archive.](https://annas-archive.org/donate?tier=1)
|
||||
|
||||
## License
|
||||
|
||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
||||
Openlib is a free software licensed under GPL v3.0 It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY.[GNU General Public License](https://www.gnu.org/licenses/gpl.html) as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
Openlib is a free software licensed under GPL v3.0 It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY. [GNU General Public License](https://www.gnu.org/licenses/gpl.html) as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
Openlib does not own or have any affiliation with the books available through the app.All books are the property of their respective owners and are protected by copyright law.Openlib is not responsible for any infringement of copyright or other intellectual property rights that may result from the use of the books available through the app.By using the app, you agree to use the books only for personal, non-commercial purposes and in compliance with all applicable laws and regulations.
|
||||
Openlib does not own or have any affiliation with the books available through the app. All books are the property of their respective owners and are protected by copyright law. Openlib is not responsible for any infringement of copyright or other intellectual property rights that may result from the use of the books available through the app. By using the app, you agree to use the books only for personal, non-commercial purposes and in compliance with all applicable laws and regulations.
|
||||
|
@ -1,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,15 @@
|
||||
<data android:scheme="https" />
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
<application
|
||||
android:label="Openlib"
|
||||
android:name="${applicationName}"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:icon="@mipmap/launcher_icon">
|
||||
<activity
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
@ -33,6 +39,7 @@
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
|
32
android/app/src/main/res/raw/annas_archive
Normal file
@ -0,0 +1,32 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFfTCCBGWgAwIBAgIRAI0YQRVveHeTEYLPgc2CDXowDQYJKoZIhvcNAQELBQAw
|
||||
RjELMAkGA1UEBhMCVVMxIjAgBgNVBAoTGUdvb2dsZSBUcnVzdCBTZXJ2aWNlcyBM
|
||||
TEMxEzARBgNVBAMTCkdUUyBDQSAxUDUwHhcNMjMxMDMwMjMwOTExWhcNMjQwMTI4
|
||||
MjMwOTEwWjAcMRowGAYDVQQDExFhbm5hcy1hcmNoaXZlLm9yZzCCASIwDQYJKoZI
|
||||
hvcNAQEBBQADggEPADCCAQoCggEBAKl79Oj7GW10c1ikBC/sSkyr2tmmIl1/iszu
|
||||
9DN5w3lbX3q5hLeSB2xPaW6FWPB0u+YR8tgXyk6jG8jkzSe2cdkW0pjKaxSNiQgW
|
||||
rXipoonUbaceSDd3aFQFGhqe0urkM+84Sgspy39REdxXuQeL2hXH8fouRPA65/pn
|
||||
2nipwkpZHOezEQfE7BbUYwt4/YQaWXD3ScBLNx0PJuZdL4sfVC41IP8Ml/i5zzU7
|
||||
mupl6EGgw5IuXwyHN1AC1NHQBU5/8X062/NdVhW/letbsR52Z6DJ727+nWLpdwq4
|
||||
WgowHui6PthI6h+F4LCu+g3V5akAibsNffqVyWssxaMWFXjMuDECAwEAAaOCAo4w
|
||||
ggKKMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMB
|
||||
Af8EAjAAMB0GA1UdDgQWBBSIWJPHr1GutuUbnUApJ/vU3TQP9DAfBgNVHSMEGDAW
|
||||
gBTV/J4N3x7K3QiXl24rxV/FK/XsuDB4BggrBgEFBQcBAQRsMGowNQYIKwYBBQUH
|
||||
MAGGKWh0dHA6Ly9vY3NwLnBraS5nb29nL3MvZ3RzMXA1L1VfN25sRDJPX1JRMDEG
|
||||
CCsGAQUFBzAChiVodHRwOi8vcGtpLmdvb2cvcmVwby9jZXJ0cy9ndHMxcDUuZGVy
|
||||
MDEGA1UdEQQqMCiCEWFubmFzLWFyY2hpdmUub3JnghMqLmFubmFzLWFyY2hpdmUu
|
||||
b3JnMCEGA1UdIAQaMBgwCAYGZ4EMAQIBMAwGCisGAQQB1nkCBQMwPAYDVR0fBDUw
|
||||
MzAxoC+gLYYraHR0cDovL2NybHMucGtpLmdvb2cvZ3RzMXA1L19ZUS1xNlF1bEJB
|
||||
LmNybDCCAQUGCisGAQQB1nkCBAIEgfYEgfMA8QB2AHb/iD8KtvuVUcJhzPWHujS0
|
||||
pM27KdxoQgqf5mdMWjp0AAABi4MQGPAAAAQDAEcwRQIhALVAeepoK0WtUPs01yzZ
|
||||
XjAkdE7WYGw2QNzSkvywFRoIAiAnp1Lec4PqwGPbg/ppC8PgGxb8+yvSlVT0ChUt
|
||||
NMinWgB3ANq2v2s/tbYin5vCu1xr6HCRcWy7UYSFNL2kPTBI1/urAAABi4MQGP0A
|
||||
AAQDAEgwRgIhAKbAEYr9qDmiafcnkXIG7xObbI4fz3IsLSht8etA/jSlAiEAkQ7G
|
||||
t6x81Onpl4RcTtrXLptI0fDkrAZ66hAPWbIH8SAwDQYJKoZIhvcNAQELBQADggEB
|
||||
AGfvZYtIOPKRvVyfI4tJpetCJmU/DEMbCIyX05M+2P1n2uY1D4tweAMmYf4trh5Z
|
||||
cuTK3QDeoICus3WK08L3Ni/699QbQ0uonp0IIIOi2NlP2rLhvtETWpmLoX6jM55W
|
||||
cYGiQldhCgWYEXoANrJshUbjkyM81QMNzSrn33JPkzWUdgVoS/KfABaeymLekXO4
|
||||
ndLp4ktLlYQZr3JJU39FvwgN8IcmeLWUnpSWsekH+nHSW9e8vOsNQoZyHw0minqz
|
||||
ZzFbS10reX1kG56+AxDf5fOOM+C+MAozSUnXUjrkpXXakwUooMTklKtYbBiwR2R0
|
||||
wrcYmVHymn07AUliDOalu2I=
|
||||
-----END CERTIFICATE-----
|
17
android/app/src/main/res/xml/network_security_config.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<base-config cleartextTrafficPermitted="false">
|
||||
<trust-anchors>
|
||||
<certificates src="@raw/annas_archive"/>
|
||||
<certificates src="system"/>
|
||||
</trust-anchors>
|
||||
</base-config>
|
||||
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
<domain includeSubdomains="true">secure.example.com</domain>
|
||||
<domain includeSubdomains="true">127.0.0.1</domain>
|
||||
</domain-config>
|
||||
<domain-config cleartextTrafficPermitted="false">
|
||||
<domain includeSubdomains="true">annas-archive.org</domain>
|
||||
</domain-config>
|
||||
</network-security-config>
|
1
assets/captcha.svg
Normal file
After Width: | Height: | Size: 11 KiB |
@ -1,10 +1,14 @@
|
||||
<p>
|
||||
<i>Openlib</i> is an open source app to download and read books from shadow library (<a href='https://annas-archive.org/' target='_blank' rel='nofollow noopener'>Anna’s Archive</a>). The App Has Built In Reader to Read Books.
|
||||
<b><i>Openlib</i></b> is an open source app to download and read books from shadow library (<a href='https://annas-archive.org/' target='_blank' rel='nofollow noopener'>Anna’s Archive</a>). The App Has Built In Reader to Read Books.
|
||||
</p>
|
||||
<p>
|
||||
As <i>Anna’s Archive</i> doesn't have an API, the app works by sending requests to <i>Anna’s Archive</i> and parses the response to objects. The app extracts the mirrors from the responses, downloads the book and stores it in the application's document directory.
|
||||
</p>
|
||||
<p>Main Features:</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>
|
||||
|
Before Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 447 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 106 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 81 KiB |
Before Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 271 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 28 KiB |
@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:io' show Platform;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:google_nav_bar/google_nav_bar.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
@ -18,6 +19,8 @@ import 'package:openlib/state/state.dart'
|
||||
themeModeProvider,
|
||||
openPdfWithExternalAppProvider,
|
||||
openEpubWithExternalAppProvider,
|
||||
userAgentProvider,
|
||||
cookieProvider,
|
||||
dbProvider;
|
||||
|
||||
void main() async {
|
||||
@ -36,7 +39,14 @@ void main() async {
|
||||
bool openEpubwithExternalapp =
|
||||
await dataBase.getPreference('openEpubwithExternalApp');
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@ -49,7 +59,9 @@ void main() async {
|
||||
openPdfWithExternalAppProvider
|
||||
.overrideWith((ref) => openPdfwithExternalapp),
|
||||
openEpubWithExternalAppProvider
|
||||
.overrideWith((ref) => openEpubwithExternalapp)
|
||||
.overrideWith((ref) => openEpubwithExternalapp),
|
||||
userAgentProvider.overrideWith((ref) => browserUserAgent),
|
||||
cookieProvider.overrideWith((ref) => browserCookie),
|
||||
],
|
||||
child: const MyApp(),
|
||||
),
|
||||
@ -64,7 +76,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!,
|
||||
);
|
||||
@ -96,39 +108,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.background,
|
||||
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(
|
||||
iconColor: isDarkMode ? Colors.white : Colors.black,
|
||||
textStyle: const TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.white,
|
||||
fontSize: 11,
|
||||
@ -137,8 +150,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 +160,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 +170,8 @@ class _HomePageState extends ConsumerState<HomePage> {
|
||||
GButton(
|
||||
icon: Icons.build,
|
||||
text: 'Settings',
|
||||
iconColor: Colors.white,
|
||||
textStyle: TextStyle(
|
||||
iconColor: isDarkMode ? Colors.white : Colors.black,
|
||||
textStyle: const TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.white,
|
||||
fontSize: 11,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:html/parser.dart' show parse;
|
||||
|
||||
@ -27,44 +28,37 @@ class BookInfoData extends BookData {
|
||||
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 super.title,
|
||||
required super.author,
|
||||
required super.thumbnail,
|
||||
required super.publisher,
|
||||
required super.info,
|
||||
required super.link,
|
||||
required super.md5,
|
||||
required this.mirrors,
|
||||
required this.format,
|
||||
required this.description})
|
||||
: super(
|
||||
title: title,
|
||||
author: author,
|
||||
thumbnail: thumbnail,
|
||||
publisher: publisher,
|
||||
info: info,
|
||||
link: link,
|
||||
md5: md5);
|
||||
required this.description});
|
||||
}
|
||||
|
||||
class AnnasArchieve {
|
||||
String baseUrl = "https://annas-archive.gs";
|
||||
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 +67,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,24 +120,44 @@ 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 {
|
||||
Future<String?> _getMirrorLink(
|
||||
String url, String userAgent, String cookie) 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'];
|
||||
final response = await dio.get(url,
|
||||
options: Options(extra: {
|
||||
'withCredentials': true
|
||||
}, headers: {
|
||||
"Host": "annas-archive.org",
|
||||
"Origin": "https://annas-archive.org",
|
||||
"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(e);
|
||||
// print('${url} ${e}');
|
||||
if (e.toString().contains("403")) {
|
||||
throw jsonEncode({"code": "403", "url": url});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<BookInfoData?> _bookInfoParser(resData, url) async {
|
||||
Future<BookInfoData?> _bookInfoParser(resData, url, userAgent, cookie) async {
|
||||
var document = parse(resData.toString());
|
||||
var main = document.querySelector('main[class="main"]');
|
||||
var ul = main?.querySelectorAll('ul[class="mb-4"]');
|
||||
@ -141,25 +166,23 @@ class AnnasArchieve {
|
||||
|
||||
if (ul != null) {
|
||||
var anchorTags = [];
|
||||
if (ul.length == 2) {
|
||||
anchorTags = ul[1].querySelectorAll('a');
|
||||
} else {
|
||||
anchorTags = ul[0].querySelectorAll('a');
|
||||
|
||||
for (var e in ul) {
|
||||
anchorTags.insertAll(0, e.querySelectorAll('a'));
|
||||
}
|
||||
|
||||
for (var element in anchorTags) {
|
||||
if (element.attributes['href']!.startsWith('https://')) {
|
||||
if (element.attributes['href'] != null) {
|
||||
mirrors.add(element.attributes['href']!);
|
||||
}
|
||||
} else if (element.attributes['href'] != null &&
|
||||
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);
|
||||
}
|
||||
String? url = await _getMirrorLink(
|
||||
'$baseUrl${element.attributes['href']!}', userAgent, cookie);
|
||||
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']!);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -212,15 +235,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";
|
||||
@ -229,10 +277,15 @@ class AnnasArchieve {
|
||||
}
|
||||
}
|
||||
|
||||
Future<BookInfoData> bookInfo({required String url}) async {
|
||||
Future<BookInfoData> bookInfo(
|
||||
{required String url,
|
||||
required String userAgent,
|
||||
required String cookie}) async {
|
||||
try {
|
||||
final response = await dio.get(url);
|
||||
BookInfoData? data = await _bookInfoParser(response.data, url);
|
||||
final response =
|
||||
await dio.get(url, options: Options(headers: defaultDioHeaders));
|
||||
BookInfoData? data =
|
||||
await _bookInfoParser(response.data, url, userAgent, cookie);
|
||||
if (data != null) {
|
||||
return data;
|
||||
} else {
|
||||
|
@ -9,7 +9,7 @@ class Sqlite {
|
||||
|
||||
Database dbInstance = await openDatabase(
|
||||
path,
|
||||
version: 3,
|
||||
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)');
|
||||
@ -18,6 +18,8 @@ class Sqlite {
|
||||
if (isMobile) {
|
||||
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 {
|
||||
@ -25,6 +27,8 @@ class Sqlite {
|
||||
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 BOOLEAN)');
|
||||
@ -33,6 +37,10 @@ class Sqlite {
|
||||
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)');
|
||||
}
|
||||
},
|
||||
);
|
||||
return dbInstance;
|
||||
@ -194,4 +202,25 @@ class MyLibraryDb {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setBrowserOptions(String name, String value) async {
|
||||
await dbInstance.insert(
|
||||
'browserOptions',
|
||||
{'name': name, 'value': value},
|
||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||
);
|
||||
}
|
||||
|
||||
Future<String> getBrowserOptions(String name) async {
|
||||
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 "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
import 'dart:io';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'files.dart';
|
||||
|
||||
@ -14,7 +16,7 @@ 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);
|
||||
}
|
||||
@ -23,45 +25,99 @@ 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 path = await getAppDirectoryPath;
|
||||
final filePath = '$path/$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;
|
||||
}
|
||||
}
|
||||
|
@ -52,3 +52,99 @@ class OpenLibrary {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GoodReads {
|
||||
String url = "https://www.goodreads.com/shelf/show/trending";
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Future<List<TrendingBookData>> trendingBooks() async {
|
||||
try {
|
||||
final dio = Dio();
|
||||
final response = await dio.get(url,
|
||||
options: Options(
|
||||
sendTimeout: const Duration(seconds: 20),
|
||||
receiveTimeout: const Duration(seconds: 20)));
|
||||
return _parser(response.data.toString());
|
||||
} on DioException catch (e) {
|
||||
if (e.type == DioExceptionType.unknown) {
|
||||
throw "socketException";
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PenguinRandomHouse {
|
||||
String url =
|
||||
"https://www.penguinrandomhouse.com/ajaxc/categories/books/?from=0&to=50&contentId=&elClass=book&dataType=html&catFilter=best-sellers";
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Future<List<TrendingBookData>> trendingBooks() async {
|
||||
try {
|
||||
final dio = Dio();
|
||||
final response = await dio.get(url,
|
||||
options: Options(
|
||||
sendTimeout: const Duration(seconds: 20),
|
||||
receiveTimeout: const Duration(seconds: 20)));
|
||||
return _parser(response.data.toString());
|
||||
} on DioException catch (e) {
|
||||
if (e.type == DioExceptionType.unknown) {
|
||||
throw "socketException";
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,8 @@ Map<String, String> sortValues = {
|
||||
'Smallest': 'smallest',
|
||||
};
|
||||
|
||||
List<String> fileType = ["All", "PDF", "Epub", "Cbr", "Cbz"];
|
||||
|
||||
final selectedIndexProvider = StateProvider<int>((ref) => 0);
|
||||
|
||||
final themeModeProvider = StateProvider<ThemeMode>((ref) => ThemeMode.light);
|
||||
@ -46,6 +48,15 @@ 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) => "");
|
||||
@ -54,9 +65,22 @@ final searchQueryProvider = StateProvider<String>((ref) => "");
|
||||
|
||||
final getTrendingBooks = FutureProvider<List<TrendingBookData>>((ref) async {
|
||||
OpenLibrary openLibrary = OpenLibrary();
|
||||
return await openLibrary.trendingBooks();
|
||||
GoodReads goodReads = GoodReads();
|
||||
PenguinRandomHouse penguinTrending = PenguinRandomHouse();
|
||||
List<TrendingBookData> trendingBooks =
|
||||
await Future.wait<List<TrendingBookData>>([
|
||||
openLibrary.trendingBooks(),
|
||||
goodReads.trendingBooks(),
|
||||
penguinTrending.trendingBooks()
|
||||
]).then((List<List<TrendingBookData>> listOfData) =>
|
||||
listOfData.expand((element) => element).toList());
|
||||
|
||||
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,21 +88,31 @@ 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) => "");
|
||||
|
||||
//Provider for Book Info
|
||||
final bookInfoProvider =
|
||||
FutureProvider.family<BookInfoData, String>((ref, url) async {
|
||||
AnnasArchieve annasArchieve = AnnasArchieve();
|
||||
BookInfoData data = await annasArchieve.bookInfo(url: url);
|
||||
BookInfoData data = await annasArchieve.bookInfo(
|
||||
url: url,
|
||||
userAgent: ref.read(userAgentProvider),
|
||||
cookie: ref.read(cookieProvider));
|
||||
return data;
|
||||
});
|
||||
|
||||
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,6 +136,15 @@ final cancelCurrentDownload = StateProvider<CancelToken>((ref) {
|
||||
return CancelToken();
|
||||
});
|
||||
|
||||
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 dbProvider = Provider<MyLibraryDb>((ref) => throw UnimplementedError());
|
||||
|
||||
final myLibraryProvider = FutureProvider((ref) async {
|
||||
|
@ -5,13 +5,13 @@ import 'package:openlib/ui/components/snack_bar_widget.dart';
|
||||
import 'package:openlib/ui/components/page_title_widget.dart';
|
||||
|
||||
class AboutPage extends StatelessWidget {
|
||||
const AboutPage({Key? key}) : super(key: key);
|
||||
const AboutPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
title: const Text("Openlib"),
|
||||
titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
),
|
||||
@ -43,7 +43,7 @@ class AboutPage extends StatelessWidget {
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 7, right: 7, top: 5),
|
||||
child: Text(
|
||||
"1.0.2",
|
||||
"1.0.6",
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.bold,
|
||||
@ -87,8 +87,7 @@ class AboutPage extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _UrlText extends StatelessWidget {
|
||||
const _UrlText({Key? key, required this.text, required this.url})
|
||||
: super(key: key);
|
||||
const _UrlText({required this.text, required this.url});
|
||||
|
||||
final String url;
|
||||
final String text;
|
||||
|
@ -1,6 +1,8 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:dio/dio.dart' show CancelToken;
|
||||
|
||||
import 'package:openlib/services/database.dart';
|
||||
import 'package:openlib/ui/components/error_widget.dart';
|
||||
@ -15,15 +17,23 @@ import 'package:openlib/state/state.dart'
|
||||
getTotalFileSize,
|
||||
getDownloadedFileSize,
|
||||
cancelCurrentDownload,
|
||||
mirrorStatusProvider,
|
||||
ProcessState,
|
||||
CheckSumProcessState,
|
||||
downloadState,
|
||||
checkSumState,
|
||||
dbProvider,
|
||||
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';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:openlib/ui/webview_page.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,24 +42,102 @@ 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.background,
|
||||
title: const Text("Openlib"),
|
||||
titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
),
|
||||
body: bookInfo.when(
|
||||
skipLoadingOnRefresh: false,
|
||||
data: (data) {
|
||||
return BookInfoWidget(
|
||||
data: data, child: ActionButtonWidget(data: data));
|
||||
},
|
||||
error: (err, _) {
|
||||
return CustomErrorWidget(
|
||||
error: err,
|
||||
stackTrace: _,
|
||||
onRefresh: () {
|
||||
// ignore: unused_result
|
||||
ref.refresh(bookInfoProvider(url));
|
||||
},
|
||||
);
|
||||
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: _,
|
||||
onRefresh: () {
|
||||
// ignore: unused_result
|
||||
ref.refresh(bookInfoProvider(url));
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
loading: () {
|
||||
return Center(
|
||||
@ -58,6 +146,7 @@ class BookInfoPage extends ConsumerWidget {
|
||||
height: 25,
|
||||
child: CircularProgressIndicator(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
strokeCap: StrokeCap.round,
|
||||
),
|
||||
));
|
||||
},
|
||||
@ -120,6 +209,7 @@ class _ActionButtonWidgetState extends ConsumerState<ActionButtonWidget> {
|
||||
loading: () {
|
||||
return CircularProgressIndicator(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
strokeCap: StrokeCap.round,
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -128,16 +218,27 @@ class _ActionButtonWidgetState extends ConsumerState<ActionButtonWidget> {
|
||||
|
||||
Future<void> downloadFileWidget(
|
||||
WidgetRef ref, BuildContext context, dynamic data) async {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return _ShowDialog(title: data.title);
|
||||
});
|
||||
|
||||
downloadFile(
|
||||
mirrors: data.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(
|
||||
id: data.md5,
|
||||
@ -150,6 +251,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 +279,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 +298,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 +320,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 +359,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 +572,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();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -2,26 +2,41 @@ import 'package:flutter/material.dart';
|
||||
import 'package:openlib/ui/extensions.dart';
|
||||
import 'package:cached_network_image/cached_network_image.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 +115,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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -5,8 +5,7 @@ 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 +144,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) {
|
||||
|
@ -9,8 +9,10 @@ class CustomErrorWidget extends StatelessWidget {
|
||||
VoidCallback? onRefresh;
|
||||
|
||||
CustomErrorWidget(
|
||||
{Key? key, required this.error, required this.stackTrace, this.onRefresh})
|
||||
: super(key: key);
|
||||
{super.key,
|
||||
required this.error,
|
||||
required this.stackTrace,
|
||||
this.onRefresh});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -1,6 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:open_file/open_file.dart';
|
||||
|
||||
import 'package:openlib/services/files.dart' show getFilePath;
|
||||
import 'package:openlib/ui/components/snack_bar_widget.dart';
|
||||
import 'package:openlib/ui/components/delete_dialog_widget.dart';
|
||||
import 'package:openlib/ui/epub_viewer.dart' show launchEpubViewer;
|
||||
import 'package:openlib/ui/pdf_viewer.dart' show launchPdfViewer;
|
||||
@ -11,11 +14,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 +39,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 +93,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);
|
||||
} catch (e) {
|
||||
// ignore: avoid_print
|
||||
// print(e);
|
||||
// ignore: use_build_context_synchronously
|
||||
showSnackBar(context: context, message: 'Unable to open pdf!');
|
||||
}
|
||||
}
|
||||
|
@ -109,8 +109,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;
|
||||
|
@ -7,7 +7,7 @@ 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;
|
||||
|
||||
@ -15,7 +15,7 @@ class BookPage extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
title: const Text("Openlib"),
|
||||
titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
),
|
||||
|
@ -35,6 +35,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(
|
||||
|
@ -77,8 +77,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;
|
||||
|
@ -10,10 +10,7 @@ import 'package:openlib/ui/components/book_card_widget.dart';
|
||||
import 'package:openlib/state/state.dart' show searchProvider;
|
||||
|
||||
class ResultPage extends ConsumerWidget {
|
||||
const ResultPage({
|
||||
super.key,
|
||||
required this.searchQuery,
|
||||
});
|
||||
const ResultPage({super.key, required this.searchQuery});
|
||||
|
||||
final String searchQuery;
|
||||
|
||||
@ -23,7 +20,7 @@ class ResultPage extends ConsumerWidget {
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
title: const Text("Openlib"),
|
||||
titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
),
|
||||
@ -46,6 +43,7 @@ class ResultPage extends ConsumerWidget {
|
||||
author: i.author ?? "unknown",
|
||||
publisher: i.publisher ?? "unknown",
|
||||
thumbnail: i.thumbnail!,
|
||||
info: i.info,
|
||||
link: i.link,
|
||||
onClick: () {
|
||||
Navigator.push(context, MaterialPageRoute(
|
||||
@ -118,6 +116,7 @@ class ResultPage extends ConsumerWidget {
|
||||
height: 25,
|
||||
child: CircularProgressIndicator(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
strokeCap: StrokeCap.round,
|
||||
),
|
||||
))
|
||||
],
|
||||
|
@ -7,24 +7,33 @@ import 'package:openlib/state/state.dart'
|
||||
searchQueryProvider,
|
||||
selectedTypeState,
|
||||
selectedSortState,
|
||||
selectedFileTypeState,
|
||||
typeValues,
|
||||
sortValues;
|
||||
fileType,
|
||||
sortValues,
|
||||
enableFiltersState;
|
||||
import 'components/snack_bar_widget.dart';
|
||||
|
||||
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 +170,48 @@ class SearchPage extends ConsumerWidget {
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 7, right: 7, top: 19),
|
||||
child: SizedBox(
|
||||
width: 165,
|
||||
child: DropdownButtonFormField(
|
||||
decoration: InputDecoration(
|
||||
labelText: 'File type',
|
||||
labelStyle: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
enabledBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.grey, width: 2),
|
||||
borderRadius: BorderRadius.all(Radius.circular(50)),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
width: 2),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(50)),
|
||||
),
|
||||
),
|
||||
value: dropDownFileTypeValue,
|
||||
items: fileType.map<DropdownMenuItem<String>>((String value) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: value,
|
||||
child: Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
fontSize: 12, fontWeight: FontWeight.bold),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (String? val) {
|
||||
ref.read(selectedFileTypeState.notifier).state =
|
||||
val ?? 'All';
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -1,4 +1,6 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:openlib/ui/components/page_title_widget.dart';
|
||||
@ -11,7 +13,7 @@ import 'package:openlib/state/state.dart'
|
||||
dbProvider;
|
||||
|
||||
class SettingsPage extends ConsumerWidget {
|
||||
const SettingsPage({Key? key}) : super(key: key);
|
||||
const SettingsPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@ -42,6 +44,12 @@ class SettingsPage extends ConsumerWidget {
|
||||
ref.read(themeModeProvider.notifier).state =
|
||||
value == true ? ThemeMode.dark : ThemeMode.light;
|
||||
ref.read(dbProvider).savePreference('darkMode', value);
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||
systemNavigationBarColor:
|
||||
value ? Colors.black : Colors.grey.shade200));
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
@ -122,8 +130,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;
|
||||
|
@ -6,7 +6,8 @@ import 'extensions.dart';
|
||||
import 'package:openlib/ui/components/page_title_widget.dart';
|
||||
import 'package:openlib/ui/components/error_widget.dart';
|
||||
import 'package:openlib/ui/results_page.dart';
|
||||
import 'package:openlib/state/state.dart' show getTrendingBooks;
|
||||
import 'package:openlib/state/state.dart'
|
||||
show getTrendingBooks, enableFiltersState;
|
||||
|
||||
class TrendingPage extends ConsumerWidget {
|
||||
const TrendingPage({super.key});
|
||||
@ -17,127 +18,134 @@ 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,
|
||||
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: [
|
||||
const SliverToBoxAdapter(
|
||||
child: TitleText("Trending"),
|
||||
),
|
||||
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,
|
||||
),
|
||||
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,
|
||||
),
|
||||
));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
75
lib/ui/webview_page.dart
Normal file
@ -0,0 +1,75 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
import 'package:webview_cookie_manager/webview_cookie_manager.dart'
|
||||
as cookiejar;
|
||||
|
||||
import 'package:openlib/state/state.dart'
|
||||
show cookieProvider, userAgentProvider, dbProvider, bookInfoProvider;
|
||||
|
||||
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> {
|
||||
WebViewController controller = WebViewController();
|
||||
|
||||
final cookieManager = cookiejar.WebviewCookieManager();
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
title: const Text("Solve Captcha"),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: WebViewWidget(
|
||||
controller: controller
|
||||
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||
..setBackgroundColor(const Color(0x00000000))
|
||||
..loadRequest(Uri.parse(widget.url))
|
||||
..getUserAgent().then((value) {
|
||||
ref.read(userAgentProvider.notifier).state = value!;
|
||||
ref.read(dbProvider).setBrowserOptions('userAgent', value);
|
||||
})
|
||||
..setNavigationDelegate(NavigationDelegate(
|
||||
onPageStarted: (url) async {
|
||||
var urlStatusCode = await controller.runJavaScriptReturningResult(
|
||||
"var xhr = new XMLHttpRequest();xhr.open('GET', window.location.href, false);xhr.send(null);xhr.status;");
|
||||
|
||||
if (urlStatusCode.toString().contains('200')) {
|
||||
final cookies = await cookieManager
|
||||
.getCookies("https://annas-archive.org");
|
||||
|
||||
List<String> cookie = [];
|
||||
for (var element in cookies) {
|
||||
if (element.name == 'cf_clearance' ||
|
||||
element.name == 'cf_chl_2') {
|
||||
cookie.add(element.toString().split(';')[0]);
|
||||
}
|
||||
}
|
||||
|
||||
String cfClearance = cookie.join('; ');
|
||||
|
||||
ref.read(cookieProvider.notifier).state = cfClearance;
|
||||
|
||||
await ref
|
||||
.read(dbProvider)
|
||||
.setBrowserOptions('cookie', cfClearance);
|
||||
|
||||
ref.invalidate(bookInfoProvider);
|
||||
|
||||
// ignore: use_build_context_synchronously
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
282
pubspec.lock
@ -5,10 +5,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: archive
|
||||
sha256: "49b1fad315e57ab0bbc15bcbb874e83116a1d78f77ebd500a4af6c9407d6b28e"
|
||||
sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.8"
|
||||
version: "3.4.10"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -37,26 +37,26 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cached_network_image
|
||||
sha256: fd3d0dc1d451f9a252b32d95d3f0c3c487bc41a75eba2e6097cb0b9c71491b15
|
||||
sha256: f98972704692ba679db144261172a8e20feb145636c617af0eb4022132a6797f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.3"
|
||||
version: "3.3.0"
|
||||
cached_network_image_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cached_network_image_platform_interface
|
||||
sha256: bb2b8403b4ccdc60ef5f25c70dead1f3d32d24b9d6117cfc087f496b178594a7
|
||||
sha256: "56aa42a7a01e3c9db8456d9f3f999931f1e05535b5a424271e9a38cabf066613"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
version: "3.0.0"
|
||||
cached_network_image_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cached_network_image_web
|
||||
sha256: b8eb814ebfcb4dea049680f8c1ffb2df399e4d03bf7a352c775e26fa06e02fa0
|
||||
sha256: "759b9a9f8f6ccbb66c185df805fac107f05730b1dab9c64626d1008cca532257"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
version: "1.1.0"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -93,10 +93,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
|
||||
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.2"
|
||||
version: "1.18.0"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -106,7 +106,7 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: crypto
|
||||
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
|
||||
@ -141,10 +141,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dio
|
||||
sha256: ce75a1b40947fea0a0e16ce73337122a86762e38b982e1ccb909daa3b9bc4197
|
||||
sha256: "797e1e341c3dd2f69f2dad42564a6feff3bfb87187d05abb93b9609e6f1645c3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.3.2"
|
||||
version: "5.4.0"
|
||||
epub_view:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -193,19 +193,19 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_blurhash:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_blurhash
|
||||
sha256: "05001537bd3fac7644fa6558b09ec8c0a3f2eba78c0765f88912882b1331a5c6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.0"
|
||||
flutter_cache_manager:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -234,34 +234,34 @@ packages:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04
|
||||
sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
version: "3.0.1"
|
||||
flutter_pdfview:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_pdfview
|
||||
sha256: d9735fd8991609910742a25c63a5f87060849e57e60112c677b802ddb64bed72
|
||||
sha256: a9055bf920c7095bf08c2781db431ba23577aa5da5a056a7152dc89a18fbec6f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
version: "1.3.2"
|
||||
flutter_riverpod:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_riverpod
|
||||
sha256: b04d4e9435a563673746ccb328d22018c6c9496bb547e11dd56c1b0cc9829fe5
|
||||
sha256: "4bce556b7ecbfea26109638d5237684538d4abc509d253e6c5c4c5733b360098"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.10"
|
||||
version: "2.4.10"
|
||||
flutter_svg:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_svg
|
||||
sha256: "8c5d68a82add3ca76d792f058b186a0599414f279f00ece4830b9b231b570338"
|
||||
sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.7"
|
||||
version: "2.0.9"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@ -284,10 +284,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: google_fonts
|
||||
sha256: e20ff62b158b96f392bfc8afe29dee1503c94fbea2cbe8186fd59b756b8ae982
|
||||
sha256: f0b8d115a13ecf827013ec9fc883390ccc0e87a96ed5347a3114cac177ef18e8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.1.0"
|
||||
version: "6.1.0"
|
||||
google_nav_bar:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -308,10 +308,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http
|
||||
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
|
||||
sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.2.0"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -348,10 +348,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
|
||||
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
version: "3.0.0"
|
||||
list_counter:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -380,18 +380,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
|
||||
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
version: "1.10.0"
|
||||
octo_image:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: octo_image
|
||||
sha256: "107f3ed1330006a3bea63615e81cf637433f5135a52466c7caa0e7152bca9143"
|
||||
sha256: "45b40f99622f11901238e18d48f5f12ea36426d8eced9f4cbf58479c7aa2430d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
version: "2.0.0"
|
||||
open_file:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -420,26 +420,26 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa
|
||||
sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
version: "2.1.2"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: "6b8b19bd80da4f11ce91b2d1fb931f3006911477cec227cce23d3253d80df3f1"
|
||||
sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
version: "2.2.2"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d"
|
||||
sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
version: "2.3.2"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -452,10 +452,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c"
|
||||
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
version: "2.1.2"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -468,74 +468,82 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: permission_handler
|
||||
sha256: "63e5216aae014a72fe9579ccd027323395ce7a98271d9defa9d57320d001af81"
|
||||
sha256: "45ff3fbcb99040fde55c528d5e3e6ca29171298a85436274d49c6201002087d6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.4.3"
|
||||
version: "11.2.0"
|
||||
permission_handler_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_android
|
||||
sha256: d74e77a5ecd38649905db0a7d05ef16bed42ff263b9efb73ed794317c5764ec3
|
||||
sha256: "758284a0976772f9c744d6384fc5dc4834aa61e3f7aa40492927f244767374eb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.3.4"
|
||||
version: "12.0.3"
|
||||
permission_handler_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_apple
|
||||
sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5"
|
||||
sha256: c6bf440f80acd2a873d3d91a699e4cc770f86e7e6b576dda98759e8b92b39830
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.1.4"
|
||||
version: "9.3.0"
|
||||
permission_handler_html:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_html
|
||||
sha256: "54bf176b90f6eddd4ece307e2c06cf977fb3973719c35a93b85cc7093eb6070d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.1"
|
||||
permission_handler_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_platform_interface
|
||||
sha256: "7c6b1500385dd1d2ca61bb89e2488ca178e274a69144d26bbd65e33eae7c02a9"
|
||||
sha256: "5c43148f2bfb6d14c5a8162c0a712afe891f2d847f35fcff29c406b37da43c3c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.11.3"
|
||||
version: "4.1.0"
|
||||
permission_handler_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_windows
|
||||
sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098
|
||||
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.3"
|
||||
version: "0.2.1"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750
|
||||
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.4.0"
|
||||
version: "6.0.2"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102
|
||||
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
version: "3.1.4"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.6"
|
||||
version: "2.1.8"
|
||||
pointycastle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pointycastle
|
||||
sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c"
|
||||
sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.7.3"
|
||||
version: "3.7.4"
|
||||
quiver:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -548,10 +556,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: riverpod
|
||||
sha256: "6c0a2c30c04206ac05494bcccd8148b76866e1a9248a5a8c84ca7b16fbcb3f6a"
|
||||
sha256: "548e2192eb7aeb826eb89387f814edb76594f3363e2c0bb99dd733d795ba3589"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.10"
|
||||
version: "2.5.0"
|
||||
rxdart:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -593,42 +601,42 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sqflite
|
||||
sha256: "591f1602816e9c31377d5f008c2d9ef7b8aca8941c3f89cc5fd9d84da0c38a9a"
|
||||
sha256: a9016f495c927cb90557c909ff26a6d92d9bd54fc42ba92e19d4e79d61e798c6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
version: "2.3.2"
|
||||
sqflite_common:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_common
|
||||
sha256: "1b92f368f44b0dee2425bb861cfa17b6f6cf3961f762ff6f941d20b33355660a"
|
||||
sha256: "28d8c66baee4968519fb8bd6cdbedad982d6e53359091f0b74544a9f32ec72d5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.0"
|
||||
version: "2.5.3"
|
||||
sqflite_common_ffi:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sqflite_common_ffi
|
||||
sha256: "0d5cc1be2eb18400ac6701c31211d44164393aa75886093002ecdd947be04f93"
|
||||
sha256: "754927d82de369a6b9e760fb60640aa81da650f35ffd468d5a992814d6022908"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0+2"
|
||||
version: "2.3.2+1"
|
||||
sqlite3:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqlite3
|
||||
sha256: db65233e6b99e99b2548932f55a987961bc06d82a31a0665451fa0b4fff4c3fb
|
||||
sha256: c4a4c5a4b2a32e2d0f6837b33d7c91a67903891a5b7dbe706cf4b1f6b0c798c5
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
version: "2.3.0"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
|
||||
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.0"
|
||||
version: "1.11.1"
|
||||
state_notifier:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -641,10 +649,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
|
||||
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
version: "2.1.2"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -657,10 +665,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: synchronized
|
||||
sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60"
|
||||
sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
version: "3.1.0+1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -673,10 +681,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
|
||||
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.0"
|
||||
version: "0.6.1"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -697,98 +705,98 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: url_launcher
|
||||
sha256: "47e208a6711459d813ba18af120d9663c20bdf6985d6ad39fe165d2538378d27"
|
||||
sha256: c512655380d241a337521703af62d2c122bf7b77a46ff7dd750092aa9433499c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.14"
|
||||
version: "6.2.4"
|
||||
url_launcher_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: b04af59516ab45762b2ca6da40fa830d72d0f6045cd97744450b73493fa76330
|
||||
sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.0"
|
||||
version: "6.2.2"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: "7c65021d5dee51813d652357bc65b8dd4a6177082a9966bc8ba6ee477baa795f"
|
||||
sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.5"
|
||||
version: "6.2.4"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_linux
|
||||
sha256: b651aad005e0cb06a01dbd84b428a301916dc75f0e7ea6165f80057fee2d8e8e
|
||||
sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.6"
|
||||
version: "3.1.1"
|
||||
url_launcher_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_macos
|
||||
sha256: b55486791f666e62e0e8ff825e58a023fd6b1f71c49926483f1128d3bbd8fe88
|
||||
sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.7"
|
||||
version: "3.1.0"
|
||||
url_launcher_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_platform_interface
|
||||
sha256: "95465b39f83bfe95fcb9d174829d6476216f2d548b79c38ab2506e0458787618"
|
||||
sha256: a932c3a8082e118f80a475ce692fde89dc20fddb24c57360b96bc56f7035de1f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
version: "2.3.1"
|
||||
url_launcher_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_web
|
||||
sha256: "2942294a500b4fa0b918685aff406773ba0a4cd34b7f42198742a94083020ce5"
|
||||
sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.20"
|
||||
version: "2.2.3"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_windows
|
||||
sha256: "95fef3129dc7cfaba2bc3d5ba2e16063bb561fc6d78e63eee16162bc70029069"
|
||||
sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.8"
|
||||
version: "3.1.1"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: uuid
|
||||
sha256: e03928880bdbcbf496fb415573f5ab7b1ea99b9b04f669c01104d085893c3134
|
||||
sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
version: "4.3.3"
|
||||
vector_graphics:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics
|
||||
sha256: "670f6e07aca990b4a2bcdc08a784193c4ccdd1932620244c3a86bb72a0eac67f"
|
||||
sha256: "18f6690295af52d081f6808f2f7c69f0eed6d7e23a71539d75f4aeb8f0062172"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.7"
|
||||
version: "1.1.9+2"
|
||||
vector_graphics_codec:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics_codec
|
||||
sha256: "7451721781d967db9933b63f5733b1c4533022c0ba373a01bdd79d1a5457f69f"
|
||||
sha256: "531d20465c10dfac7f5cd90b60bbe4dd9921f1ec4ca54c83ebb176dbacb7bb2d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.7"
|
||||
version: "1.1.9+2"
|
||||
vector_graphics_compiler:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics_compiler
|
||||
sha256: "80a13c613c8bde758b1464a1755a7b3a8f2b6cec61fbf0f5a53c94c30f03ba2e"
|
||||
sha256: "03012b0a33775c5530576b70240308080e1d5050f0faf000118c20e6463bc0ad"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.7"
|
||||
version: "1.1.9+2"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -809,34 +817,74 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
|
||||
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.4-beta"
|
||||
version: "0.3.0"
|
||||
webview_cookie_manager:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: webview_cookie_manager
|
||||
sha256: "425a9feac5cd2cb62a71da3dda5ac2eaf9ece5481ee8d79f3868dc5ba8223ad3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.6"
|
||||
webview_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: webview_flutter
|
||||
sha256: d81b68e88cc353e546afb93fb38958e3717282c5ac6e5d3be4a4aef9fc3c1413
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.0"
|
||||
webview_flutter_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webview_flutter_android
|
||||
sha256: "4ea3c4e1b8ed590162b15b8a61b41b1ef3ff179a314627c16ce40c086d94b8af"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.14.0"
|
||||
webview_flutter_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webview_flutter_platform_interface
|
||||
sha256: d937581d6e558908d7ae3dc1989c4f87b786891ab47bb9df7de548a151779d8d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.10.0"
|
||||
webview_flutter_wkwebview:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webview_flutter_wkwebview
|
||||
sha256: b99ca8d8bae9c6b43d568218691aa537fb0aeae1d7d34eadf112a6aa36d26506
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.11.0"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: "9e82a402b7f3d518fb9c02d0e9ae45952df31b9bf34d77baf19da2de03fc2aaa"
|
||||
sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.7"
|
||||
version: "5.2.0"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2"
|
||||
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
version: "1.0.4"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xml
|
||||
sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84"
|
||||
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.0"
|
||||
version: "6.5.0"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -846,5 +894,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
sdks:
|
||||
dart: ">=3.1.0 <4.0.0"
|
||||
flutter: ">=3.13.0"
|
||||
dart: ">=3.2.3 <4.0.0"
|
||||
flutter: ">=3.16.6"
|
||||
|
19
pubspec.yaml
@ -16,7 +16,7 @@ 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.2+1
|
||||
version: 1.0.6+9
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.5 <4.0.0'
|
||||
@ -34,24 +34,26 @@ 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
|
||||
# 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
|
||||
webview_flutter: ^4.4.1
|
||||
webview_cookie_manager: ^2.0.6
|
||||
|
||||
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,6 +61,7 @@ dependencies:
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.2
|
||||
dev: ^1.0.0
|
||||
crypto: ^3.0.3
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@ -70,7 +73,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:
|
||||
@ -121,4 +124,4 @@ flutter:
|
||||
# weight: 700
|
||||
#
|
||||
# For details regarding fonts from package dependencies,
|
||||
# see https://flutter.dev/custom-fonts/#from-packages
|
||||
# see https://flutter.dev/custom-fonts/#from-packages
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 447 KiB |
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 536 KiB After Width: | Height: | Size: 106 KiB |
Before Width: | Height: | Size: 378 KiB After Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 227 KiB After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 379 KiB After Width: | Height: | Size: 81 KiB |
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 271 KiB |
Before Width: | Height: | Size: 231 KiB After Width: | Height: | Size: 28 KiB |
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|