Compare commits
49 Commits
v1.0.4-bet
...
v1.0.9-bet
Author | SHA1 | Date | |
---|---|---|---|
1e84848d39 | |||
78a50e28ff | |||
fda962a46f | |||
a7ca206b10 | |||
6be9927b26 | |||
ea86d76015 | |||
f396f92463 | |||
70e96b12e1 | |||
9a91cffb31 | |||
669e5f7a66 | |||
6e1f75b11a | |||
7f36ca98b7 | |||
1025552f2f | |||
11a7dca93c | |||
ccd60ac357 | |||
d0c8128631 | |||
e1a42df7e7 | |||
50f907eff7 | |||
c0c0de58a5 | |||
f0940e2947 | |||
62ff665b87 | |||
67d894855a | |||
a3ca57772d | |||
d9b19a6ec3 | |||
e013713516 | |||
d655a4bd32 | |||
9b696c7e9d | |||
cbbe049ef8 | |||
cddaeb553a | |||
ced66d0d1c | |||
17dcbb05b3 | |||
27e43a358c | |||
1715b73494 | |||
4c557ed28d | |||
61a4e566df | |||
2a2e5df118 | |||
aac2e807f0 | |||
95288271fa | |||
73a294fb89 | |||
c068714e52 | |||
5fa66ba42f | |||
034c3aaa5e | |||
c7ed1a3c4a | |||
7d550ea399 | |||
dfc70cfce0 | |||
1aedffc919 | |||
7364f69b60 | |||
a342f80fee | |||
3d56cd2c65 |
103
README.md
@ -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 ([Anna’s Archive](https://annas-archive.org/)).
|
||||
<img src="assets/icons/appIcon.png" width="150">
|
||||
|
||||
[](https://flutter.dev/) [](https://opensource.org/licenses/) [](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 ([Anna’s Archive](https://annas-archive.org/))
|
||||
|
||||
[](https://flutter.dev/)
|
||||
[](https://opensource.org/licenses/)
|
||||
[](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 ([Anna’s Archive](https://annas-archive.org/)) . The App Has Built In Reader to Read Books.
|
||||
## Description 📖
|
||||
|
||||
##### As [Anna’s Archive](https://annas-archive.org/) Doesn't Have An API.The App Works By Sending Request To Anna’s Archive And Parses The Response To objects.The App Extracts The Mirrors From Response And Downloads The Book.
|
||||
Openlib Is An Open Source App To Download And Read Books From Shadow Library ([Anna’s Archive](https://annas-archive.org/)). The App Has Built In Reader to Read Books
|
||||
|
||||
As [Anna’s Archive](https://annas-archive.org/) Doesn't Have An API. The App Works By Sending Request To Anna’s Archive And Parses The Response To objects. The App Extracts The Mirrors From Response And Downloads The Book
|
||||
|
||||
## Features ✨
|
||||
|
||||
## 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 Anna’s Archive](https://annas-archive.org/donate?tier=1).
|
||||
## Donate 🎁
|
||||
|
||||
If you like Openlib, you're welcome to send a donation.
|
||||
|
||||
[Donate To Anna’s Archive.](https://annas-archive.org/donate?tier=1)
|
||||
|
||||
## License 📜
|
||||
|
||||
## License
|
||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
||||
Openlib is a free software licensed under GPL v3.0 It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY.[GNU General Public License](https://www.gnu.org/licenses/gpl.html) as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
Openlib is a free software licensed under GPL v3.0 It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY. [GNU General Public License](https://www.gnu.org/licenses/gpl.html) as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
|
||||
## Disclaimer
|
||||
## 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.
|
||||
|
@ -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"
|
||||
|
@ -1,10 +1,14 @@
|
||||
<p>
|
||||
<i>Openlib</i> is an open source app to download and read books from shadow library (<a href='https://annas-archive.org/' target='_blank' rel='nofollow noopener'>Anna’s Archive</a>). The App Has Built In Reader to Read Books.
|
||||
<b><i>Openlib</i></b> is an open source app to download and read books from shadow library (<a href='https://annas-archive.org/' target='_blank' rel='nofollow noopener'>Anna’s Archive</a>). The App Has Built In Reader to Read Books.
|
||||
</p>
|
||||
<p>
|
||||
As <i>Anna’s Archive</i> doesn't have an API, the app works by sending requests to <i>Anna’s Archive</i> and parses the response to objects. The app extracts the mirrors from the responses, downloads the book and stores it in the application's document directory.
|
||||
</p>
|
||||
<p>Main Features:</p>
|
||||
<p>
|
||||
<b>Note :</b>
|
||||
The app requires VPN to function properly . Without VPN the might show the captcha required page even after completing the captcha
|
||||
</p>
|
||||
<b><p>Main Features:</p></b>
|
||||
<ul>
|
||||
<li>Trending Books</li>
|
||||
<li>Download And Read Books With In-Built Viewer</li>
|
||||
|
Before Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 447 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 106 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 81 KiB |
Before Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 271 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 28 KiB |
@ -1 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
|
@ -1 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
|
44
ios/Podfile
Normal 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
|
@ -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,
|
||||
),
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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!');
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
// Flutter imports:
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TitleText extends StatelessWidget {
|
||||
|
@ -1,3 +1,4 @@
|
||||
// Flutter imports:
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void showSnackBar({required BuildContext context, required String message}) {
|
||||
|
@ -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;
|
||||
|
@ -1,3 +1,4 @@
|
||||
// Flutter imports:
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
extension ColorExtension on String {
|
||||
|
@ -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,
|
||||
|
@ -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});
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
),
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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(
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -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);
|
||||
|
@ -3,6 +3,7 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
open_file_linux
|
||||
url_launcher_linux
|
||||
)
|
||||
|
||||
|
@ -1 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||
|
@ -1 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||
|
@ -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
@ -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
@ -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
|
@ -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;
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1300"
|
||||
LastUpgradeVersion = "1510"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@ -4,4 +4,7 @@
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
@NSApplicationMain
|
||||
@main
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
|
@ -8,5 +8,7 @@
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -4,5 +4,7 @@
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
564
pubspec.lock
30
pubspec.yaml
@ -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
|
||||
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 447 KiB |
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 536 KiB After Width: | Height: | Size: 106 KiB |
Before Width: | Height: | Size: 378 KiB After Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 227 KiB After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 379 KiB After Width: | Height: | Size: 81 KiB |
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 271 KiB |
Before Width: | Height: | Size: 231 KiB After Width: | Height: | Size: 28 KiB |
@ -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() {
|
||||
|
@ -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
|
||||
|