25 Commits

Author SHA1 Message Date
74793e5d65 Merge pull request #54 from inson1/fix-bad-badge-version
Fix bad badge version
2023-12-17 11:16:49 +05:30
88c1612184 Fixed result not found error 2023-12-16 21:45:51 -08:00
2924020eb7 Fix bad badge version 2023-12-17 06:04:22 +01:00
3bda1a7284 Fixed no result found and added new resources for trending books 2023-12-05 05:52:14 -08:00
b0579a5c15 updated version code 2023-11-21 21:44:49 -08:00
092f6029b6 Fixed unable to download due to change in annas archive 2023-11-20 23:47:40 -08:00
51283c3410 fixed no result found error 2023-11-03 06:05:03 -07:00
3a95d79c4a fixed bugs 2023-10-24 04:05:29 -07:00
9aeea38192 implemented mirror availabilty check 2023-10-24 03:42:05 -07:00
76c80be783 implemented cloudflare clearance 2023-10-23 23:59:38 -07:00
f71535438b added file type filter and fixed search not working 2023-10-16 06:55:32 -07:00
b536e1600a finished file type indicator 2023-10-12 23:41:24 -07:00
2ddc71d830 added file type indicator for book card 2023-10-12 06:00:59 -07:00
013cce078f removed ripple effect from bottom nav 2023-09-13 22:43:14 -07:00
7f3e255259 added systemNavigationBarColor to blend with bottom navbar 2023-09-12 00:21:50 -07:00
0825857231 Merge pull request #26 from basitali1509/feat/bottom-nav-bar-UI
FEEDBACK: Bottom Navigation Bar background color should be white in light mode
2023-09-12 08:44:34 +05:30
9255201049 Merge pull request #29 from basitali1509/fix/search-page
FIXED #28: Search process should not work when the search field is empty
2023-09-12 08:42:37 +05:30
ed8a6e01e9 Fixed Empty Search Field Bug 2023-09-12 02:11:19 +05:00
9def607737 bottom navbar background color update 2023-09-12 00:00:13 +05:00
6d0d417ce0 Merge branch 'main' of https://github.com/dstark5/Openlib into feat/bottom-nav-bar-UI 2023-09-11 23:35:52 +05:00
b127683003 Merge branch 'main' of https://github.com/basitali1509/Openlib into feat/bottom-nav-bar-UI 2023-09-11 23:32:27 +05:00
a6775b95a9 Merge remote-tracking branch 'original/main' 2023-09-11 23:30:27 +05:00
647687c0c9 Fixed download failing due to change in Anna's archive website 2023-09-11 23:29:23 +05:00
9dc7c67440 updated readme 2023-09-11 03:28:37 -07:00
6ac9f0c77a bottom navbar UI update 2023-09-09 22:31:06 +05:00
25 changed files with 989 additions and 274 deletions

View File

@ -1,24 +1,30 @@
## Openlib Contribution guidelines
# Code contribution
### Getting started
### Fork the repo
- This project is powered by Flutter. Make sure you have the latest version of Flutter.
- Clone the Openlib repository with git clone https://github.com/dstark5/Openlib.git (or use the link from your own fork, if you want to open a PR)
- If you want to fix bug or implement a new feature, that already mention in the [issues](https://github.com/dstark5/Openlib/issues), please, assign this issue to you and/or comment about it.
### Make your update:
Make your changes to the file(s) you'd like to update.
- Whether you have to implement new feature, please, open an issue or discussion regarding it to ensure it will be accepted.
### Open a pull request
When you're done making changes and you'd like to propose them for review, open your PR (pull request). You can use the GitHub user interface for some small changes, like fixing a typo or updating a readme. You can also fork the repo and then clone it locally, to view changes and run your tests on your machine.
### Before PR Self Review
Always review your own PR first
- Confirm the changes doesn't break anything else.
- Compare your pull request's source changes to staging to confirm that the output matches the source and that everything is rendering as expected. This helps spot issues like typos, content that doesn't follow the style guide, or content that isn't rendering due to versioning problems.
### Submit your PR & get it reviewed
Once you Submit Your PR We Review The Code And Merge It.
- Once you submit your PR, others from the Docs community will review it with you. The first thing you're going to want to do is a [self review](#self-review).
- After that, we may have questions, check back on your PR to keep up with the conversation.
- We may ask for changes to be made before a PR can be merged. You can make any other changes in your fork, then commit them to your branch.
### Self review
You should always review your own PR first.
For content changes, make sure that you:
- [ ] Confirm the changes doesn't break anything else.
- [ ] Compare your pull request's source changes to staging to confirm that the output matches the source and that everything is rendering as expected. This helps spot issues like typos, content that doesn't follow the style guide, or content that isn't rendering due to versioning problems.
- [ ] Review the content for technical accuracy.
- [ ] Copy-edit the changes for grammar or spelling mistakes.
### Notes
- Please, do not modify readme and other information files (except for typos).
- Avoid adding new dependencies unless required. APK size is important.

View File

@ -3,13 +3,13 @@
#### An Open source app to download and read books from shadow library ([Annas Archive](https://annas-archive.org/)).
[![made-with-flutter](https://img.shields.io/badge/Made%20with-Flutter-4361ee.svg)](https://flutter.dev/) [![GPLv3 License](https://img.shields.io/badge/License-GPL%20v3-e63946.svg)](https://opensource.org/licenses/) ![version](https://img.shields.io/badge/version-1.0_beta-06d6a0)
[![made-with-flutter](https://img.shields.io/badge/Made%20with-Flutter-4361ee.svg)](https://flutter.dev/) [![GPLv3 License](https://img.shields.io/badge/License-GPL%20v3-e63946.svg)](https://opensource.org/licenses/) [![Latest release](https://img.shields.io/github/release/dstark5/Openlib.svg?style=flat)](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"
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)
## Note
@ -18,6 +18,10 @@
#### Publishing Openlib, Or Any Fork Of It In The Google Play Store Violates Their Terms And Conditions.
## Download
- Download and install APK from [GitHub Releases](https://github.com/dstark5/Openlib/releases).
- Download and install APK from [ IzzyOnDroid ](https://android.izzysoft.de/repo/apk/com.app.openlib).
## Screenshots
@ -33,25 +37,23 @@
## Description
##### Openlib Is An Open Source App To Download And Read Books From Shadow Library ([Annas Archive](https://annas-archive.org/)) . The App Has Built In Reader to Read Books.
##### As [Annas Archive](https://annas-archive.org/) Doesn't Have An API.The App Works By Sending Request To Annas Archive And Parses The Response To objects.The App Extracts The Mirrors From Response And Downloads The Book And Store It In The Application Document Directory.
##### As [Annas Archive](https://annas-archive.org/) Doesn't Have An API.The App Works By Sending Request To Annas Archive And Parses The Response To objects.The App Extracts The Mirrors From Response And Downloads The Book.
## Features
- Trending Books
- 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,7 +78,9 @@ 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

View File

@ -1,4 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
@ -17,10 +19,14 @@
<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
android:name=".MainActivity"
android:exported="true"
@ -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"

View File

@ -0,0 +1,32 @@
-----BEGIN CERTIFICATE-----
MIIFfTCCBGWgAwIBAgIRAI0YQRVveHeTEYLPgc2CDXowDQYJKoZIhvcNAQELBQAw
RjELMAkGA1UEBhMCVVMxIjAgBgNVBAoTGUdvb2dsZSBUcnVzdCBTZXJ2aWNlcyBM
TEMxEzARBgNVBAMTCkdUUyBDQSAxUDUwHhcNMjMxMDMwMjMwOTExWhcNMjQwMTI4
MjMwOTEwWjAcMRowGAYDVQQDExFhbm5hcy1hcmNoaXZlLm9yZzCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAKl79Oj7GW10c1ikBC/sSkyr2tmmIl1/iszu
9DN5w3lbX3q5hLeSB2xPaW6FWPB0u+YR8tgXyk6jG8jkzSe2cdkW0pjKaxSNiQgW
rXipoonUbaceSDd3aFQFGhqe0urkM+84Sgspy39REdxXuQeL2hXH8fouRPA65/pn
2nipwkpZHOezEQfE7BbUYwt4/YQaWXD3ScBLNx0PJuZdL4sfVC41IP8Ml/i5zzU7
mupl6EGgw5IuXwyHN1AC1NHQBU5/8X062/NdVhW/letbsR52Z6DJ727+nWLpdwq4
WgowHui6PthI6h+F4LCu+g3V5akAibsNffqVyWssxaMWFXjMuDECAwEAAaOCAo4w
ggKKMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMB
Af8EAjAAMB0GA1UdDgQWBBSIWJPHr1GutuUbnUApJ/vU3TQP9DAfBgNVHSMEGDAW
gBTV/J4N3x7K3QiXl24rxV/FK/XsuDB4BggrBgEFBQcBAQRsMGowNQYIKwYBBQUH
MAGGKWh0dHA6Ly9vY3NwLnBraS5nb29nL3MvZ3RzMXA1L1VfN25sRDJPX1JRMDEG
CCsGAQUFBzAChiVodHRwOi8vcGtpLmdvb2cvcmVwby9jZXJ0cy9ndHMxcDUuZGVy
MDEGA1UdEQQqMCiCEWFubmFzLWFyY2hpdmUub3JnghMqLmFubmFzLWFyY2hpdmUu
b3JnMCEGA1UdIAQaMBgwCAYGZ4EMAQIBMAwGCisGAQQB1nkCBQMwPAYDVR0fBDUw
MzAxoC+gLYYraHR0cDovL2NybHMucGtpLmdvb2cvZ3RzMXA1L19ZUS1xNlF1bEJB
LmNybDCCAQUGCisGAQQB1nkCBAIEgfYEgfMA8QB2AHb/iD8KtvuVUcJhzPWHujS0
pM27KdxoQgqf5mdMWjp0AAABi4MQGPAAAAQDAEcwRQIhALVAeepoK0WtUPs01yzZ
XjAkdE7WYGw2QNzSkvywFRoIAiAnp1Lec4PqwGPbg/ppC8PgGxb8+yvSlVT0ChUt
NMinWgB3ANq2v2s/tbYin5vCu1xr6HCRcWy7UYSFNL2kPTBI1/urAAABi4MQGP0A
AAQDAEgwRgIhAKbAEYr9qDmiafcnkXIG7xObbI4fz3IsLSht8etA/jSlAiEAkQ7G
t6x81Onpl4RcTtrXLptI0fDkrAZ66hAPWbIH8SAwDQYJKoZIhvcNAQELBQADggEB
AGfvZYtIOPKRvVyfI4tJpetCJmU/DEMbCIyX05M+2P1n2uY1D4tweAMmYf4trh5Z
cuTK3QDeoICus3WK08L3Ni/699QbQ0uonp0IIIOi2NlP2rLhvtETWpmLoX6jM55W
cYGiQldhCgWYEXoANrJshUbjkyM81QMNzSrn33JPkzWUdgVoS/KfABaeymLekXO4
ndLp4ktLlYQZr3JJU39FvwgN8IcmeLWUnpSWsekH+nHSW9e8vOsNQoZyHw0minqz
ZzFbS10reX1kG56+AxDf5fOOM+C+MAozSUnXUjrkpXXakwUooMTklKtYbBiwR2R0
wrcYmVHymn07AUliDOalu2I=
-----END CERTIFICATE-----

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="@raw/annas_archive"/>
<certificates src="system"/>
</trust-anchors>
</base-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">secure.example.com</domain>
<domain includeSubdomains="true">127.0.0.1</domain>
</domain-config>
<domain-config cleartextTrafficPermitted="false">
<domain includeSubdomains="true">annas-archive.org</domain>
</domain-config>
</network-security-config>

1
assets/captcha.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -1,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(),
),
@ -96,6 +108,8 @@ 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(
@ -107,28 +121,27 @@ class _HomePageState extends ConsumerState<HomePage> {
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,

View File

@ -1,4 +1,5 @@
import 'package:dio/dio.dart';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:html/parser.dart' show parse;
@ -48,23 +49,24 @@ class BookInfoData extends BookData {
}
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 +75,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 +128,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"]');
@ -149,17 +182,17 @@ class AnnasArchieve {
for (var element in anchorTags) {
if (element.attributes['href']!.startsWith('https://')) {
if (element.attributes['href'] != null) {
if (element.attributes['href'] != null &&
element.attributes['href'].startsWith('https://1lib.sk') !=
true) {
mirrors.add(element.attributes['href']!);
}
} else if (element.attributes['href'] != null &&
element.attributes['href']!.startsWith('/slow_download')) {
if (element.text.contains('Slow Partner Server #1') != true) {
String? url =
await _getMirrorLink('$baseUrl${element.attributes['href']!}');
if (url != null && url.isNotEmpty) {
mirrors.add(url);
}
String? url = await _getMirrorLink(
'$baseUrl${element.attributes['href']!}', userAgent, cookie);
if (url != null && url.isNotEmpty) {
mirrors.add(url);
}
}
}
@ -212,15 +245,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 +287,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 {

View File

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

View File

@ -1,4 +1,5 @@
import 'package:dio/dio.dart';
import 'package:chunked_downloader/chunked_downloader.dart';
import 'files.dart';
Future<String> _getFilePath(String fileName) async {
@ -14,7 +15,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 +24,62 @@ List<String> _reorderMirrors(List<String> mirrors) {
return [...ipfsMirrors, ...httpsMirrors];
}
Future<String?> _getAliveMirror(List<String> mirrors, Dio dio) async {
for (var url in mirrors) {
try {
final response = await dio.head(url,
options: Options(receiveTimeout: const Duration(seconds: 5)));
if (response.statusCode == 200) {
return url;
}
} catch (e) {
// print("timeOut");
}
}
return null;
}
Future<void> downloadFile(
{required List<String> mirrors,
required String md5,
required String format,
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);
String? workingMirror = await _getAliveMirror(orderedMirrors, dio);
// print(workingMirror);
// print(path);
// print(orderedMirrors);
// print(orderedMirrors[0]);
try {
CancelToken cancelToken = CancelToken();
if (workingMirror != null) {
try {
var chunkedDownloader = await ChunkedDownloader(
url: workingMirror,
saveFilePath: path,
chunkSize: 32 * 1024,
onError: (error) {
onDownlaodFailed();
},
onProgress: (received, total, speed) {
onProgress(received, total);
},
onDone: (file) {})
.start();
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();
}
throw err;
});
cancelDownlaod(cancelToken);
} catch (e) {
mirrorStatus(true);
cancelDownlaod(chunkedDownloader);
} catch (e) {
onDownlaodFailed();
}
} else {
onDownlaodFailed();
}
}

View File

@ -52,3 +52,97 @@ 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_.")),
);
}
}
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;
}
}
}

View File

@ -2,7 +2,7 @@ import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:openlib/services/database.dart';
import 'package:dio/dio.dart';
import 'package:chunked_downloader/chunked_downloader.dart';
import 'package:openlib/services/open_library.dart';
import 'package:openlib/services/annas_archieve.dart';
@ -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);
@ -98,8 +132,8 @@ final getDownloadedFileSize = StateProvider.autoDispose<String>((ref) {
return bytesToFileSize(ref.watch(downloadedFileSizeInBytes));
});
final cancelCurrentDownload = StateProvider<CancelToken>((ref) {
return CancelToken();
final cancelCurrentDownload = StateProvider<ChunkedDownloader>((ref) {
return ChunkedDownloader(saveFilePath: "", url: "");
});
final dbProvider = Provider<MyLibraryDb>((ref) => throw UnimplementedError());

View File

@ -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.4",
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,

View File

@ -1,5 +1,6 @@
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:openlib/services/database.dart';
@ -15,12 +16,18 @@ import 'package:openlib/state/state.dart'
getTotalFileSize,
getDownloadedFileSize,
cancelCurrentDownload,
mirrorStatusProvider,
dbProvider,
checkIdExists,
myLibraryProvider;
import 'package:chunked_downloader/chunked_downloader.dart';
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);
@ -37,19 +44,97 @@ class BookInfoPage extends ConsumerWidget {
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 +143,7 @@ class BookInfoPage extends ConsumerWidget {
height: 25,
child: CircularProgressIndicator(
color: Theme.of(context).colorScheme.secondary,
strokeCap: StrokeCap.round,
),
));
},
@ -120,6 +206,7 @@ class _ActionButtonWidgetState extends ConsumerState<ActionButtonWidget> {
loading: () {
return CircularProgressIndicator(
color: Theme.of(context).colorScheme.secondary,
strokeCap: StrokeCap.round,
);
},
);
@ -158,9 +245,12 @@ Future<void> downloadFileWidget(
showSnackBar(context: context, message: 'Book has been downloaded!');
}
},
cancelDownlaod: (CancelToken downloadToken) {
cancelDownlaod: (ChunkedDownloader downloadToken) {
ref.read(cancelCurrentDownload.notifier).state = downloadToken;
},
mirrorStatus: (val) {
ref.read(mirrorStatusProvider.notifier).state = val;
},
onDownlaodFailed: () {
Navigator.of(context).pop();
showSnackBar(
@ -185,6 +275,7 @@ class _ShowDialog extends ConsumerWidget {
final downloadProgress = ref.watch(downloadProgressProvider);
final fileSize = ref.watch(getTotalFileSize);
final downloadedFileSize = ref.watch(getDownloadedFileSize);
final mirrorStatus = ref.watch(mirrorStatusProvider);
if (downloadProgress == 1.0) {
Navigator.of(context).pop();
@ -197,7 +288,7 @@ class _ShowDialog extends ConsumerWidget {
padding: const EdgeInsets.all(15.0),
child: Container(
width: double.infinity,
height: 255,
height: 285,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
color: Theme.of(context).colorScheme.tertiaryContainer,
@ -236,6 +327,46 @@ 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,
),
]),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
@ -286,7 +417,7 @@ class _ShowDialog extends ConsumerWidget {
color: Colors.white,
)),
onPressed: () {
ref.read(cancelCurrentDownload).cancel();
ref.read(cancelCurrentDownload).stop();
Navigator.of(context).pop();
},
child: const Padding(

View File

@ -2,6 +2,18 @@ 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,
@ -9,6 +21,7 @@ class BookInfoCard extends StatelessWidget {
required this.author,
required this.publisher,
required this.thumbnail,
required this.info,
required this.link,
required this.onClick})
: super(key: key);
@ -17,11 +30,14 @@ class BookInfoCard extends StatelessWidget {
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 +116,50 @@ class BookInfoCard extends StatelessWidget {
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
Text(
author,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.bold,
color: Theme.of(context).textTheme.headlineSmall?.color,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
if (fileType != null)
Container(
decoration: BoxDecoration(
color: "#a5a5a5".toColor(),
borderRadius: BorderRadius.circular(2.5),
),
child: Padding(
padding: const EdgeInsets.fromLTRB(3, 2, 3, 2),
child: Text(
fileType,
style: const TextStyle(
fontSize: 8.5,
fontWeight: FontWeight.bold,
color: Colors.white,
letterSpacing: 0.5,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
),
if (fileType != null)
const SizedBox(
width: 3,
),
Expanded(
child: Text(
author,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.textTheme
.headlineSmall
?.color,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
],
),
],
),

View File

@ -1,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;
@ -37,9 +40,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 +94,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!');
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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
View 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({Key? key, required this.url}) : super(key: key);
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);
}
},
)),
),
),
);
}
}

View File

@ -73,6 +73,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.3"
chunked_downloader:
dependency: "direct main"
description:
name: chunked_downloader
sha256: "7fe3a37a0c7fb1d41f3f922c30937fdccae2647b6f6f71778bffcf4823048754"
url: "https://pub.dev"
source: hosted
version: "0.0.2"
cli_util:
dependency: transitive
description:
@ -141,10 +149,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:
@ -284,10 +292,10 @@ packages:
dependency: "direct main"
description:
name: google_fonts
sha256: e20ff62b158b96f392bfc8afe29dee1503c94fbea2cbe8186fd59b756b8ae982
sha256: "6b6f10f0ce3c42f6552d1c70d2c28d764cf22bb487f50f66cca31dcd5194f4d6"
url: "https://pub.dev"
source: hosted
version: "5.1.0"
version: "4.0.4"
google_nav_bar:
dependency: "direct main"
description:
@ -308,10 +316,10 @@ packages:
dependency: transitive
description:
name: http
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
version: "0.13.6"
http_parser:
dependency: transitive
description:
@ -813,6 +821,46 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.1.4-beta"
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: c1ab9b81090705c6069197d9fdc1625e587b52b8d70cdde2339d177ad0dbb98e
url: "https://pub.dev"
source: hosted
version: "4.4.1"
webview_flutter_android:
dependency: transitive
description:
name: webview_flutter_android
sha256: b0cd33dd7d3dd8e5f664e11a19e17ba12c352647269921a3b568406b001f1dff
url: "https://pub.dev"
source: hosted
version: "3.12.0"
webview_flutter_platform_interface:
dependency: transitive
description:
name: webview_flutter_platform_interface
sha256: "6d9213c65f1060116757a7c473247c60f3f7f332cac33dc417c9e362a9a13e4f"
url: "https://pub.dev"
source: hosted
version: "2.6.0"
webview_flutter_wkwebview:
dependency: transitive
description:
name: webview_flutter_wkwebview
sha256: "30b9af6bdd457b44c08748b9190d23208b5165357cc2eb57914fee1366c42974"
url: "https://pub.dev"
source: hosted
version: "3.9.1"
win32:
dependency: transitive
description:

View File

@ -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.4+7
environment:
sdk: '>=3.0.5 <4.0.0'
@ -34,24 +34,27 @@ 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
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:
cached_network_image: ^3.2.3
chunked_downloader: ^0.0.2
sqflite_common_ffi: ^2.3.0+2
url_launcher: ^6.1.12

View File

@ -31,6 +31,11 @@ bool FlutterWindow::OnCreate() {
this->Show();
});
// Flutter can complete the first frame before the "show window" callback is
// registered. The following call ensures a frame is pending to ensure the
// window is shown. It is a no-op if the first frame hasn't completed yet.
flutter_controller_->ForceRedraw();
return true;
}