49 Commits

Author SHA1 Message Date
1e84848d39 Fixed trending page request connection error 2024-10-13 20:36:54 +05:30
78a50e28ff Merge pull request #119 from Nav-jangra/trending
trending page error
2024-10-13 18:00:06 +05:30
fda962a46f git commit -m 'trending page is not working due to open lib not responding :FIXED' 2024-10-12 15:52:04 +05:30
a7ca206b10 Fixed Stuck on Solve Captcha Screen 2024-09-28 13:10:48 +05:30
6be9927b26 Updated build number in pubspec.yaml 2024-09-10 18:49:23 +05:30
ea86d76015 removed unused imports 2024-08-31 16:00:48 +05:30
f396f92463 Fixed "no mirror found" and "always redirected to captcha" issues 2024-08-31 14:17:50 +05:30
70e96b12e1 Merge pull request #100 from clovis-dugue/develop
Multiple features and updates
2024-08-27 12:49:40 +05:30
9a91cffb31 chore: update Podfile.lock 2024-08-26 19:32:44 +02:00
669e5f7a66 feat: move files when changing book directory in settings 2024-08-26 19:32:29 +02:00
6e1f75b11a Merge pull request #96 from clovis-dugue/fix/no-mirrors
fix: no mirrors found after anna's archive ui update
2024-08-24 11:11:29 +05:30
7f36ca98b7 feat: add a setting for the destination folder of downloaded books 2024-08-24 00:23:12 +02:00
1025552f2f chore: format README.md 2024-08-23 23:35:35 +02:00
11a7dca93c chore: organize imports 2024-08-23 23:35:09 +02:00
ccd60ac357 fix: macos internet permission 2024-08-23 20:09:55 +02:00
d0c8128631 fix: no mirrors found after anna's archive ui update 2024-08-22 22:26:55 +02:00
e1a42df7e7 chore: update macos build 2024-08-22 18:58:07 +02:00
50f907eff7 chore: update ios build 2024-08-22 18:57:56 +02:00
c0c0de58a5 fix: update dependencies to be compatible with the latest flutter version 2024-08-22 18:55:47 +02:00
f0940e2947 chore: remove in-app reader 2024-08-22 18:47:40 +02:00
62ff665b87 fix: no mirrors found after anna's archive ui update 2024-08-22 18:46:00 +02:00
67d894855a Update README.md 2024-03-31 22:16:19 +05:30
a3ca57772d Update README.md 2024-03-31 21:55:44 +05:30
d9b19a6ec3 Merge pull request #71 from inson1/patch-3
Update badges in Readme
2024-03-10 18:54:24 +05:30
e013713516 Update badges in Readme 2024-02-26 16:17:25 +01:00
d655a4bd32 fixed mirror not found error 2024-02-25 18:32:34 +05:30
9b696c7e9d updated about page 2024-02-15 22:53:41 +05:30
cbbe049ef8 reverted back to old version as the issue still exist 2024-02-15 22:52:33 +05:30
cddaeb553a updated version number 2024-02-15 22:31:16 +05:30
ced66d0d1c Merge branch 'main' of https://github.com/dstark5/Openlib 2024-02-15 22:26:27 +05:30
17dcbb05b3 Fixed no mirror and download failed error 2024-02-15 22:25:27 +05:30
27e43a358c Merge pull request #64 from inson1/patch-2
Fix dot in README.md
2024-02-07 15:06:02 +05:30
1715b73494 Fix dot in README.md 2024-02-06 10:37:42 +01:00
4c557ed28d fastlane description update 2024-02-05 20:32:12 +05:30
61a4e566df fastlane description update 2024-02-05 20:31:12 +05:30
2a2e5df118 Fixed grey screen while file downloading 2024-02-05 20:17:11 +05:30
aac2e807f0 Fixed gray screen error 2024-02-05 19:29:10 +05:30
95288271fa added warning for failed file checksum veification 2024-02-04 17:26:11 +05:30
73a294fb89 updated version details 2024-02-04 16:16:52 +05:30
c068714e52 added file verification with md5 checksum 2024-02-04 12:41:48 +05:30
5fa66ba42f Added F-droid link to README.md 2024-02-01 12:14:32 +05:30
034c3aaa5e Updated screenshots 2024-01-25 17:42:50 +05:30
c7ed1a3c4a Update README.md 2024-01-25 11:28:07 +05:30
7d550ea399 Merge pull request #61 from inson1/patch-1
Nicer Readme
2024-01-24 21:50:17 -08:00
dfc70cfce0 Update README.md 2024-01-24 18:17:26 +01:00
1aedffc919 Update README.md 2024-01-24 18:16:07 +01:00
7364f69b60 Update README.md 2024-01-24 18:14:25 +01:00
a342f80fee Merge pull request #56 from inson1/patch-1
Fixing Readme 2.0
2023-12-26 17:46:49 +05:30
3d56cd2c65 Fixing Readme 2.0
idk why the first PR wasnt merged
2023-12-25 14:59:38 +01:00
69 changed files with 1755 additions and 804 deletions

103
README.md
View File

@ -1,29 +1,34 @@
<p align="center"><img src="assets/icons/appIcon.png" width="150"></p>
<h1 align="center"><b>Openlib</b></h1>
<div align="center">
#### An Open source app to download and read books from shadow library ([Annas Archive](https://annas-archive.org/)).
<img src="assets/icons/appIcon.png" width="150">
[![made-with-flutter](https://img.shields.io/badge/Made%20with-Flutter-4361ee.svg)](https://flutter.dev/) [![GPLv3 License](https://img.shields.io/badge/License-GPL%20v3-e63946.svg)](https://opensource.org/licenses/) [![Latest release](https://img.shields.io/github/release/dstark5/Openlib.svg?style=flat)](https://github.com/dstark5/Openlib/releases)
# Openlib
[<img src="github_releases.png"
alt="Download from GitHub"
height="60">](https://github.com/dstark5/Openlib/releases) [<img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png"
alt="Get it on IzzyDroid"
An Open source app to download and read books from shadow library ([Annas Archive](https://annas-archive.org/))
[![made-with-flutter](https://img.shields.io/badge/Made%20with-Flutter-4361ee.svg?style=for-the-badge)](https://flutter.dev/)
[![GPLv3 License](https://img.shields.io/badge/License-GPL%20v3-e63946.svg?style=for-the-badge)](https://opensource.org/licenses/)
[![Latest release](https://img.shields.io/github/release/dstark5/Openlib.svg?style=for-the-badge)](https://github.com/dstark5/Openlib/releases)
[<img src="github_releases.png"
alt="Get it on GitHub"
height="60">](https://github.com/dstark5/Openlib/releases)
[<img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png"
alt="Get it on IzzyOnDroid"
height="60">](https://android.izzysoft.de/repo/apk/com.app.openlib)
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Get it on F-Droid"
height="60">](https://f-droid.org/en/packages/com.app.openlib/)
</div>
## Note
## Note 📝
**WARNING:** This App Is In Beta Stage, So You May Encounter Bugs. If You Do, Open An Issue In Github Repository.
#### Publishing Openlib, Or Any Fork Of It In The Google Play Store Violates Their Terms And Conditions.
**Publishing Openlib, Or Any Fork Of It In The Google Play Store Violates Their Terms And Conditions**
## 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
## Screenshots 🖼️
[<img src="screenshots/Screenshot_1.png" width=160>](screenshots/Screenshot_1.png)
[<img src="screenshots/Screenshot_2.png" width=160>](screenshots/Screenshot_2.png)
@ -34,12 +39,14 @@
[<img src="screenshots/Screenshot_7.png" width=160>](screenshots/Screenshot_7.png)
[<img src="screenshots/Screenshot_8.png" width=160>](screenshots/Screenshot_8.png)
## Description
##### Openlib Is An Open Source App To Download And Read Books From Shadow Library ([Annas Archive](https://annas-archive.org/)) . The App Has Built In Reader to Read Books.
## Description 📖
##### As [Annas Archive](https://annas-archive.org/) Doesn't Have An API.The App Works By Sending Request To Annas Archive And Parses The Response To objects.The App Extracts The Mirrors From Response And Downloads The Book.
Openlib Is An Open Source App To Download And Read Books From Shadow Library ([Annas Archive](https://annas-archive.org/)). The App Has Built In Reader to Read Books
As [Annas Archive](https://annas-archive.org/) Doesn't Have An API. The App Works By Sending Request To Annas Archive And Parses The Response To objects. The App Extracts The Mirrors From Response And Downloads The Book
## Features ✨
## Features
- Trending Books
- Download And Read Books With In-Built Viewer
- Supports Epub And Pdf Formats
@ -47,12 +54,12 @@
- Filter Books
- Sort Books
## Roadmap
## Roadmap 🎯
- Adding More Book Format supports (cbz,cbr,azw3,etc...)
- Adding Support For Background Downloads
- Adding Support For Multiple Downloads
- Move existing books when changing the storage path
## Building from Source
@ -60,41 +67,55 @@
- Git Clone The Repo
```
git clone https://github.com/dstark5/Openlib.git
```
```sh
git clone https://github.com/dstark5/Openlib.git
```
- Run the app with Android Studio or VS Code. Or the command line:
```
flutter pub get
flutter run
```
```sh
flutter pub get
flutter run
```
- To Build App Run:
```
flutter build
```
```sh
flutter build
```
- The Build Will Be In './build/app/outputs/flutter-apk/app-release.apk'
## Contribution
### Android
Make sure that `android/local.properties` has `flutter.minSdkVersion=21` or above
## Contributor required 🚧
We are actively seeking contributors. Whether you're a seasoned developer or just starting out, we welcome your contributions to help make this project even better!
## Contribution 💝
Whether you have ideas, design changes or even major code changes, help is always welcome. The app gets better and better with each contribution, no matter how big or small!
If you'd like to get involved See [CONTRIBUTING.md](./CONTRIBUTING.md) for the guidelines.
## Issues
## Issues 🚩
Please report bugs via the [issue tracker](https://github.com/dstark5/Openlib/issues).
## Donate
If you like Openlib, you're welcome to send a donation.
#### [Donate To Annas Archive](https://annas-archive.org/donate?tier=1).
## Donate 🎁
If you like Openlib, you're welcome to send a donation.
[Donate To Annas Archive.](https://annas-archive.org/donate?tier=1)
## License 📜
## License
[![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](https://www.gnu.org/licenses/gpl-3.0.en.html)
Openlib is a free software licensed under GPL v3.0 It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY.[GNU General Public License](https://www.gnu.org/licenses/gpl.html) as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
Openlib is a free software licensed under GPL v3.0 It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY. [GNU General Public License](https://www.gnu.org/licenses/gpl.html) as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
## Disclaimer
## Disclaimer ⚠️
Openlib does not own or have any affiliation with the books available through the app.All books are the property of their respective owners and are protected by copyright law.Openlib is not responsible for any infringement of copyright or other intellectual property rights that may result from the use of the books available through the app.By using the app, you agree to use the books only for personal, non-commercial purposes and in compliance with all applicable laws and regulations.
Openlib does not own or have any affiliation with the books available through the app. All books are the property of their respective owners and are protected by copyright law. Openlib is not responsible for any infringement of copyright or other intellectual property rights that may result from the use of the books available through the app. By using the app, you agree to use the books only for personal, non-commercial purposes and in compliance with all applicable laws and regulations.

View File

@ -25,7 +25,8 @@
android:name="${applicationName}"
android:requestLegacyExternalStorage="true"
android:networkSecurityConfig="@xml/network_security_config"
android:icon="@mipmap/launcher_icon">
android:icon="@mipmap/launcher_icon"
android:enableOnBackInvokedCallback="true">
<activity
android:name=".MainActivity"

View File

@ -1,10 +1,14 @@
<p>
<i>Openlib</i> is an open source app to download and read books from shadow library (<a href='https://annas-archive.org/' target='_blank' rel='nofollow noopener'>Annas Archive</a>). The App Has Built In Reader to Read Books.
<b><i>Openlib</i></b> is an open source app to download and read books from shadow library (<a href='https://annas-archive.org/' target='_blank' rel='nofollow noopener'>Annas Archive</a>). The App Has Built In Reader to Read Books.
</p>
<p>
As <i>Annas Archive</i> doesn't have an API, the app works by sending requests to <i>Annas Archive</i> and parses the response to objects. The app extracts the mirrors from the responses, downloads the book and stores it in the application's document directory.
</p>
<p>Main Features:</p>
<p>
<b>Note :</b>
The app requires VPN to function properly . Without VPN the might show the captcha required page even after completing the captcha
</p>
<b><p>Main Features:</p></b>
<ul>
<li>Trending Books</li>
<li>Download And Read Books With In-Built Viewer</li>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

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

View File

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

44
ios/Podfile Normal file
View File

@ -0,0 +1,44 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '12.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end

View File

@ -1,16 +1,23 @@
// Dart imports:
import 'dart:io' show Platform;
// Flutter imports:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
// Package imports:
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_nav_bar/google_nav_bar.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
// Project imports:
import 'package:openlib/services/database.dart' show MyLibraryDb;
import 'package:openlib/ui/mylibrary_page.dart';
import 'package:openlib/ui/search_page.dart';
import 'package:openlib/ui/settings_page.dart';
import 'package:openlib/ui/themes.dart';
import 'package:openlib/ui/trending_page.dart';
import 'package:openlib/ui/search_page.dart';
import 'package:openlib/ui/mylibrary_page.dart';
import 'package:openlib/ui/settings_page.dart';
import 'package:openlib/services/database.dart' show Sqlite, MyLibraryDb;
import 'package:openlib/services/files.dart'
show moveFilesToAndroidInternalStorage;
import 'package:openlib/state/state.dart'
@ -20,8 +27,7 @@ import 'package:openlib/state/state.dart'
openPdfWithExternalAppProvider,
openEpubWithExternalAppProvider,
userAgentProvider,
cookieProvider,
dbProvider;
cookieProvider;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
@ -31,13 +37,22 @@ void main() async {
databaseFactory = databaseFactoryFfi;
}
Database initDb = await Sqlite.initDb();
MyLibraryDb dataBase = MyLibraryDb(dbInstance: initDb);
bool isDarkMode = await dataBase.getPreference('darkMode');
bool openPdfwithExternalapp =
await dataBase.getPreference('openPdfwithExternalApp');
bool openEpubwithExternalapp =
await dataBase.getPreference('openEpubwithExternalApp');
MyLibraryDb dataBase = MyLibraryDb.instance;
bool isDarkMode =
await dataBase.getPreference('darkMode') == 0 ? false : true;
bool openPdfwithExternalapp = await dataBase
.getPreference('openPdfwithExternalApp')
.catchError((e) => print(e)) ==
0
? false
: true;
bool openEpubwithExternalapp = await dataBase
.getPreference('openEpubwithExternalApp')
.catchError((e) => print(e)) ==
0
? false
: true;
String browserUserAgent = await dataBase.getBrowserOptions('userAgent');
String browserCookie = await dataBase.getBrowserOptions('cookie');
@ -53,7 +68,6 @@ void main() async {
runApp(
ProviderScope(
overrides: [
dbProvider.overrideWithValue(dataBase),
themeModeProvider.overrideWith(
(ref) => isDarkMode ? ThemeMode.dark : ThemeMode.light),
openPdfWithExternalAppProvider
@ -76,7 +90,7 @@ class MyApp extends ConsumerWidget {
builder: (BuildContext context, Widget? child) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(
textScaleFactor: 1.0,
textScaler: const TextScaler.linear(1.0),
),
child: child!,
);
@ -114,7 +128,7 @@ class _HomePageState extends ConsumerState<HomePage> {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.primary,
backgroundColor: Theme.of(context).colorScheme.background,
title: const Text("Openlib"),
titleTextStyle: Theme.of(context).textTheme.displayLarge,
),

View File

@ -1,6 +1,8 @@
import 'package:dio/dio.dart';
import 'dart:convert';
// Flutter imports:
import 'package:flutter/material.dart';
// Package imports:
import 'package:dio/dio.dart';
import 'package:html/parser.dart' show parse;
class BookData {
@ -23,33 +25,25 @@ class BookData {
}
class BookInfoData extends BookData {
final List<String>? mirrors;
String? mirror;
final String? description;
final String? format;
BookInfoData(
{required String title,
required String? author,
required String? thumbnail,
required String? publisher,
required String? info,
required String link,
required String md5,
required this.mirrors,
{required super.title,
required super.author,
required super.thumbnail,
required super.publisher,
required super.info,
required super.link,
required super.md5,
required this.format,
required this.description})
: super(
title: title,
author: author,
thumbnail: thumbnail,
publisher: publisher,
info: info,
link: link,
md5: md5);
required this.mirror,
required this.description});
}
class AnnasArchieve {
String baseUrl = "https://annas-archive.org";
static const String baseUrl = "https://annas-archive.org";
final Dio dio = Dio();
@ -134,67 +128,82 @@ class AnnasArchieve {
}
}
Future<String?> _getMirrorLink(
String url, String userAgent, String cookie) async {
try {
final response = await dio.get(url,
options: Options(extra: {
'withCredentials': true
}, headers: {
"Host": "annas-archive.org",
"Origin": "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
}));
// Future<String?> _getMirrorLink(
// String url, String userAgent, String cookie) async {
// try {
// final response = await dio.get(url,
// options: Options(extra: {
// 'withCredentials': true
// }, headers: {
// "Host": "annas-archive.org",
// "Origin": baseUrl,
// "Upgrade-Insecure-Requests": "1",
// "Sec-Fetch-Dest": "secure",
// "Sec-Fetch-Mode": "navigate",
// "Sec-Fetch-Site": "same-site",
// "Cookie": cookie,
// "User-Agent": userAgent
// }));
var document = parse(response.data.toString());
// var document = parse(response.data.toString());
var pTag = document.querySelectorAll('p[class="mb-4"]');
String? link = pTag[1].querySelector('a')?.attributes['href'];
return link;
} catch (e) {
// print('${url} ${e}');
if (e.toString().contains("403")) {
throw jsonEncode({"code": "403", "url": url});
}
return null;
}
}
// var pTag = document.querySelectorAll('p[class="mb-4"]');
// String? link = pTag[1].querySelector('a')?.attributes['href'];
// return link;
// } catch (e) {
// // print('${url} ${e}');
// if (e.toString().contains("403")) {
// throw jsonEncode({"code": "403", "url": url});
// }
// return null;
// }
// }
Future<BookInfoData?> _bookInfoParser(resData, url, userAgent, cookie) async {
Future<BookInfoData?> _bookInfoParser(resData, url) async {
var document = parse(resData.toString());
var main = document.querySelector('main[class="main"]');
var ul = main?.querySelectorAll('ul[class="mb-4"]');
var ul = main?.querySelectorAll('ul[class="list-inside mb-4 ml-1"]');
List<String> mirrors = [];
// List<String> mirrors = [];
// if (ul != null) {
// var anchorTags = [];
// for (var e in ul) {
// anchorTags.insertAll(0, e.querySelectorAll('a'));
// }
// for (var element in anchorTags) {
// if (element.attributes['href'] != null &&
// element.attributes['href']!.startsWith('/slow_download') &&
// element.attributes['href']!.endsWith('/2')) {
// String? url =
// await _getMirrorLink('$baseUrl${element.attributes['href']!}');
// if (url != null && url.isNotEmpty) {
// mirrors.add(url);
// }
// } else if (element.attributes['href']!.startsWith('https://')) {
// if (element.attributes['href'] != null &&
// element.attributes['href'].contains('ipfs') == true) {
// mirrors.add(element.attributes['href']!);
// }
// }
// }
// }
String? mirror;
var anchorTags = [];
if (ul != null) {
var anchorTags = [];
if (ul.length == 2) {
anchorTags = ul[1].querySelectorAll('a');
} else {
anchorTags = ul[0].querySelectorAll('a');
for (var e in ul) {
anchorTags.insertAll(0, e.querySelectorAll('a'));
}
}
for (var element in anchorTags) {
if (element.attributes['href']!.startsWith('https://')) {
if (element.attributes['href'] != null &&
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')) {
String? url = await _getMirrorLink(
'$baseUrl${element.attributes['href']!}', userAgent, cookie);
if (url != null && url.isNotEmpty) {
mirrors.add(url);
}
}
for (var element in anchorTags) {
if (element.attributes['href'] != null &&
element.attributes['href']!.startsWith('/slow_download') &&
element.attributes['href']!.endsWith('/2')) {
mirror = '$baseUrl${element.attributes['href']}';
}
}
@ -210,9 +219,9 @@ class AnnasArchieve {
'info':
main?.querySelector('div[class="text-sm text-gray-500"]')?.text ?? '',
'description': main
?.querySelector(
'div[class="mt-4 line-clamp-[5] js-md5-top-box-description"]')
?.text ??
?.querySelector('div[class="mb-1"]')
?.text
.replaceFirst("description", '') ??
" "
};
@ -237,7 +246,7 @@ class AnnasArchieve {
link: data['link'],
md5: getMd5(data['link'].toString()),
format: getFormat(data['info']),
mirrors: mirrors,
mirror: mirror,
description: data['description'],
);
} else {
@ -287,15 +296,11 @@ class AnnasArchieve {
}
}
Future<BookInfoData> bookInfo(
{required String url,
required String userAgent,
required String cookie}) async {
Future<BookInfoData> bookInfo({required String url}) async {
try {
final response =
await dio.get(url, options: Options(headers: defaultDioHeaders));
BookInfoData? data =
await _bookInfoParser(response.data, url, userAgent, cookie);
BookInfoData? data = await _bookInfoParser(response.data, url);
if (data != null) {
return data;
} else {

View File

@ -1,51 +1,11 @@
// Dart imports:
import 'dart:io';
// Package imports:
import 'package:sqflite/sqflite.dart';
class Sqlite {
static Future<Database> initDb() async {
var databasesPath = await getDatabasesPath();
String path = '$databasesPath/mylibrary.db';
bool isMobile = Platform.isAndroid || Platform.isIOS;
Database dbInstance = await openDatabase(
path,
version: 5,
onCreate: (Database db, int version) async {
await db.execute(
'CREATE TABLE mybooks (id TEXT PRIMARY KEY, title TEXT,author TEXT,thumbnail TEXT,link TEXT,publisher TEXT,info TEXT,format TEXT,description TEXT)');
await db.execute(
'CREATE TABLE preferences (name TEXT PRIMARY KEY,value BOOLEAN)');
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 {
List<dynamic> isTableExist = await db.query('sqlite_master',
where: 'name = ?', whereArgs: ['bookposition']);
List<dynamic> isPreferenceTableExist = await db.query('sqlite_master',
where: 'name = ?', whereArgs: ['preferences']);
List<dynamic> isbrowserOptionsExist = await db.query('sqlite_master',
where: 'name = ?', whereArgs: ['browserOptions']);
if (isPreferenceTableExist.isEmpty) {
await db.execute(
'CREATE TABLE preferences (name TEXT PRIMARY KEY,value BOOLEAN)');
}
if (isMobile && isTableExist.isEmpty) {
await db.execute(
'CREATE TABLE bookposition (fileName TEXT PRIMARY KEY,position TEXT)');
}
if (isMobile && isbrowserOptionsExist.isEmpty) {
await db.execute(
'CREATE TABLE browserOptions (name TEXT PRIMARY KEY,value TEXT)');
}
},
);
return dbInstance;
}
}
// Project imports:
import 'package:openlib/services/files.dart';
class MyBook {
final String id;
@ -90,12 +50,79 @@ class MyBook {
}
class MyLibraryDb {
Database dbInstance;
static final MyLibraryDb instance = MyLibraryDb._internal();
static Database? _database;
MyLibraryDb._internal();
Future<Database> get database async {
if (_database != null) {
return _database!;
}
_database = await _initDatabase();
return _database!;
}
Future<Database> _initDatabase() async {
final databasePath = await getDatabasesPath();
final path = '$databasePath/mylibrary.db';
final bool isMobile = Platform.isAndroid || Platform.isIOS;
return await openDatabase(
path,
version: 5,
onCreate: (Database db, int version) async {
await db.execute(
'CREATE TABLE mybooks (id TEXT PRIMARY KEY, title TEXT,author TEXT,thumbnail TEXT,link TEXT,publisher TEXT,info TEXT,format TEXT,description TEXT)');
await db.execute(
'CREATE TABLE preferences (name TEXT PRIMARY KEY,value TEXT)');
if (isMobile || true) {
// TODO: Breaks getBrowserOptions() on Mac
await db.execute(
'CREATE TABLE bookposition (fileName TEXT PRIMARY KEY,position TEXT)');
await db.execute(
'CREATE TABLE browserOptions (name TEXT PRIMARY KEY,value TEXT)');
}
},
onUpgrade: (db, oldVersion, newVersion) async {
List<dynamic> isTableExist = await db.query('sqlite_master',
where: 'name = ?', whereArgs: ['bookposition']);
List<dynamic> isPreferenceTableExist = await db.query('sqlite_master',
where: 'name = ?', whereArgs: ['preferences']);
List<dynamic> isbrowserOptionsExist = await db.query('sqlite_master',
where: 'name = ?', whereArgs: ['browserOptions']);
if (isPreferenceTableExist.isEmpty) {
await db.execute(
'CREATE TABLE preferences (name TEXT PRIMARY KEY,value TEXT)');
}
if (isMobile && isTableExist.isEmpty) {
await db.execute(
'CREATE TABLE bookposition (fileName TEXT PRIMARY KEY,position TEXT)');
}
if (isMobile && isbrowserOptionsExist.isEmpty) {
await db.execute(
'CREATE TABLE browserOptions (name TEXT PRIMARY KEY,value TEXT)');
}
},
onOpen: (db) async {
final bookStorageDefaultDirectory =
await getBookStorageDefaultDirectory;
await db.execute(
"INSERT OR IGNORE INTO preferences (name, value) VALUES ('darkMode', 0)");
await db.execute(
"INSERT OR IGNORE INTO preferences (name, value) VALUES ('openPdfwithExternalApp', 0)");
await db.execute(
"INSERT OR IGNORE INTO preferences (name, value) VALUES ('openEpubwithExternalApp', 0)");
await db.execute(
"INSERT OR IGNORE INTO preferences (name, value) VALUES ('bookStorageDirectory', '$bookStorageDefaultDirectory')");
},
);
}
// Database dbInstance;
String tableName = 'mybooks';
MyLibraryDb({required this.dbInstance});
Future<void> insert(MyBook book) async {
final dbInstance = await instance.database;
await dbInstance.insert(
tableName,
book.toMap(),
@ -104,6 +131,7 @@ class MyLibraryDb {
}
Future<void> delete(String id) async {
final dbInstance = await instance.database;
await dbInstance.delete(
tableName,
where: 'id = ?',
@ -112,6 +140,7 @@ class MyLibraryDb {
}
Future<MyBook?> getId(String id) async {
final dbInstance = await instance.database;
List<Map<String, dynamic>> data =
await dbInstance.query(tableName, where: 'id = ?', whereArgs: [id]);
List<MyBook> book = listMapToMyBook(data);
@ -122,6 +151,7 @@ class MyLibraryDb {
}
Future<bool> checkIdExists(String id) async {
final dbInstance = await instance.database;
List<Map<String, dynamic>> data =
await dbInstance.query(tableName, where: 'id = ?', whereArgs: [id]);
List<MyBook> book = listMapToMyBook(data);
@ -132,6 +162,7 @@ class MyLibraryDb {
}
Future<List<MyBook>> getAll() async {
final dbInstance = await instance.database;
final List<Map<String, dynamic>> maps = await dbInstance.query(tableName);
return listMapToMyBook(maps);
}
@ -153,6 +184,7 @@ class MyLibraryDb {
}
Future<void> saveBookState(String fileName, String position) async {
final dbInstance = await instance.database;
await dbInstance.insert(
'bookposition',
{'fileName': fileName, 'position': position},
@ -161,6 +193,7 @@ class MyLibraryDb {
}
Future<void> deleteBookState(String fileName) async {
final dbInstance = await instance.database;
await dbInstance.delete(
'bookposition',
where: 'fileName = ?',
@ -169,6 +202,7 @@ class MyLibraryDb {
}
Future<String?> getBookState(String fileName) async {
final dbInstance = await instance.database;
List<Map<String, dynamic>> data = await dbInstance
.query('bookposition', where: 'fileName = ?', whereArgs: [fileName]);
List<dynamic> dataList = List.generate(data.length, (i) {
@ -181,29 +215,45 @@ class MyLibraryDb {
}
}
Future<void> savePreference(String name, bool value) async {
int boolInt = value ? 1 : 0;
Future<void> savePreference(String name, dynamic value) async {
switch (value.runtimeType) {
case bool:
value = value ? 1 : 0;
break;
case int || String:
break;
default:
throw 'Invalid type';
}
Database dbInstance = await instance.database;
await dbInstance.insert(
'preferences',
{'name': name, 'value': boolInt},
{'name': name, 'value': value},
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
Future<bool> getPreference(String name) async {
Future<dynamic> getPreference(String name) async {
Database dbInstance = await instance.database;
List<Map<String, dynamic>> data = await dbInstance
.query('preferences', where: 'name = ?', whereArgs: [name]);
List<dynamic> dataList = List.generate(data.length, (i) {
return {'name': data[i]['name'], 'value': data[i]['value']};
});
if (dataList.isNotEmpty) {
return dataList[0]['value'] == 0 ? false : true;
} else {
return false;
// Convert to int if possible
int? preference = int.tryParse(dataList[0]['value']);
if (preference != null) {
return preference;
}
// Return string value if not int
return dataList[0]['value'];
}
throw "Preference $name not found";
}
Future<void> setBrowserOptions(String name, String value) async {
final dbInstance = await instance.database;
await dbInstance.insert(
'browserOptions',
{'name': name, 'value': value},
@ -212,6 +262,7 @@ class MyLibraryDb {
}
Future<String> getBrowserOptions(String name) async {
final dbInstance = await instance.database;
List<Map<String, dynamic>> data = await dbInstance
.query('browserOptions', where: 'name = ?', whereArgs: [name]);
List<dynamic> dataList = List.generate(data.length, (i) {

View File

@ -1,10 +1,19 @@
// Dart imports:
import 'dart:io';
// Package imports:
import 'package:crypto/crypto.dart';
import 'package:dio/dio.dart';
import 'package:chunked_downloader/chunked_downloader.dart';
import 'files.dart';
// Project imports:
import 'package:openlib/services/database.dart' show MyLibraryDb;
MyLibraryDb dataBase = MyLibraryDb.instance;
Future<String> _getFilePath(String fileName) async {
final path = await getAppDirectoryPath;
return '$path/$fileName';
String bookStorageDirectory =
await dataBase.getPreference('bookStorageDirectory');
return '$bookStorageDirectory/$fileName';
}
List<String> _reorderMirrors(List<String> mirrors) {
@ -24,15 +33,17 @@ List<String> _reorderMirrors(List<String> mirrors) {
return [...ipfsMirrors, ...httpsMirrors];
}
Future<String?> _getAliveMirror(List<String> mirrors, Dio dio) async {
Future<String?> _getAliveMirror(List<String> mirrors) async {
Dio dio = Dio();
for (var url in mirrors) {
try {
final response = await dio.head(url,
options: Options(receiveTimeout: const Duration(seconds: 5)));
options: Options(receiveTimeout: const Duration(seconds: 10)));
if (response.statusCode == 200) {
dio.close();
return url;
}
} catch (e) {
} catch (_) {
// print("timeOut");
}
}
@ -43,43 +54,79 @@ Future<void> downloadFile(
{required List<String> mirrors,
required String md5,
required String format,
required Function onStart,
required Function onProgress,
required Function cancelDownlaod,
required Function mirrorStatus,
required Function onDownlaodFailed}) async {
Dio dio = Dio();
String path = await _getFilePath('$md5.$format');
List<String> orderedMirrors = _reorderMirrors(mirrors);
String? workingMirror = await _getAliveMirror(orderedMirrors, dio);
// print(workingMirror);
// print(path);
// print(orderedMirrors);
// print(orderedMirrors[0]);
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();
mirrorStatus(true);
cancelDownlaod(chunkedDownloader);
} catch (e) {
onDownlaodFailed();
}
if (mirrors.isEmpty) {
onDownlaodFailed('No mirrors available!');
} else {
onDownlaodFailed();
Dio dio = Dio();
String path = await _getFilePath('$md5.$format');
List<String> orderedMirrors = _reorderMirrors(mirrors);
String? workingMirror = await _getAliveMirror(orderedMirrors);
// print(workingMirror);
// print(path);
// print(orderedMirrors);
// print(orderedMirrors[0]);
if (workingMirror != null) {
onStart();
try {
CancelToken cancelToken = CancelToken();
dio.download(
workingMirror,
path,
options: Options(headers: {
'Connection': 'Keep-Alive',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36'
}),
onReceiveProgress: (rcv, total) {
if (!(rcv.isNaN || rcv.isInfinite) &&
!(total.isNaN || total.isInfinite)) {
onProgress(rcv, total);
}
},
deleteOnError: true,
cancelToken: cancelToken,
).catchError((err) {
if (err.type != DioExceptionType.cancel) {
onDownlaodFailed('downloaded Failed! try again...');
}
throw err;
});
mirrorStatus(true);
cancelDownlaod(cancelToken);
} catch (_) {
onDownlaodFailed('downloaded Failed! try again...');
}
} else {
onDownlaodFailed('No working mirrors available to download book!');
}
}
}
Future<bool> verifyFileCheckSum(
{required String md5Hash, required String format}) async {
try {
final bookStorageDirectory =
await dataBase.getPreference('bookStorageDirectory');
final filePath = '$bookStorageDirectory/$md5Hash.$format';
final file = File(filePath);
final stream = file.openRead();
final hash = await md5.bind(stream).first;
if (md5Hash == hash.toString()) {
return true;
}
return false;
} catch (_) {
return false;
}
}

View File

@ -1,9 +1,17 @@
// Dart imports:
import 'dart:io';
// Package imports:
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:path_provider/path_provider.dart';
import 'package:openlib/state/state.dart' show dbProvider, myLibraryProvider;
Future<String> get getAppDirectoryPath async {
// Project imports:
import 'package:openlib/services/database.dart';
import 'package:openlib/state/state.dart' show myLibraryProvider;
MyLibraryDb dataBase = MyLibraryDb.instance;
Future<String> get getBookStorageDefaultDirectory async {
if (Platform.isAndroid) {
final directory = await getExternalStorageDirectory();
return directory!.path;
@ -32,6 +40,23 @@ Future<void> moveFilesToAndroidInternalStorage() async {
}
}
Future<void> moveFolderContents(
String source_path, String destination_path) async {
final source = Directory(source_path);
source.listSync(recursive: false).forEach((var entity) {
if (entity is Directory) {
var newDirectory =
Directory('${destination_path}/${entity.path.split('/').last}');
newDirectory.createSync();
moveFolderContents(entity.path, newDirectory.path);
entity.deleteSync();
} else if (entity is File) {
entity.copySync('${destination_path}/${entity.path.split('/').last}');
entity.deleteSync();
}
});
}
Future<bool> isFileExists(String filePath) async {
return await File(filePath).exists();
}
@ -43,8 +68,9 @@ Future<void> deleteFile(String filePath) async {
}
Future<String> getFilePath(String fileName) async {
String appDirPath = await getAppDirectoryPath;
String filePath = '$appDirPath/$fileName';
final bookStorageDirectory =
await dataBase.getPreference('bookStorageDirectory');
String filePath = '$bookStorageDirectory/$fileName';
bool isExists = await isFileExists(filePath);
if (isExists == true) {
return filePath;
@ -56,10 +82,11 @@ Future<void> deleteFileWithDbData(
FutureProviderRef ref, String md5, String format) async {
try {
String fileName = '$md5.$format';
String appDirPath = await getAppDirectoryPath;
await deleteFile('$appDirPath/$fileName');
await ref.read(dbProvider).delete(md5);
await ref.read(dbProvider).deleteBookState(fileName);
final bookStorageDirectory =
await dataBase.getPreference('bookStorageDirectory');
await deleteFile('$bookStorageDirectory/$fileName');
await dataBase.delete(md5);
await dataBase.deleteBookState(fileName);
// ignore: unused_result
ref.refresh(myLibraryProvider);
} catch (e) {

View File

@ -1,3 +1,4 @@
// Package imports:
import 'package:dio/dio.dart';
import 'package:html/parser.dart' show parse;
@ -7,9 +8,31 @@ class TrendingBookData {
TrendingBookData({this.title, this.thumbnail});
}
class OpenLibrary {
String url = "https://openlibrary.org/trending/daily";
abstract class TrendingBooksImpl {
String url = '';
int timeOutDuration = 20;
List<TrendingBookData> _parser(dynamic data);
Future<List<TrendingBookData>> trendingBooks() async {
try {
final dio = Dio();
final response = await dio.get(url,
options: Options(
sendTimeout: Duration(seconds: timeOutDuration),
receiveTimeout: Duration(seconds: timeOutDuration)));
return _parser(response.data.toString());
} on DioException catch (e) {
return [];
}
}
}
class OpenLibrary extends TrendingBooksImpl {
OpenLibrary() {
super.url = "https://openlibrary.org/trending/daily";
}
@override
List<TrendingBookData> _parser(data) {
var document = parse(data.toString());
var bookList = document.querySelectorAll('li[class="searchResultItem"]');
@ -31,31 +54,33 @@ class OpenLibrary {
return trendingBooks;
}
@override
Future<List<TrendingBookData>> trendingBooks() async {
try {
final dio = Dio();
const timeOutDuration = 5;
final response = await dio.get(url,
options: Options(
sendTimeout: const Duration(seconds: 20),
receiveTimeout: const Duration(seconds: 20)));
sendTimeout: const Duration(seconds: timeOutDuration),
receiveTimeout: const Duration(seconds: timeOutDuration)));
final response2 = await dio.get(
"https://openlibrary.org/trending/daily?page=2",
options: Options(
sendTimeout: const Duration(seconds: 20),
receiveTimeout: const Duration(seconds: 20)));
sendTimeout: const Duration(seconds: timeOutDuration),
receiveTimeout: const Duration(seconds: timeOutDuration)));
return _parser('${response.data.toString()}${response2.data.toString()}');
} on DioException catch (e) {
if (e.type == DioExceptionType.unknown) {
throw "socketException";
}
rethrow;
return [];
}
}
}
class GoodReads {
String url = "https://www.goodreads.com/shelf/show/trending";
class GoodReads extends TrendingBooksImpl {
GoodReads() {
super.url = "https://www.goodreads.com/shelf/show/trending";
}
@override
List<TrendingBookData> _parser(data) {
var document = parse(data.toString());
var bookList = document.querySelectorAll('div[class="elementList"]');
@ -74,35 +99,24 @@ class GoodReads {
?.attributes['title']
.toString()
.trim(),
thumbnail:
thumbnail.toString().replaceAll("._SY75_.", "._SY225_.")),
thumbnail: thumbnail
.toString()
.replaceAll("._SY75_.", "._SY225_.")
.replaceAll("._SX50_.", "._SX148_.")),
);
}
}
return trendingBooks;
}
Future<List<TrendingBookData>> trendingBooks() async {
try {
final dio = Dio();
final response = await dio.get(url,
options: Options(
sendTimeout: const Duration(seconds: 20),
receiveTimeout: const Duration(seconds: 20)));
return _parser(response.data.toString());
} on DioException catch (e) {
if (e.type == DioExceptionType.unknown) {
throw "socketException";
}
rethrow;
}
}
}
class PenguinRandomHouse {
String url =
"https://www.penguinrandomhouse.com/ajaxc/categories/books/?from=0&to=50&contentId=&elClass=book&dataType=html&catFilter=best-sellers";
class PenguinRandomHouse extends TrendingBooksImpl {
PenguinRandomHouse() {
super.url =
"https://www.penguinrandomhouse.com/ajaxc/categories/books/?from=0&to=50&contentId=&elClass=book&dataType=html&catFilter=best-sellers";
}
@override
List<TrendingBookData> _parser(data) {
var document = parse(data.toString());
var bookList = document.querySelectorAll('div[class="book"]');
@ -129,20 +143,34 @@ class PenguinRandomHouse {
}
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";
class BookDigits extends TrendingBooksImpl {
BookDigits() {
super.url = "https://bookdigits.com/fresh";
}
@override
List<TrendingBookData> _parser(data) {
var document = parse(data.toString());
var bookList = document.querySelectorAll('div[class="list-row"]');
List<TrendingBookData> trendingBooks = [];
for (var element in bookList) {
if (element.querySelector('div[class="list-title link-reg"]')?.text !=
null &&
element.querySelector('img')?.attributes['src'] != null) {
String? thumbnail = element.querySelector('img')?.attributes['src'];
trendingBooks.add(
TrendingBookData(
title: element
.querySelector('div[class="list-title link-reg"]')
?.text
.toString()
.trim(),
thumbnail: thumbnail.toString()),
);
}
rethrow;
}
return trendingBooks;
}
}

View File

@ -1,12 +1,20 @@
// Dart imports:
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:openlib/services/database.dart';
import 'package:chunked_downloader/chunked_downloader.dart';
import 'package:openlib/services/open_library.dart';
// Flutter imports:
import 'package:flutter/material.dart';
// Package imports:
import 'package:dio/dio.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// Project imports:
import 'package:openlib/services/annas_archieve.dart';
import 'package:openlib/services/database.dart';
import 'package:openlib/services/files.dart';
import 'package:openlib/services/open_library.dart';
MyLibraryDb dataBase = MyLibraryDb.instance;
//Provider for dropdownbutton in search page
@ -64,17 +72,22 @@ final searchQueryProvider = StateProvider<String>((ref) => "");
//Provider for Trending Books
final getTrendingBooks = FutureProvider<List<TrendingBookData>>((ref) async {
OpenLibrary openLibrary = OpenLibrary();
// OpenLibrary openLibrary = OpenLibrary();
GoodReads goodReads = GoodReads();
PenguinRandomHouse penguinTrending = PenguinRandomHouse();
BookDigits bookDigits = BookDigits();
List<TrendingBookData> trendingBooks =
await Future.wait<List<TrendingBookData>>([
openLibrary.trendingBooks(),
goodReads.trendingBooks(),
penguinTrending.trendingBooks()
penguinTrending.trendingBooks(),
// openLibrary.trendingBooks(),
bookDigits.trendingBooks(),
]).then((List<List<TrendingBookData>> listOfData) =>
listOfData.expand((element) => element).toList());
if (trendingBooks.isEmpty) {
throw 'Nothing Trending Today :(';
}
trendingBooks.shuffle();
return trendingBooks;
});
@ -97,14 +110,13 @@ final searchProvider = FutureProvider.family
final cookieProvider = StateProvider<String>((ref) => "");
final userAgentProvider = StateProvider<String>((ref) => "");
final webViewLoadingState = StateProvider.autoDispose<bool>((ref) => true);
//Provider for Book Info
final bookInfoProvider =
FutureProvider.family<BookInfoData, String>((ref, url) async {
AnnasArchieve annasArchieve = AnnasArchieve();
BookInfoData data = await annasArchieve.bookInfo(
url: url,
userAgent: ref.read(userAgentProvider),
cookie: ref.read(cookieProvider));
BookInfoData data = await annasArchieve.bookInfo(url: url);
return data;
});
@ -132,19 +144,26 @@ final getDownloadedFileSize = StateProvider.autoDispose<String>((ref) {
return bytesToFileSize(ref.watch(downloadedFileSizeInBytes));
});
final cancelCurrentDownload = StateProvider<ChunkedDownloader>((ref) {
return ChunkedDownloader(saveFilePath: "", url: "");
final cancelCurrentDownload = StateProvider<CancelToken>((ref) {
return CancelToken();
});
final dbProvider = Provider<MyLibraryDb>((ref) => throw UnimplementedError());
enum ProcessState { waiting, running, complete }
enum CheckSumProcessState { waiting, running, failed, success }
final downloadState =
StateProvider.autoDispose<ProcessState>((ref) => ProcessState.waiting);
final checkSumState = StateProvider.autoDispose<CheckSumProcessState>(
(ref) => CheckSumProcessState.waiting);
final myLibraryProvider = FutureProvider((ref) async {
return await ref.read(dbProvider).getAll();
return dataBase.getAll();
});
final checkIdExists =
FutureProvider.family.autoDispose<bool, String>((ref, id) async {
return await ref.read(dbProvider).checkIdExists(id);
return await dataBase.checkIdExists(id);
});
class FileName {
@ -164,18 +183,18 @@ final totalPdfPage = StateProvider.autoDispose<int>((ref) => 0);
Future<void> savePdfState(String fileName, WidgetRef ref) async {
String position = ref.watch(pdfCurrentPage).toString();
await ref.watch(dbProvider).saveBookState(fileName, position);
await dataBase.saveBookState(fileName, position);
}
Future<void> saveEpubState(
String fileName, String? position, WidgetRef ref) async {
String pos = position ?? '';
await ref.watch(dbProvider).saveBookState(fileName, pos);
await dataBase.saveBookState(fileName, pos);
}
final getBookPosition =
FutureProvider.family.autoDispose<String?, String>((ref, fileName) async {
return await ref.read(dbProvider).getBookState(fileName);
return await dataBase.getBookState(fileName);
});
final openPdfWithExternalAppProvider = StateProvider<bool>((ref) => false);

View File

@ -1,17 +1,23 @@
// Flutter imports:
import 'package:flutter/material.dart';
// Package imports:
import 'package:url_launcher/url_launcher.dart';
import 'package:openlib/ui/components/snack_bar_widget.dart';
// Project imports:
import 'package:openlib/ui/components/page_title_widget.dart';
import 'package:openlib/ui/components/snack_bar_widget.dart';
class AboutPage extends StatelessWidget {
const AboutPage({Key? key}) : super(key: key);
const AboutPage({super.key});
@override
Widget build(BuildContext context) {
const version = "1.0.9";
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.primary,
backgroundColor: Theme.of(context).colorScheme.background,
title: const Text("Openlib"),
titleTextStyle: Theme.of(context).textTheme.displayLarge,
),
@ -43,7 +49,7 @@ class AboutPage extends StatelessWidget {
Padding(
padding: EdgeInsets.only(left: 7, right: 7, top: 5),
child: Text(
"1.0.4",
version,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
@ -87,8 +93,7 @@ class AboutPage extends StatelessWidget {
}
class _UrlText extends StatelessWidget {
const _UrlText({Key? key, required this.text, required this.url})
: super(key: key);
const _UrlText({required this.text, required this.url});
final String url;
final String text;

View File

@ -1,12 +1,25 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// Dart imports:
// import 'dart:convert';
import 'package:openlib/services/database.dart';
import 'package:openlib/ui/components/error_widget.dart';
import 'package:openlib/services/download_file.dart';
// Flutter imports:
import 'package:flutter/material.dart';
// import 'package:flutter/scheduler.dart';
// Package imports:
import 'package:dio/dio.dart' show CancelToken;
import 'package:flutter_riverpod/flutter_riverpod.dart';
// import 'package:flutter_svg/svg.dart';
// Project imports:
import 'package:openlib/services/annas_archieve.dart' show BookInfoData;
import 'package:openlib/services/database.dart';
import 'package:openlib/services/download_file.dart';
import 'package:openlib/ui/components/book_info_widget.dart';
import 'package:openlib/ui/components/error_widget.dart';
import 'package:openlib/ui/components/file_buttons_widget.dart';
import 'package:openlib/ui/components/snack_bar_widget.dart';
import 'package:openlib/ui/webview_page.dart';
import 'package:openlib/state/state.dart'
show
bookInfoProvider,
@ -17,20 +30,15 @@ import 'package:openlib/state/state.dart'
getDownloadedFileSize,
cancelCurrentDownload,
mirrorStatusProvider,
dbProvider,
ProcessState,
CheckSumProcessState,
downloadState,
checkSumState,
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);
const BookInfoPage({super.key, required this.url});
final String url;
@ -39,7 +47,7 @@ class BookInfoPage extends ConsumerWidget {
final bookInfo = ref.watch(bookInfoProvider(url));
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.primary,
backgroundColor: Theme.of(context).colorScheme.background,
title: const Text("Openlib"),
titleTextStyle: Theme.of(context).textTheme.displayLarge,
),
@ -50,91 +58,91 @@ class BookInfoPage extends ConsumerWidget {
data: data, child: ActionButtonWidget(data: data));
},
error: (err, _) {
if (err.toString().contains("403")) {
var errJson = jsonDecode(err.toString());
// 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"]);
})));
});
}
// 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));
},
);
}
// 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(
@ -190,7 +198,17 @@ class _ActionButtonWidgetState extends ConsumerState<ActionButtonWidget> {
color: Colors.white,
)),
onPressed: () async {
await downloadFileWidget(ref, context, widget.data);
final result = await Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return Webview(url: widget.data.mirror ?? '');
}));
if (result != null) {
// ignore: use_build_context_synchronously
widget.data.mirror = result;
// ignore: use_build_context_synchronously
await downloadFileWidget(ref, context, widget.data);
}
},
child: const Padding(
padding: EdgeInsets.all(8.0),
@ -214,19 +232,34 @@ class _ActionButtonWidgetState extends ConsumerState<ActionButtonWidget> {
}
Future<void> downloadFileWidget(
WidgetRef ref, BuildContext context, dynamic data) async {
WidgetRef ref, BuildContext context, BookInfoData data) async {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return _ShowDialog(title: data.title);
});
List<String> mirrors = [data.mirror!];
print(mirrors);
downloadFile(
mirrors: data.mirrors!,
mirrors: mirrors,
md5: data.md5,
format: data.format!,
onStart: () {
ref.read(downloadState.notifier).state = ProcessState.running;
},
onProgress: (int rcv, int total) async {
if (ref.read(totalFileSizeInBytes) != total) {
ref.read(totalFileSizeInBytes.notifier).state = total;
}
ref.read(downloadedFileSizeInBytes.notifier).state = rcv;
ref.read(downloadProgressProvider.notifier).state = rcv / total;
if (rcv / total == 1.0) {
await ref.read(dbProvider).insert(MyBook(
MyLibraryDb dataBase = MyLibraryDb.instance;
await dataBase.insert(MyBook(
id: data.md5,
title: data.title,
author: data.author,
@ -237,6 +270,23 @@ Future<void> downloadFileWidget(
format: data.format,
description: data.description));
ref.read(downloadState.notifier).state = ProcessState.complete;
ref.read(checkSumState.notifier).state = CheckSumProcessState.running;
try {
final checkSum = await verifyFileCheckSum(
md5Hash: data.md5, format: data.format!);
if (checkSum == true) {
ref.read(checkSumState.notifier).state =
CheckSumProcessState.success;
} else {
ref.read(checkSumState.notifier).state =
CheckSumProcessState.failed;
}
} catch (_) {
ref.read(checkSumState.notifier).state =
CheckSumProcessState.failed;
}
// ignore: unused_result
ref.refresh(checkIdExists(data.md5));
// ignore: unused_result
@ -245,23 +295,15 @@ Future<void> downloadFileWidget(
showSnackBar(context: context, message: 'Book has been downloaded!');
}
},
cancelDownlaod: (ChunkedDownloader downloadToken) {
cancelDownlaod: (CancelToken downloadToken) {
ref.read(cancelCurrentDownload.notifier).state = downloadToken;
},
mirrorStatus: (val) {
ref.read(mirrorStatusProvider.notifier).state = val;
},
onDownlaodFailed: () {
onDownlaodFailed: (msg) {
Navigator.of(context).pop();
showSnackBar(
context: context, message: 'downloaded Failed! try again...');
});
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return _ShowDialog(title: data.title);
showSnackBar(context: context, message: msg.toString());
});
}
@ -276,9 +318,18 @@ class _ShowDialog extends ConsumerWidget {
final fileSize = ref.watch(getTotalFileSize);
final downloadedFileSize = ref.watch(getDownloadedFileSize);
final mirrorStatus = ref.watch(mirrorStatusProvider);
final downloadProcessState = ref.watch(downloadState);
final checkSumVerifyState = ref.watch(checkSumState);
if (downloadProgress == 1.0) {
Navigator.of(context).pop();
if (downloadProgress == 1.0 &&
(checkSumVerifyState == CheckSumProcessState.failed ||
checkSumVerifyState == CheckSumProcessState.success)) {
Future.delayed(const Duration(seconds: 1), () {
Navigator.of(context).pop();
if (checkSumVerifyState == CheckSumProcessState.failed) {
_showWarningFileDialog(context);
}
});
}
return Stack(
@ -288,7 +339,7 @@ class _ShowDialog extends ConsumerWidget {
padding: const EdgeInsets.all(15.0),
child: Container(
width: double.infinity,
height: 285,
height: 345,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
color: Theme.of(context).colorScheme.tertiaryContainer,
@ -367,6 +418,109 @@ class _ShowDialog extends ConsumerWidget {
),
]),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
switch (downloadProcessState) {
ProcessState.waiting => Icon(
Icons.timer_sharp,
size: 15,
color: Theme.of(context)
.colorScheme
.tertiary
.withAlpha(140),
),
ProcessState.running => SizedBox(
width: 9,
height: 9,
child: CircularProgressIndicator(
color:
Theme.of(context).colorScheme.secondary,
strokeWidth: 2.5,
strokeCap: StrokeCap.round,
),
),
ProcessState.complete => const Icon(
Icons.check_circle,
size: 15,
color: Colors.green,
),
},
const SizedBox(
width: 3,
),
Text(
"Downloading",
style: TextStyle(
fontSize: 11.5,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.colorScheme
.tertiary
.withAlpha(140),
decoration: TextDecoration.none),
overflow: TextOverflow.ellipsis,
maxLines: 2,
textAlign: TextAlign.start,
),
]),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
switch (checkSumVerifyState) {
CheckSumProcessState.waiting => Icon(
Icons.timer_sharp,
size: 15,
color: Theme.of(context)
.colorScheme
.tertiary
.withAlpha(140),
),
CheckSumProcessState.running => SizedBox(
width: 9,
height: 9,
child: CircularProgressIndicator(
color:
Theme.of(context).colorScheme.secondary,
strokeWidth: 2.5,
strokeCap: StrokeCap.round,
),
),
CheckSumProcessState.failed => const Icon(
Icons.close,
size: 15,
color: Colors.red,
),
CheckSumProcessState.success => const Icon(
Icons.check_circle,
size: 15,
color: Colors.green,
),
},
const SizedBox(
width: 3,
),
Text(
"Verifying file checksum",
style: TextStyle(
fontSize: 11.5,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.colorScheme
.tertiary
.withAlpha(140),
decoration: TextDecoration.none),
overflow: TextOverflow.ellipsis,
maxLines: 2,
textAlign: TextAlign.start,
),
]),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
@ -417,7 +571,7 @@ class _ShowDialog extends ConsumerWidget {
color: Colors.white,
)),
onPressed: () {
ref.read(cancelCurrentDownload).stop();
ref.read(cancelCurrentDownload).cancel();
Navigator.of(context).pop();
},
child: const Padding(
@ -437,3 +591,52 @@ class _ShowDialog extends ConsumerWidget {
);
}
}
Future<void> _showWarningFileDialog(BuildContext context) async {
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
title: Text(
'Checksum failed!',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.secondary,
decoration: TextDecoration.none,
letterSpacing: 1),
),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text(
'The downloaded book may be malicious. Delete it and get the same book from another source, or use the book at your own risk.',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.tertiary.withAlpha(170),
),
),
],
),
),
actions: <Widget>[
TextButton(
child: Text(
'Okay',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.secondary,
decoration: TextDecoration.none,
letterSpacing: 1),
),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}

View File

@ -1,7 +1,12 @@
// Flutter imports:
import 'package:flutter/material.dart';
import 'package:openlib/ui/extensions.dart';
// Package imports:
import 'package:cached_network_image/cached_network_image.dart';
// Project imports:
import 'package:openlib/ui/extensions.dart';
String? getFileType(String? info) {
if (info != null && info.isNotEmpty) {
info = info.toLowerCase();
@ -16,15 +21,14 @@ String? getFileType(String? info) {
class BookInfoCard extends StatelessWidget {
const BookInfoCard(
{Key? key,
{super.key,
required this.title,
required this.author,
required this.publisher,
required this.thumbnail,
required this.info,
required this.link,
required this.onClick})
: super(key: key);
required this.onClick});
final String title;
final String author;

View File

@ -1,12 +1,14 @@
// Flutter imports:
import 'package:flutter/material.dart';
// Package imports:
import 'package:cached_network_image/cached_network_image.dart';
class BookInfoWidget extends StatelessWidget {
final Widget child;
final dynamic data;
const BookInfoWidget({Key? key, required this.child, required this.data})
: super(key: key);
const BookInfoWidget({super.key, required this.child, required this.data});
@override
Widget build(BuildContext context) {
@ -145,9 +147,7 @@ class _TopPaddedText extends StatelessWidget {
required this.fontSize,
required this.topPadding,
required this.color,
required this.maxLines,
Key? key})
: super(key: key);
required this.maxLines});
@override
Widget build(BuildContext context) {

View File

@ -1,7 +1,12 @@
// Flutter imports:
import 'package:flutter/material.dart';
// Package imports:
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:openlib/ui/components/snack_bar_widget.dart';
// Project imports:
import 'package:openlib/state/state.dart' show FileName, deleteFileFromMyLib;
import 'package:openlib/ui/components/snack_bar_widget.dart';
class ShowDeleteDialog extends ConsumerWidget {
final String id;

View File

@ -1,7 +1,12 @@
// Flutter imports:
import 'package:flutter/material.dart';
import 'package:openlib/ui/extensions.dart';
// Package imports:
import 'package:flutter_svg/svg.dart';
// Project imports:
import 'package:openlib/ui/extensions.dart';
// ignore: must_be_immutable
class CustomErrorWidget extends StatelessWidget {
final Object error;
@ -9,8 +14,10 @@ class CustomErrorWidget extends StatelessWidget {
VoidCallback? onRefresh;
CustomErrorWidget(
{Key? key, required this.error, required this.stackTrace, this.onRefresh})
: super(key: key);
{super.key,
required this.error,
required this.stackTrace,
this.onRefresh});
@override
Widget build(BuildContext context) {

View File

@ -1,10 +1,14 @@
// Flutter imports:
import 'package:flutter/material.dart';
// Package imports:
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:open_file/open_file.dart';
// Project imports:
import 'package:openlib/services/files.dart' show getFilePath;
import 'package:openlib/ui/components/snack_bar_widget.dart';
import 'package:openlib/ui/components/delete_dialog_widget.dart';
import 'package:openlib/ui/components/snack_bar_widget.dart';
import 'package:openlib/ui/epub_viewer.dart' show launchEpubViewer;
import 'package:openlib/ui/pdf_viewer.dart' show launchPdfViewer;
@ -14,11 +18,10 @@ class FileOpenAndDeleteButtons extends ConsumerWidget {
final Function onDelete;
const FileOpenAndDeleteButtons(
{Key? key,
{super.key,
required this.id,
required this.format,
required this.onDelete})
: super(key: key);
required this.onDelete});
@override
Widget build(BuildContext context, WidgetRef ref) {
@ -99,11 +102,11 @@ Future<void> openCbrAndCbz(
{required String fileName, required BuildContext context}) async {
try {
String path = await getFilePath(fileName);
await OpenFile.open(path);
await OpenFile.open(path, linuxByProcess: true);
} catch (e) {
// ignore: avoid_print
// print(e);
// ignore: use_build_context_synchronously
showSnackBar(context: context, message: 'Unable to open pdf!');
showSnackBar(context: context, message: 'Unable to open file!');
}
}

View File

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

View File

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

View File

@ -1,18 +1,21 @@
// Dart imports:
import 'dart:io';
import 'dart:convert';
// Flutter imports:
import 'package:flutter/material.dart';
// Package imports:
import 'package:epub_view/epub_view.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:vocsy_epub_viewer/epub_viewer.dart';
import 'package:open_file/open_file.dart';
// Project imports:
import 'package:openlib/services/files.dart' show getFilePath;
import 'package:openlib/ui/components/snack_bar_widget.dart';
import 'package:openlib/state/state.dart'
show
filePathProvider,
saveEpubState,
dbProvider,
getBookPosition,
openEpubWithExternalAppProvider;
@ -22,33 +25,19 @@ Future<void> launchEpubViewer(
required WidgetRef ref}) async {
if (Platform.isAndroid || Platform.isIOS) {
String path = await getFilePath(fileName);
String? epubConfig = await ref.read(dbProvider).getBookState(fileName);
bool openWithExternalApp = ref.watch(openEpubWithExternalAppProvider);
if (openWithExternalApp) {
await OpenFile.open(path);
await OpenFile.open(path, linuxByProcess: true);
} else {
try {
VocsyEpub.setConfig(
// ignore: use_build_context_synchronously
themeColor: const Color.fromARGB(255, 210, 15, 1),
identifier: "iosBook",
scrollDirection: EpubScrollDirection.HORIZONTAL,
);
if ((epubConfig?.isNotEmpty ?? true) &&
(epubConfig != null) &&
(!(epubConfig.startsWith('epubcfi')))) {
VocsyEpub.open(path,
lastLocation: EpubLocator.fromJson(json.decode(epubConfig)));
} else {
VocsyEpub.open(path);
}
VocsyEpub.locatorStream.listen((locator) async {
await saveEpubState(fileName, locator, ref);
// convert locator from string to json and save to your database to be retrieved later
});
// ignore: use_build_context_synchronously
Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return EpubViewerWidget(
fileName: fileName,
);
}));
} catch (e) {
// ignore: use_build_context_synchronously
showSnackBar(context: context, message: 'Unable to open pdf!');
@ -109,8 +98,7 @@ class _EpubViewState extends ConsumerState<EpubViewerWidget> {
}
class EpubViewer extends ConsumerStatefulWidget {
const EpubViewer({Key? key, required this.filePath, required this.fileName})
: super(key: key);
const EpubViewer({super.key, required this.filePath, required this.fileName});
final String filePath;
final String fileName;

View File

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

View File

@ -1,13 +1,16 @@
// Flutter imports:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:openlib/services/database.dart';
import 'package:openlib/state/state.dart' show dbProvider;
// Package imports:
import 'package:flutter_riverpod/flutter_riverpod.dart';
// Project imports:
import 'package:openlib/services/database.dart';
import 'package:openlib/ui/components/book_info_widget.dart';
import 'package:openlib/ui/components/file_buttons_widget.dart';
class BookPage extends StatelessWidget {
const BookPage({Key? key, required this.id}) : super(key: key);
const BookPage({super.key, required this.id});
final String id;
@ -15,13 +18,15 @@ class BookPage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.primary,
backgroundColor: Theme.of(context).colorScheme.background,
title: const Text("Openlib"),
titleTextStyle: Theme.of(context).textTheme.displayLarge,
),
body: Consumer(
builder: (BuildContext context, WidgetRef ref, _) {
final bookInfo = ref.read(dbProvider).getId(id);
MyLibraryDb dataBase = MyLibraryDb.instance;
final bookInfo = dataBase.getId(id);
return FutureBuilder(
future: bookInfo,

View File

@ -1,13 +1,17 @@
// Flutter imports:
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:openlib/ui/extensions.dart';
import 'package:openlib/ui/mybook_page.dart';
// Package imports:
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
// Project imports:
import 'package:openlib/state/state.dart' show myLibraryProvider;
import 'package:openlib/ui/components/book_card_widget.dart';
import 'package:openlib/ui/components/error_widget.dart';
import 'package:openlib/ui/components/page_title_widget.dart';
import 'package:openlib/ui/components/book_card_widget.dart';
import 'package:openlib/state/state.dart' show myLibraryProvider;
import 'package:openlib/ui/extensions.dart';
import 'package:openlib/ui/mybook_page.dart';
class MyLibraryPage extends ConsumerWidget {
const MyLibraryPage({super.key});

View File

@ -1,6 +1,18 @@
// Dart imports:
import 'dart:io' show Platform;
// Flutter imports:
import 'package:flutter/material.dart';
// Package imports:
import 'package:flutter_pdfview/flutter_pdfview.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:open_file/open_file.dart';
import 'package:url_launcher/url_launcher.dart';
// Project imports:
import 'package:openlib/services/files.dart' show getFilePath;
import 'package:openlib/state/state.dart'
show
filePathProvider,
@ -9,11 +21,6 @@ import 'package:openlib/state/state.dart'
savePdfState,
openPdfWithExternalAppProvider,
getBookPosition;
import 'package:url_launcher/url_launcher.dart';
import 'package:open_file/open_file.dart';
import 'dart:io' show Platform;
import 'package:openlib/services/files.dart' show getFilePath;
Future<void> launchPdfViewer(
{required String fileName,
@ -22,7 +29,7 @@ Future<void> launchPdfViewer(
bool openWithExternalApp = ref.watch(openPdfWithExternalAppProvider);
if (openWithExternalApp) {
String path = await getFilePath(fileName);
await OpenFile.open(path);
await OpenFile.open(path, linuxByProcess: true);
} else {
Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) {
return PdfView(
@ -77,8 +84,7 @@ class _PdfViewState extends ConsumerState<PdfView> {
}
class PdfViewer extends ConsumerStatefulWidget {
const PdfViewer({Key? key, required this.filePath, required this.fileName})
: super(key: key);
const PdfViewer({super.key, required this.filePath, required this.fileName});
final String filePath;
final String fileName;

View File

@ -1,13 +1,17 @@
// Flutter imports:
import 'package:flutter/material.dart';
// Package imports:
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:openlib/ui/extensions.dart';
// Project imports:
import 'package:openlib/state/state.dart' show searchProvider;
import 'package:openlib/ui/book_info_page.dart';
import 'package:openlib/ui/components/book_card_widget.dart';
import 'package:openlib/ui/components/error_widget.dart';
import 'package:openlib/ui/components/page_title_widget.dart';
import 'package:openlib/ui/components/book_card_widget.dart';
import 'package:openlib/state/state.dart' show searchProvider;
import 'package:openlib/ui/extensions.dart';
class ResultPage extends ConsumerWidget {
const ResultPage({super.key, required this.searchQuery});
@ -20,7 +24,7 @@ class ResultPage extends ConsumerWidget {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.primary,
backgroundColor: Theme.of(context).colorScheme.background,
title: const Text("Openlib"),
titleTextStyle: Theme.of(context).textTheme.displayLarge,
),

View File

@ -1,7 +1,14 @@
// Flutter imports:
import 'package:flutter/material.dart';
// Package imports:
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:openlib/ui/results_page.dart';
// Project imports:
import 'package:openlib/ui/components/page_title_widget.dart';
import 'package:openlib/ui/results_page.dart';
import 'components/snack_bar_widget.dart';
import 'package:openlib/state/state.dart'
show
searchQueryProvider,
@ -12,10 +19,9 @@ import 'package:openlib/state/state.dart'
fileType,
sortValues,
enableFiltersState;
import 'components/snack_bar_widget.dart';
class SearchPage extends ConsumerWidget {
const SearchPage({Key? key}) : super(key: key);
const SearchPage({super.key});
void onSubmit(BuildContext context, WidgetRef ref) {
if (ref.read(searchQueryProvider).isNotEmpty) {

View File

@ -1,22 +1,62 @@
// Dart imports:
import 'dart:io';
// Flutter imports:
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';
// Package imports:
import 'package:device_info_plus/device_info_plus.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:openlib/services/files.dart';
import 'package:permission_handler/permission_handler.dart';
// Project imports:
import 'package:openlib/services/database.dart';
import 'package:openlib/ui/about_page.dart';
import 'package:openlib/ui/components/page_title_widget.dart';
import 'package:openlib/state/state.dart'
show
themeModeProvider,
openPdfWithExternalAppProvider,
openEpubWithExternalAppProvider,
dbProvider;
openEpubWithExternalAppProvider;
Future<void> requestStoragePermission() async {
bool permissionGranted = false;
// Check whether the device is running Android 11 or higher
DeviceInfoPlugin plugin = DeviceInfoPlugin();
AndroidDeviceInfo android = await plugin.androidInfo;
// Android < 11
if (android.version.sdkInt < 33) {
if (await Permission.storage.request().isGranted) {
permissionGranted = true;
} else if (await Permission.storage.request().isPermanentlyDenied) {
await openAppSettings();
}
}
// Android > 11
else {
if (await Permission.manageExternalStorage.request().isGranted) {
permissionGranted = true;
} else if (await Permission.manageExternalStorage
.request()
.isPermanentlyDenied) {
await openAppSettings();
} else if (await Permission.manageExternalStorage.request().isDenied) {
permissionGranted = false;
}
}
print("Storage permission status: $permissionGranted");
}
class SettingsPage extends ConsumerWidget {
const SettingsPage({Key? key}) : super(key: key);
const SettingsPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
MyLibraryDb dataBase = MyLibraryDb.instance;
return Padding(
padding: const EdgeInsets.only(left: 5, right: 5, top: 10),
child: SingleChildScrollView(
@ -43,8 +83,7 @@ class SettingsPage extends ConsumerWidget {
onChanged: (bool value) {
ref.read(themeModeProvider.notifier).state =
value == true ? ThemeMode.dark : ThemeMode.light;
ref.read(dbProvider).savePreference('darkMode', value);
dataBase.savePreference('darkMode', value);
if (Platform.isAndroid) {
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
systemNavigationBarColor:
@ -71,9 +110,7 @@ class SettingsPage extends ConsumerWidget {
onChanged: (bool value) {
ref.read(openPdfWithExternalAppProvider.notifier).state =
value;
ref
.read(dbProvider)
.savePreference('openPdfwithExternalApp', value);
dataBase.savePreference('openPdfwithExternalApp', value);
},
)
],
@ -97,13 +134,37 @@ class SettingsPage extends ConsumerWidget {
onChanged: (bool value) {
ref.read(openEpubWithExternalAppProvider.notifier).state =
value;
ref
.read(dbProvider)
.savePreference('openEpubwithExternalApp', value);
dataBase.savePreference('openEpubwithExternalApp', value);
},
)
],
),
_PaddedContainer(
onClick: () async {
final currentDirectory =
await dataBase.getPreference('bookStorageDirectory');
String? pickedDirectory =
await FilePicker.platform.getDirectoryPath();
if (pickedDirectory == null) {
return;
}
await requestStoragePermission();
// Attempt moving existing books to the new directory
moveFolderContents(currentDirectory, pickedDirectory);
dataBase.savePreference(
'bookStorageDirectory', pickedDirectory);
},
children: [
Text(
"Change storage path",
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.tertiary,
),
),
Icon(Icons.folder),
]),
_PaddedContainer(
onClick: () {
Navigator.push(context,
@ -130,8 +191,7 @@ class SettingsPage extends ConsumerWidget {
}
class _PaddedContainer extends StatelessWidget {
const _PaddedContainer({Key? key, this.onClick, required this.children})
: super(key: key);
const _PaddedContainer({this.onClick, required this.children});
final VoidCallback? onClick;
final List<Widget> children;

View File

@ -1,7 +1,12 @@
// Flutter imports:
import 'package:flutter/material.dart';
import 'package:openlib/ui/extensions.dart';
// Package imports:
import 'package:google_fonts/google_fonts.dart';
// Project imports:
import 'package:openlib/ui/extensions.dart';
ThemeData lightTheme = ThemeData(
primaryColor: Colors.white,
colorScheme: ColorScheme.light(

View File

@ -1,11 +1,16 @@
// Flutter imports:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'extensions.dart';
import 'package:openlib/ui/components/page_title_widget.dart';
// Package imports:
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// Project imports:
import 'package:openlib/ui/components/error_widget.dart';
import 'package:openlib/ui/components/page_title_widget.dart';
import 'package:openlib/ui/results_page.dart';
import 'extensions.dart';
import 'package:openlib/state/state.dart'
show getTrendingBooks, enableFiltersState;

View File

@ -1,14 +1,12 @@
// Flutter imports:
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;
// Package imports:
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
class Webview extends ConsumerStatefulWidget {
const Webview({Key? key, required this.url}) : super(key: key);
const Webview({super.key, required this.url});
final String url;
@override
// ignore: library_private_types_in_public_api
@ -16,9 +14,22 @@ class Webview extends ConsumerStatefulWidget {
}
class _WebviewState extends ConsumerState<Webview> {
WebViewController controller = WebViewController();
final GlobalKey webViewKey = GlobalKey();
InAppWebViewController? webViewController;
final urlController = TextEditingController();
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
final cookieManager = cookiejar.WebviewCookieManager();
@override
Widget build(BuildContext context) {
return Scaffold(
@ -27,47 +38,42 @@ class _WebviewState extends ConsumerState<Webview> {
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;");
child: Column(
children: <Widget>[
Expanded(
child: Stack(
children: [
InAppWebView(
key: webViewKey,
initialUrlRequest: URLRequest(url: WebUri(widget.url)),
onWebViewCreated: (controller) {
webViewController = controller;
},
onLoadStart: (controller, url) {},
onLoadStop: (controller, url) async {
String query =
"""var paragraphTag=document.querySelector('p[class="mb-4 text-xl font-bold"]');var anchorTagHref=paragraphTag.querySelector('a').href;var url=()=>{return anchorTagHref};url();""";
String? mirrorLink = await webViewController
?.evaluateJavascript(source: query);
// final ipfsUrl = widget.url
// .replaceAll("slow_download", "ipfs_downloads")
// .replaceAll("/0/2", "");
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);
}
},
)),
// await webViewController?.loadUrl(
// urlRequest: URLRequest(
// url: WebUri('https://example.com/new-page')));
if (mirrorLink != null) {
Future.delayed(const Duration(milliseconds: 70), () {
// ignore: use_build_context_synchronously
Navigator.pop(context, mirrorLink);
});
}
},
),
],
),
),
],
),
),
);

View File

@ -6,9 +6,13 @@
#include "generated_plugin_registrant.h"
#include <open_file_linux/open_file_linux_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) open_file_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "OpenFileLinuxPlugin");
open_file_linux_plugin_register_with_registrar(open_file_linux_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);

View File

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

View File

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

View File

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

View File

@ -5,11 +5,17 @@
import FlutterMacOS
import Foundation
import device_info_plus
import flutter_inappwebview_macos
import open_file_mac
import path_provider_foundation
import sqflite
import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin"))
OpenFilePlugin.register(with: registry.registrar(forPlugin: "OpenFilePlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))

43
macos/Podfile Normal file
View File

@ -0,0 +1,43 @@
platform :osx, '10.14'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_macos_podfile_setup
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_macos_build_settings(target)
end
end

49
macos/Podfile.lock Normal file
View File

@ -0,0 +1,49 @@
PODS:
- device_info_plus (0.0.1):
- FlutterMacOS
- FlutterMacOS (1.0.0)
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- sqflite (0.0.3):
- Flutter
- FlutterMacOS
- url_launcher_macos (0.0.1):
- FlutterMacOS
- webview_flutter_wkwebview (0.0.1):
- Flutter
- FlutterMacOS
DEPENDENCIES:
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
- FlutterMacOS (from `Flutter/ephemeral`)
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
- sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/darwin`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
- webview_flutter_wkwebview (from `Flutter/ephemeral/.symlinks/plugins/webview_flutter_wkwebview/darwin`)
EXTERNAL SOURCES:
device_info_plus:
:path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos
FlutterMacOS:
:path: Flutter/ephemeral
path_provider_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
sqflite:
:path: Flutter/ephemeral/.symlinks/plugins/sqflite/darwin
url_launcher_macos:
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
webview_flutter_wkwebview:
:path: Flutter/ephemeral/.symlinks/plugins/webview_flutter_wkwebview/darwin
SPEC CHECKSUMS:
device_info_plus: ce1b7762849d3ec103d0e0517299f2db7ad60720
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399
webview_flutter_wkwebview: 0982481e3d9c78fd5c6f62a002fcd24fc791f1e4
PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367
COCOAPODS: 1.15.2

View File

@ -27,6 +27,8 @@
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
94DC8CF5FF5142DBE4D793D5 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0F4FBA8BD682AFDC387908E5 /* Pods_Runner.framework */; };
C7F7B62346249DBF21B09589 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 597839FED6E06332F68C6ADA /* Pods_RunnerTests.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -60,11 +62,15 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
026D244AA1C7FAEB6738F1E2 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
0C77C89AD60C9098EEDED45D /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
0F4FBA8BD682AFDC387908E5 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
1A522043CF646715895EA782 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
33CC10ED2044A3C60003C045 /* libgen.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "libgen.app"; sourceTree = BUILT_PRODUCTS_DIR; };
33CC10ED2044A3C60003C045 /* libgen.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = libgen.app; sourceTree = BUILT_PRODUCTS_DIR; };
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
@ -76,8 +82,12 @@
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
4CC42A086113EDD186C59134 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
597839FED6E06332F68C6ADA /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
B3FD517C4809A4AF4328C1B7 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
BE75E83973607FF4034DF300 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -85,6 +95,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C7F7B62346249DBF21B09589 /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -92,6 +103,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
94DC8CF5FF5142DBE4D793D5 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -125,6 +137,7 @@
331C80D6294CF71000263BE5 /* RunnerTests */,
33CC10EE2044A3C60003C045 /* Products */,
D73912EC22F37F3D000D13A0 /* Frameworks */,
F9274C19F91F0638504DB3B3 /* Pods */,
);
sourceTree = "<group>";
};
@ -175,10 +188,26 @@
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
isa = PBXGroup;
children = (
0F4FBA8BD682AFDC387908E5 /* Pods_Runner.framework */,
597839FED6E06332F68C6ADA /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
F9274C19F91F0638504DB3B3 /* Pods */ = {
isa = PBXGroup;
children = (
B3FD517C4809A4AF4328C1B7 /* Pods-Runner.debug.xcconfig */,
1A522043CF646715895EA782 /* Pods-Runner.release.xcconfig */,
0C77C89AD60C9098EEDED45D /* Pods-Runner.profile.xcconfig */,
026D244AA1C7FAEB6738F1E2 /* Pods-RunnerTests.debug.xcconfig */,
BE75E83973607FF4034DF300 /* Pods-RunnerTests.release.xcconfig */,
4CC42A086113EDD186C59134 /* Pods-RunnerTests.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -186,6 +215,7 @@
isa = PBXNativeTarget;
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
EAFD152091A84CC666BC9C95 /* [CP] Check Pods Manifest.lock */,
331C80D1294CF70F00263BE5 /* Sources */,
331C80D2294CF70F00263BE5 /* Frameworks */,
331C80D3294CF70F00263BE5 /* Resources */,
@ -204,11 +234,13 @@
isa = PBXNativeTarget;
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
B03B73F3042AD92BAE2F1724 /* [CP] Check Pods Manifest.lock */,
33CC10E92044A3C60003C045 /* Sources */,
33CC10EA2044A3C60003C045 /* Frameworks */,
33CC10EB2044A3C60003C045 /* Resources */,
33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */,
A920CB9F33F83AF0095EB7FC /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@ -227,7 +259,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 1300;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C80D4294CF70F00263BE5 = {
@ -328,6 +360,67 @@
shellPath = /bin/sh;
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
};
A920CB9F33F83AF0095EB7FC /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
B03B73F3042AD92BAE2F1724 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
EAFD152091A84CC666BC9C95 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@ -379,6 +472,7 @@
/* Begin XCBuildConfiguration section */
331C80DB294CF71000263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 026D244AA1C7FAEB6738F1E2 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
@ -393,6 +487,7 @@
};
331C80DC294CF71000263BE5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = BE75E83973607FF4034DF300 /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
@ -407,6 +502,7 @@
};
331C80DD294CF71000263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 4CC42A086113EDD186C59134 /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,8 @@
name: openlib
description: An Open source app to download and read books from shadow library (Anna`s Archive)
description: An Open source app to download and read books from shadow library (Anna`s Archive)
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
publish_to: "none" # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
@ -16,10 +16,10 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.4+7
version: 1.0.9+12
environment:
sdk: '>=3.0.5 <4.0.0'
sdk: ">=3.3.0 <4.0.0"
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
@ -35,8 +35,8 @@ dependencies:
google_nav_bar: ^5.0.6
flutter_pdfview: ^1.2.7
vocsy_epub_viewer: ^2.0.0
epub_view: ^3.2.0
# vocsy_epub_viewer: ^3.0.0
# syncfusion_flutter_pdfviewer: ^22.2.5
# pdfx: ^2.4.0
@ -45,16 +45,13 @@ dependencies:
sqflite: ^2.3.0
path_provider: ^2.0.15
permission_handler: ^10.4.3
permission_handler: ^11.2.0
open_file: ^3.3.2
webview_flutter: ^4.4.1
webview_cookie_manager: ^2.0.6
flutter_svg: ^2.0.7
google_fonts:
google_fonts: ^6.1.0
cached_network_image: ^3.2.3
chunked_downloader: ^0.0.2
cached_network_image: 3.3.0
sqflite_common_ffi: ^2.3.0+2
url_launcher: ^6.1.12
@ -62,8 +59,13 @@ dependencies:
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
dev: ^1.0.0
crypto: ^3.0.3
file_picker: ^8.1.2
device_info_plus: ^10.1.2
flutter_inappwebview: ^6.0.0
dev_dependencies:
import_sorter: ^4.6.0
flutter_test:
sdk: flutter
@ -73,8 +75,7 @@ dev_dependencies:
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^2.0.0
flutter_lints: ^3.0.1
flutter_icons:
android: "launcher_icon"
@ -86,7 +87,6 @@ flutter_icons:
# The following section is specific to Flutter packages.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
@ -124,4 +124,4 @@ flutter:
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages
# see https://flutter.dev/custom-fonts/#from-packages

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 447 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 536 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 378 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 227 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 379 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 231 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -5,9 +5,13 @@
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
// Flutter imports:
import 'package:flutter/material.dart';
// Package imports:
import 'package:flutter_test/flutter_test.dart';
// Project imports:
import 'package:openlib/main.dart';
void main() {

View File

@ -10,6 +10,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake)
# https://github.com/flutter/flutter/issues/57146.
set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
# Set fallback configurations for older versions of the flutter tool.
if (NOT DEFINED FLUTTER_TARGET_PLATFORM)
set(FLUTTER_TARGET_PLATFORM "windows-x64")
endif()
# === Flutter Library ===
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
@ -92,7 +97,7 @@ add_custom_command(
COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
windows-x64 $<CONFIG>
${FLUTTER_TARGET_PLATFORM} $<CONFIG>
VERBATIM
)
add_custom_target(flutter_assemble DEPENDS