21 Commits

Author SHA1 Message Date
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
22 changed files with 784 additions and 250 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/) ![version](https://img.shields.io/badge/version-1.0.2_beta-06d6a0)
[<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,15 @@
<data android:scheme="https" />
</intent>
</queries>
<application
android:label="Openlib"
android:name="${applicationName}"
android:requestLegacyExternalStorage="true"
android:usesCleartextTraffic="true"
android:networkSecurityConfig="@xml/network_security_config"
android:icon="@mipmap/launcher_icon">
<activity
android:name=".MainActivity"
android:exported="true"
@ -33,6 +40,7 @@
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"

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,25 @@ 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 "]');
'a[class="js-vim-focus custom-a flex items-center relative left-[-10px] w-[calc(100%+20px)] px-[10px] outline-offset-[-2px] outline-2 rounded-[3px] hover:bg-[#00000011] focus:outline "]');
List<BookData> bookList = [];
@ -73,21 +76,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 +129,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 +183,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);
}
}
}
@ -215,12 +249,16 @@ class AnnasArchieve {
Future<List<BookData>> searchBooks(
{required String searchQuery,
String content = "",
String sort = ""}) async {
String sort = "",
String fileType = ""}) async {
try {
final response = await dio.get(
'$baseUrl/search?lang=&content=$content&ext=&sort=$sort&q=$searchQuery',
);
return _parser(response.data);
final String encodedURL = content == ""
? '$baseUrl/search?index=&q=$searchQuery&ext=$fileType&sort=$sort'
: '$baseUrl/search?index=&q=$searchQuery&content=$content&ext=$fileType&sort=$sort';
final response = await dio.get(encodedURL,
options: Options(headers: defaultDioHeaders));
return _parser(response.data, fileType);
} on DioException catch (e) {
if (e.type == DioExceptionType.unknown) {
throw "socketException";
@ -229,10 +267,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

@ -14,7 +14,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 +23,72 @@ 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 {
CancelToken cancelToken = CancelToken();
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;
});
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) {
onProgress(rcv, total);
},
deleteOnError: true,
cancelToken: cancelToken,
).catchError((err) {
if (err.type != DioExceptionType.cancel) {
onDownlaodFailed();
}
throw err;
});
cancelDownlaod(cancelToken);
} catch (e) {
mirrorStatus(true);
cancelDownlaod(cancelToken);
} catch (e) {
onDownlaodFailed();
}
} else {
onDownlaodFailed();
}
}

View File

@ -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) => "");
@ -64,21 +75,30 @@ 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));
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);

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

View File

@ -1,5 +1,7 @@
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 +17,15 @@ import 'package:openlib/state/state.dart'
getTotalFileSize,
getDownloadedFileSize,
cancelCurrentDownload,
mirrorStatusProvider,
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);
@ -37,19 +42,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(
@ -161,6 +244,9 @@ Future<void> downloadFileWidget(
cancelDownlaod: (CancelToken downloadToken) {
ref.read(cancelCurrentDownload.notifier).state = downloadToken;
},
mirrorStatus: (val) {
ref.read(mirrorStatusProvider.notifier).state = val;
},
onDownlaodFailed: () {
Navigator.of(context).pop();
showSnackBar(
@ -185,6 +271,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 +284,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 +323,45 @@ 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,
),
),
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: [

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

@ -46,6 +46,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(

View File

@ -7,24 +7,33 @@ import 'package:openlib/state/state.dart'
searchQueryProvider,
selectedTypeState,
selectedSortState,
selectedFileTypeState,
typeValues,
fileType,
sortValues;
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) {
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

@ -17,127 +17,133 @@ 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: () {
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,
),
));
});
}
}

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

@ -813,6 +813,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.3+5
environment:
sdk: '>=3.0.5 <4.0.0'
@ -47,6 +47,8 @@ dependencies:
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

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