57 Commits

Author SHA1 Message Date
b0579a5c15 updated version code 2023-11-21 21:44:49 -08:00
092f6029b6 Fixed unable to download due to change in annas archive 2023-11-20 23:47:40 -08:00
51283c3410 fixed no result found error 2023-11-03 06:05:03 -07:00
3a95d79c4a fixed bugs 2023-10-24 04:05:29 -07:00
9aeea38192 implemented mirror availabilty check 2023-10-24 03:42:05 -07:00
76c80be783 implemented cloudflare clearance 2023-10-23 23:59:38 -07:00
f71535438b added file type filter and fixed search not working 2023-10-16 06:55:32 -07:00
b536e1600a finished file type indicator 2023-10-12 23:41:24 -07:00
2ddc71d830 added file type indicator for book card 2023-10-12 06:00:59 -07:00
013cce078f removed ripple effect from bottom nav 2023-09-13 22:43:14 -07:00
7f3e255259 added systemNavigationBarColor to blend with bottom navbar 2023-09-12 00:21:50 -07:00
0825857231 Merge pull request #26 from basitali1509/feat/bottom-nav-bar-UI
FEEDBACK: Bottom Navigation Bar background color should be white in light mode
2023-09-12 08:44:34 +05:30
9255201049 Merge pull request #29 from basitali1509/fix/search-page
FIXED #28: Search process should not work when the search field is empty
2023-09-12 08:42:37 +05:30
ed8a6e01e9 Fixed Empty Search Field Bug 2023-09-12 02:11:19 +05:00
9def607737 bottom navbar background color update 2023-09-12 00:00:13 +05:00
6d0d417ce0 Merge branch 'main' of https://github.com/dstark5/Openlib into feat/bottom-nav-bar-UI 2023-09-11 23:35:52 +05:00
b127683003 Merge branch 'main' of https://github.com/basitali1509/Openlib into feat/bottom-nav-bar-UI 2023-09-11 23:32:27 +05:00
a6775b95a9 Merge remote-tracking branch 'original/main' 2023-09-11 23:30:27 +05:00
647687c0c9 Fixed download failing due to change in Anna's archive website 2023-09-11 23:29:23 +05:00
9dc7c67440 updated readme 2023-09-11 03:28:37 -07:00
ccd260d451 Fixed download failing due to change in Anna's archive website 2023-09-10 22:50:57 -07:00
6ac9f0c77a bottom navbar UI update 2023-09-09 22:31:06 +05:00
546805be20 updated packages 2023-09-03 07:46:52 -07:00
250f0f8b7b openwith support 2023-09-03 07:29:57 -07:00
f0d0178a2d added openwith feature 2023-09-02 05:38:27 -07:00
f4f86a1353 openwith feature added 2023-09-02 04:59:01 -07:00
93013fe6bb added open with feature 2023-09-02 03:53:03 -07:00
1de126104d Migated to new epub viewer for android and ios 2023-08-30 00:56:02 -07:00
e4c8ddaa88 fixed crashing 2023-08-29 06:36:55 -07:00
82798bb749 added resume reading in new epub reader 2023-08-29 06:24:20 -07:00
16fc051219 added new epub reader 2023-08-29 05:23:38 -07:00
da399604bf added new epub viewer 2023-08-27 23:28:33 -07:00
1b87e89470 finished settings and dark theme 2023-08-25 05:20:13 -07:00
0db7794455 added about page 2023-08-25 05:02:47 -07:00
6b31a63e86 adjusted bottombar transitions 2023-08-24 23:17:02 -07:00
27061a1922 added db to persist theme modes 2023-08-24 23:12:30 -07:00
162ea41196 theme change added 2023-08-23 01:18:12 -07:00
010dc1dfdb added dark mode 2023-08-23 01:02:21 -07:00
b2a1be7a7e init settings page design 2023-08-22 23:29:07 -07:00
1507656944 settings page 2023-08-22 20:13:03 -07:00
9cc3c3dde5 implemented resume reading for pdf and epub 2023-08-21 22:52:19 -07:00
e7b6a9d40f added resume reading for pdf viewer 2023-08-18 07:03:23 -07:00
60df4afac2 improved code readability 2023-08-14 23:13:10 -07:00
bcfb35ab14 Fixed a bug with Sqflite_ffi 2023-08-13 22:56:01 -07:00
4ac80f08d8 Merge pull request #6 from bipinkrish/main
View for non mobile platforms
2023-08-13 22:31:14 -07:00
a9fde1c561 view for non mobile platforms 2023-08-13 20:49:19 +05:30
110d089b8d Merge branch 'main' of https://github.com/dstark5/Openlib 2023-08-13 05:04:50 -07:00
4445fc4d84 added wake lock to permissions and commented unused lines 2023-08-13 05:04:41 -07:00
11d19b3001 Update README.md 2023-08-13 05:01:13 -07:00
052c5993b1 Update README.md 2023-08-13 04:34:07 -07:00
bc36a350ab Update README.md 2023-08-13 04:28:54 -07:00
9102b0be7b Add files via upload 2023-08-13 04:24:43 -07:00
365d29c19e Update README.md 2023-08-13 04:23:42 -07:00
52c742e8d0 Update README.md 2023-08-13 04:21:54 -07:00
d2c992bf80 Merge pull request #1 from IzzySoft/fastlane
initial fastlane structure
2023-08-12 06:27:20 -07:00
39a91b9992 initial fastlane structure 2023-08-12 14:32:43 +02:00
19df3b4f7b Update README.md 2023-08-11 04:59:59 -07:00
46 changed files with 1993 additions and 483 deletions

View File

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

View File

@ -1,9 +1,15 @@
<p align="center"><img src="assets/icons/appIcon.png" width="150"></p>
<p align="center"><img src="assets/icons/appIcon.png" width="150"></p>
<h1 align="center"><b>Openlib</b></h1>
#### An Open source app to download and read books from shadow library ([Annas Archive](https://annas-archive.org/)).
[![made-with-flutter](https://img.shields.io/badge/Made%20with-Flutter-4361ee.svg)](https://flutter.dev/) [![GPLv3 License](https://img.shields.io/badge/License-GPL%20v3-e63946.svg)](https://opensource.org/licenses/)
[![made-with-flutter](https://img.shields.io/badge/Made%20with-Flutter-4361ee.svg)](https://flutter.dev/) [![GPLv3 License](https://img.shields.io/badge/License-GPL%20v3-e63946.svg)](https://opensource.org/licenses/) ![version](https://img.shields.io/badge/version-1.0.2_beta-06d6a0)
[<img src="github_releases.png"
alt="Download from GitHub"
height="60">](https://github.com/dstark5/Openlib/releases) [<img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png"
alt="Get it on IzzyDroid"
height="60">](https://android.izzysoft.de/repo/apk/com.app.openlib)
## Note
@ -12,6 +18,10 @@
#### Publishing Openlib, Or Any Fork Of It In The Google Play Store Violates Their Terms And Conditions.
## Download
- Download and install APK from [GitHub Releases](https://github.com/dstark5/Openlib/releases).
- Download and install APK from [ IzzyOnDroid ](https://android.izzysoft.de/repo/apk/com.app.openlib).
## Screenshots
@ -27,24 +37,23 @@
## Description
##### Openlib Is An Open Source App To Download And Read Books From Shadow Library ([Annas Archive](https://annas-archive.org/)) . The App Has Built In Reader to Read Books.
##### As [Annas Archive](https://annas-archive.org/) Doesn't Have An API.The App Works By Sending Request To Annas Archive And Parses The Response To objects.The App Extracts The Mirrors From Response And Downloads The Book And Store It In The Application Document Directory.
##### As [Annas Archive](https://annas-archive.org/) Doesn't Have An API.The App Works By Sending Request To Annas Archive And Parses The Response To objects.The App Extracts The Mirrors From Response And Downloads The Book.
## Features
- Trending Books
- Trending Books
- Download And Read Books With In-Built Viewer
- Supports Epub And Pdf Formats
- Open Books In Your Favourite Ebook Reader
- Filter Books
- Sort Books
## Roadmap
- Additing More Book Format supports (cbz,cbr,azw3,etc...)
- Adding More Book Format supports (cbz,cbr,azw3,etc...)
- Adding Support For Background Downloads
- Adding Support For Multiple Downloads
## Installation and updates
- Download the APK from [GitHub Releases](https://github.com/dstark5/Openlib/releases) and install it.
## Building from Source
- If you don't have Flutter SDK installed, please visit official [Flutter](https://flutter.dev) site.
@ -69,7 +78,9 @@ flutter build
- The Build Will Be In './build/app/outputs/flutter-apk/app-release.apk'
## Contribution
Whether you have ideas, design changes or even major code changes, help is always welcome. The app gets better and better with each contribution, no matter how big or small! If you'd like to get involved just fork the app make a pull request.
Whether you have ideas, design changes or even major code changes, help is always welcome. The app gets better and better with each contribution, no matter how big or small!
If you'd like to get involved See [CONTRIBUTING.md](./CONTRIBUTING.md) for the guidelines.
## Issues

View File

@ -79,6 +79,8 @@ android {
// Signing with the debug keys for now, so `flutter run --release` works.
// replace with "debug" when running on debug version
signingConfig signingConfigs.release
minifyEnabled false
shrinkResources false
}
}
}
@ -89,4 +91,4 @@ flutter {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
}

View File

@ -1,11 +1,34 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEOS" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29"/>
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
</queries>
<application
android:label="Openlib"
android:name="${applicationName}"
android:requestLegacyExternalStorage="true"
android:usesCleartextTraffic="true"
android:networkSecurityConfig="@xml/network_security_config"
android:icon="@mipmap/launcher_icon">
<activity
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
@ -17,6 +40,7 @@
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"

1
assets/captcha.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,15 @@
<p>
<i>Openlib</i> is an open source app to download and read books from shadow library (<a href='https://annas-archive.org/' target='_blank' rel='nofollow noopener'>Annas Archive</a>). The App Has Built In Reader to Read Books.
</p>
<p>
As <i>Annas Archive</i> doesn't have an API, the app works by sending requests to <i>Annas Archive</i> and parses the response to objects. The app extracts the mirrors from the responses, downloads the book and stores it in the application's document directory.
</p>
<p>Main Features:</p>
<ul>
<li>Trending Books</li>
<li>Download And Read Books With In-Built Viewer</li>
<li>Supports Epub And Pdf Formats</li>
<li>Open Books With Your Favourite Ebooks Reader</li>
<li>Filter Books</li>
<li>Sort Books</li>
</ul>

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -0,0 +1 @@
download and read books from shadow library (Annas Archive)

BIN
github_releases.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1,29 +1,77 @@
import 'dart:io' show Platform;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_nav_bar/google_nav_bar.dart';
import 'package:sqflite/sqflite.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
import 'package:openlib/ui/extensions.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'
show selectedIndexProvider, dbProvider;
show
selectedIndexProvider,
themeModeProvider,
openPdfWithExternalAppProvider,
openEpubWithExternalAppProvider,
userAgentProvider,
cookieProvider,
dbProvider;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
Database db = await Sqlite.initDb();
runApp(ProviderScope(
overrides: [dbProvider.overrideWithValue(MyLibraryDb(dbInstance: db))],
child: const MyApp()));
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
sqfliteFfiInit();
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');
String browserUserAgent = await dataBase.getBrowserOptions('userAgent');
String browserCookie = await dataBase.getBrowserOptions('cookie');
if (Platform.isAndroid) {
//[SystemChrome] Also change colors in settings page Theme colors if any change
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
systemNavigationBarColor:
isDarkMode ? Colors.black : Colors.grey.shade200));
await moveFilesToAndroidInternalStorage();
}
runApp(
ProviderScope(
overrides: [
dbProvider.overrideWithValue(dataBase),
themeModeProvider.overrideWith(
(ref) => isDarkMode ? ThemeMode.dark : ThemeMode.light),
openPdfWithExternalAppProvider
.overrideWith((ref) => openPdfwithExternalapp),
openEpubWithExternalAppProvider
.overrideWith((ref) => openEpubwithExternalapp),
userAgentProvider.overrideWith((ref) => browserUserAgent),
cookieProvider.overrideWith((ref) => browserCookie),
],
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
class MyApp extends ConsumerWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
Widget build(BuildContext context, WidgetRef ref) {
return MaterialApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
@ -35,32 +83,9 @@ class MyApp extends StatelessWidget {
},
debugShowCheckedModeBanner: false,
title: 'Openlib',
theme: ThemeData(
primaryColor: Colors.white,
colorScheme: ColorScheme.light(
primary: Colors.white,
secondary: '#FB0101'.toColor(),
tertiary: Colors.black,
tertiaryContainer: '#F2F2F2'.toColor(),
),
textTheme: const TextTheme(
displayLarge: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 21,
),
displayMedium: TextStyle(
fontSize: 13,
fontWeight: FontWeight.bold,
color: Colors.black,
overflow: TextOverflow.ellipsis,
),
),
fontFamily: GoogleFonts.nunito().fontFamily,
useMaterial3: true,
textSelectionTheme: TextSelectionThemeData(
selectionColor: '#FB0101'.toColor(),
selectionHandleColor: '#FB0101'.toColor())),
theme: lightTheme,
darkTheme: darkTheme,
themeMode: ref.watch(themeModeProvider),
home: const HomePage(),
);
}
@ -77,11 +102,14 @@ class _HomePageState extends ConsumerState<HomePage> {
static const List<Widget> _widgetOptions = <Widget>[
TrendingPage(),
SearchPage(),
MyLibraryPage()
MyLibraryPage(),
SettingsPage()
];
@override
Widget build(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
final selectedIndex = ref.watch(selectedIndexProvider);
return Scaffold(
@ -93,51 +121,60 @@ class _HomePageState extends ConsumerState<HomePage> {
body: _widgetOptions.elementAt(selectedIndex),
bottomNavigationBar: SafeArea(
child: GNav(
rippleColor: Colors.redAccent,
backgroundColor: Colors.black,
backgroundColor: isDarkMode ? Colors.black : Colors.grey.shade200,
haptic: true,
tabBorderRadius: 50,
tabActiveBorder: Border.all(
color: Theme.of(context).colorScheme.secondary,
),
tabMargin: const EdgeInsets.fromLTRB(13, 6, 13, 2.5),
curve: Curves.easeInOut, // tab animation curves
duration: const Duration(milliseconds: 150),
curve: Curves.fastLinearToSlowEaseIn,
duration: const Duration(milliseconds: 25),
gap: 5,
color: const Color.fromARGB(255, 255, 255, 255),
activeColor: const Color.fromARGB(255, 255, 255, 255),
iconSize: 21, // tab button icon size
iconSize: 19, // tab button icon size
tabBackgroundColor: Theme.of(context).colorScheme.secondary,
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 6.5),
tabs: const [
padding: const EdgeInsets.symmetric(horizontal: 13, vertical: 6.5),
tabs: [
GButton(
icon: Icons.trending_up,
text: 'Trending',
iconColor: Colors.white,
textStyle: TextStyle(
iconColor: isDarkMode ? Colors.white : Colors.black,
textStyle: const TextStyle(
fontWeight: FontWeight.w900,
color: Colors.white,
fontSize: 13,
fontSize: 11,
),
),
GButton(
icon: Icons.search,
text: 'Search',
iconColor: Colors.white,
textStyle: TextStyle(
iconColor: isDarkMode ? Colors.white : Colors.black,
textStyle: const TextStyle(
fontWeight: FontWeight.w900,
color: Colors.white,
fontSize: 13,
fontSize: 11,
),
),
GButton(
icon: Icons.collections_bookmark,
text: 'My Library',
iconColor: Colors.white,
textStyle: TextStyle(
iconColor: isDarkMode ? Colors.white : Colors.black,
textStyle: const TextStyle(
fontWeight: FontWeight.w900,
color: Colors.white,
fontSize: 13,
fontSize: 11,
),
),
GButton(
icon: Icons.build,
text: 'Settings',
iconColor: isDarkMode ? Colors.white : Colors.black,
textStyle: const TextStyle(
fontWeight: FontWeight.w900,
color: Colors.white,
fontSize: 11,
),
),
],

View File

@ -1,4 +1,5 @@
import 'package:dio/dio.dart';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:html/parser.dart' show parse;
@ -48,23 +49,25 @@ class BookInfoData extends BookData {
}
class AnnasArchieve {
String baseUrl = "https://annas-archive.gs";
String baseUrl = "https://annas-archive.org";
final Dio dio = Dio(BaseOptions(headers: {
'User-Agent':
'Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/7.0.4 Mobile/16B91 Safari/605.1.15'
}));
final Dio dio = Dio();
Map<String, dynamic> defaultDioHeaders = {
"user-agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36",
};
String getMd5(String url) {
String md5 = url.toString().split('/').last;
return md5;
}
List<BookData> _parser(resData) {
List<BookData> _parser(resData, String fileType) {
var document =
parse(resData.toString().replaceAll(RegExp(r"<!--|-->"), ''));
var books = document.querySelectorAll(
'a[class="js-vim-focus custom-a flex items-center relative left-[-10px] w-[calc(100%+20px)] px-[10px] py-2 outline-offset-[-2px] outline-2 rounded-[3px] hover:bg-[#00000011] focus:outline "]');
'a[class="js-vim-focus custom-a flex items-center relative left-[-10px] w-[calc(100%+20px)] px-[10px] outline-offset-[-2px] outline-2 rounded-[3px] hover:bg-[#00000011] focus:outline "]');
List<BookData> bookList = [];
@ -73,21 +76,32 @@ class AnnasArchieve {
'title': element.querySelector('h3')?.text,
'thumbnail': element.querySelector('img')?.attributes['src'],
'link': element.attributes['href'],
'author': element.querySelector('div[class="truncate italic"]')?.text ??
'author': element
.querySelector(
'div[class="max-lg:line-clamp-[2] lg:truncate leading-[1.2] lg:leading-[1.35] max-lg:text-sm italic"]')
?.text ??
'unknown',
'publisher':
element.querySelector('div[class="truncate text-sm"]')?.text ??
"unknown",
'publisher': element
.querySelector(
'div[class="truncate leading-[1.2] lg:leading-[1.35] max-lg:text-xs"]')
?.text ??
"unknown",
'info': element
.querySelector('div[class="truncate text-xs text-gray-500"]')
.querySelector(
'div[class="line-clamp-[2] leading-[1.2] text-[10px] lg:text-xs text-gray-500"]')
?.text ??
''
};
if ((data['title'] != null && data['title'] != '') &&
(data['link'] != null && data['link'] != '') &&
(data['info'] != null &&
(data['info']!.contains('pdf') ||
data['info']!.contains('epub')))) {
((fileType == "") &&
(data['info']!.contains('pdf') ||
data['info']!.contains('epub') ||
data['info']!.contains('cbr') ||
data['info']!.contains('cbz')) ||
((fileType != "") && data['info']!.contains(fileType))))) {
String link = baseUrl + data['link']!;
String publisher = ((data['publisher']?.contains('0') == true &&
data['publisher']!.length < 2) ||
@ -115,46 +129,71 @@ class AnnasArchieve {
if (info.contains('pdf') == true) {
return 'pdf';
} else {
return 'epub';
if (info.contains('cbr')) return "cbr";
if (info.contains('cbz')) return "cbz";
return "epub";
}
}
Future<String?> _getMirrorLink(String url) async {
Future<String?> _getMirrorLink(
String url, String userAgent, String cookie) async {
try {
final response = await dio.get(url);
var document = parse(response.toString());
var pTag = document.querySelector('p[class="mb-4"]');
String? link = pTag?.querySelector('a')?.attributes['href'];
final response = await dio.get(url,
options: Options(extra: {
'withCredentials': true
}, headers: {
"Host": "annas-archive.org",
"Origin": "https://annas-archive.org",
"Upgrade-Insecure-Requests": "1",
"Sec-Fetch-Dest": "secure",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "same-site",
"Cookie": cookie,
"User-Agent": userAgent
}));
var document = parse(response.data.toString());
var pTag = document.querySelectorAll('p[class="mb-4"]');
String? link = pTag[1].querySelector('a')?.attributes['href'];
return link;
} catch (e) {
print(e);
// print('${url} ${e}');
if (e.toString().contains("403")) {
throw jsonEncode({"code": "403", "url": url});
}
return null;
}
}
Future<BookInfoData?> _bookInfoParser(resData, url) async {
Future<BookInfoData?> _bookInfoParser(resData, url, userAgent, cookie) async {
var document = parse(resData.toString());
var main = document.querySelector('main[class="main"]');
var ul = main?.querySelector('ul[class="mb-4"]');
var ul = main?.querySelectorAll('ul[class="mb-4"]');
List<String> mirrors = [];
if (ul != null) {
var a = ul.querySelectorAll('a');
var anchorTags = [];
if (ul.length == 2) {
anchorTags = ul[1].querySelectorAll('a');
} else {
anchorTags = ul[0].querySelectorAll('a');
}
for (var element in a) {
for (var element in anchorTags) {
if (element.attributes['href']!.startsWith('https://')) {
if (element.attributes['href'] != null) {
if (element.attributes['href'] != null &&
element.attributes['href'].startsWith('https://1lib.sk') !=
true) {
mirrors.add(element.attributes['href']!);
}
} else if (element.attributes['href'] != null &&
element.attributes['href']!.startsWith('/slow_download')) {
if (element.text.contains('Slow Partner Server #1') != true) {
String? url =
await _getMirrorLink('$baseUrl${element.attributes['href']!}');
if (url != null && url.isNotEmpty) {
mirrors.add(url);
}
String? url = await _getMirrorLink(
'$baseUrl${element.attributes['href']!}', userAgent, cookie);
if (url != null && url.isNotEmpty) {
mirrors.add(url);
}
}
}
@ -210,12 +249,16 @@ class AnnasArchieve {
Future<List<BookData>> searchBooks(
{required String searchQuery,
String content = "",
String sort = ""}) async {
String sort = "",
String fileType = ""}) async {
try {
final response = await dio.get(
'$baseUrl/search?lang=&content=$content&ext=&sort=$sort&q=$searchQuery',
);
return _parser(response.data);
final String encodedURL = content == ""
? '$baseUrl/search?index=&q=$searchQuery&ext=$fileType&sort=$sort'
: '$baseUrl/search?index=&q=$searchQuery&content=$content&ext=$fileType&sort=$sort';
final response = await dio.get(encodedURL,
options: Options(headers: defaultDioHeaders));
return _parser(response.data, fileType);
} on DioException catch (e) {
if (e.type == DioExceptionType.unknown) {
throw "socketException";
@ -224,10 +267,15 @@ class AnnasArchieve {
}
}
Future<BookInfoData> bookInfo({required String url}) async {
Future<BookInfoData> bookInfo(
{required String url,
required String userAgent,
required String cookie}) async {
try {
final response = await dio.get(url);
BookInfoData? data = await _bookInfoParser(response.data, url);
final response =
await dio.get(url, options: Options(headers: defaultDioHeaders));
BookInfoData? data =
await _bookInfoParser(response.data, url, userAgent, cookie);
if (data != null) {
return data;
} else {

View File

@ -1,15 +1,48 @@
import 'dart:io';
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: 1,
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)');
});
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;
}
}
@ -118,4 +151,76 @@ class MyLibraryDb {
});
return myBookList.reversed.toList();
}
Future<void> saveBookState(String fileName, String position) async {
await dbInstance.insert(
'bookposition',
{'fileName': fileName, 'position': position},
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
Future<void> deleteBookState(String fileName) async {
await dbInstance.delete(
'bookposition',
where: 'fileName = ?',
whereArgs: [fileName],
);
}
Future<String?> getBookState(String fileName) async {
List<Map<String, dynamic>> data = await dbInstance
.query('bookposition', where: 'fileName = ?', whereArgs: [fileName]);
List<dynamic> dataList = List.generate(data.length, (i) {
return {'fileName': data[i]['fileName'], 'position': data[i]['position']};
});
if (dataList.isNotEmpty) {
return dataList[0]['position'];
} else {
return null;
}
}
Future<void> savePreference(String name, bool value) async {
int boolInt = value ? 1 : 0;
await dbInstance.insert(
'preferences',
{'name': name, 'value': boolInt},
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
Future<bool> getPreference(String name) async {
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;
}
}
Future<void> setBrowserOptions(String name, String value) async {
await dbInstance.insert(
'browserOptions',
{'name': name, 'value': value},
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
Future<String> getBrowserOptions(String name) async {
List<Map<String, dynamic>> data = await dbInstance
.query('browserOptions', where: 'name = ?', whereArgs: [name]);
List<dynamic> dataList = List.generate(data.length, (i) {
return {'name': data[i]['name'], 'value': data[i]['value']};
});
if (dataList.isNotEmpty) {
return dataList[0]['value'];
} else {
return "";
}
}
}

View File

@ -1,9 +1,9 @@
import 'package:path_provider/path_provider.dart';
import 'package:dio/dio.dart';
import 'files.dart';
Future<String> _getFilePath(String fileName) async {
final path = await getApplicationDocumentsDirectory();
return '${path.path}/$fileName';
final path = await getAppDirectoryPath;
return '$path/$fileName';
}
List<String> _reorderMirrors(List<String> mirrors) {
@ -14,7 +14,8 @@ List<String> _reorderMirrors(List<String> mirrors) {
if (element.contains('ipfs') == true) {
ipfsMirrors.add(element);
} else {
if (element.startsWith('https://annas-archive.gs') != true) {
if (element.startsWith('https://annas-archive.org') != true &&
element.startsWith('https://1lib.sk') != true) {
httpsMirrors.add(element);
}
}
@ -22,45 +23,72 @@ List<String> _reorderMirrors(List<String> mirrors) {
return [...ipfsMirrors, ...httpsMirrors];
}
Future<String?> _getAliveMirror(List<String> mirrors, Dio dio) async {
for (var url in mirrors) {
try {
final response = await dio.head(url,
options: Options(receiveTimeout: const Duration(seconds: 5)));
if (response.statusCode == 200) {
return url;
}
} catch (e) {
// print("timeOut");
}
}
return null;
}
Future<void> downloadFile(
{required List<String> mirrors,
required String md5,
required String format,
required Function onProgress,
required Function cancelDownlaod,
required Function mirrorStatus,
required Function onDownlaodFailed}) async {
Dio dio = Dio();
String path = await _getFilePath('$md5.$format');
List<String> orderedMirrors = _reorderMirrors(mirrors);
String? workingMirror = await _getAliveMirror(orderedMirrors, dio);
// print(workingMirror);
// print(path);
// print(orderedMirrors);
// print(orderedMirrors[0]);
try {
CancelToken cancelToken = CancelToken();
if (workingMirror != null) {
try {
CancelToken cancelToken = CancelToken();
dio.download(
orderedMirrors[0],
path,
options: Options(headers: {
'Connection': 'Keep-Alive',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36'
}),
onReceiveProgress: (rcv, total) {
onProgress(rcv, total);
},
deleteOnError: true,
cancelToken: cancelToken,
).catchError((err) {
if (err.type != DioExceptionType.cancel) {
onDownlaodFailed();
}
throw err;
});
dio.download(
workingMirror,
path,
options: Options(headers: {
'Connection': 'Keep-Alive',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36'
}),
onReceiveProgress: (rcv, total) {
onProgress(rcv, total);
},
deleteOnError: true,
cancelToken: cancelToken,
).catchError((err) {
if (err.type != DioExceptionType.cancel) {
onDownlaodFailed();
}
throw err;
});
cancelDownlaod(cancelToken);
} catch (e) {
mirrorStatus(true);
cancelDownlaod(cancelToken);
} catch (e) {
onDownlaodFailed();
}
} else {
onDownlaodFailed();
}
}

View File

@ -4,8 +4,32 @@ import 'package:path_provider/path_provider.dart';
import 'package:openlib/state/state.dart' show dbProvider, myLibraryProvider;
Future<String> get getAppDirectoryPath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
if (Platform.isAndroid) {
final directory = await getExternalStorageDirectory();
return directory!.path;
} else {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
}
Future<void> moveFilesToAndroidInternalStorage() async {
try {
final directory = await getApplicationDocumentsDirectory();
final directoryExternal = await getExternalStorageDirectory();
List<FileSystemEntity> files = Directory(directory.path).listSync();
for (var element in files) {
if ((element.path.contains('pdf')) || element.path.contains('epub')) {
String fileName = element.path.split('/').last;
File file = File(element.path);
file.copySync('${directoryExternal!.path}/$fileName');
file.deleteSync();
}
}
} catch (e) {
// ignore: avoid_print
print(e);
}
}
Future<bool> isFileExists(String filePath) async {
@ -35,9 +59,11 @@ Future<void> deleteFileWithDbData(
String appDirPath = await getAppDirectoryPath;
await deleteFile('$appDirPath/$fileName');
await ref.read(dbProvider).delete(md5);
await ref.read(dbProvider).deleteBookState(fileName);
// ignore: unused_result
ref.refresh(myLibraryProvider);
} catch (e) {
print(e);
// print(e);
rethrow;
}
}

View File

@ -1,4 +1,5 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:openlib/services/database.dart';
import 'package:dio/dio.dart';
@ -29,8 +30,12 @@ Map<String, String> sortValues = {
'Smallest': 'smallest',
};
List<String> fileType = ["All", "PDF", "Epub", "Cbr", "Cbz"];
final selectedIndexProvider = StateProvider<int>((ref) => 0);
final themeModeProvider = StateProvider<ThemeMode>((ref) => ThemeMode.light);
final selectedTypeState = StateProvider<String>((ref) => "All");
final getTypeValue = Provider.autoDispose<String>((ref) {
@ -43,6 +48,15 @@ final getSortValue = Provider.autoDispose<String>((ref) {
return sortValues[ref.read(selectedSortState)] ?? '';
});
final selectedFileTypeState = StateProvider<String>((ref) => "All");
final getFileTypeValue = Provider.autoDispose<String>((ref) {
if (ref.read(selectedFileTypeState) == "All") {
return '';
}
return ref.read(selectedFileTypeState).toLowerCase();
});
//searchQueryProvider
final searchQueryProvider = StateProvider<String>((ref) => "");
@ -61,21 +75,30 @@ final searchProvider = FutureProvider.family
List<BookData> data = await annasArchieve.searchBooks(
searchQuery: searchQuery,
content: ref.watch(getTypeValue),
sort: ref.watch(getSortValue));
sort: ref.watch(getSortValue),
fileType: ref.watch(getFileTypeValue));
return data;
});
final cookieProvider = StateProvider<String>((ref) => "");
final userAgentProvider = StateProvider<String>((ref) => "");
//Provider for Book Info
final bookInfoProvider =
FutureProvider.family<BookInfoData, String>((ref, url) async {
AnnasArchieve annasArchieve = AnnasArchieve();
BookInfoData data = await annasArchieve.bookInfo(url: url);
BookInfoData data = await annasArchieve.bookInfo(
url: url,
userAgent: ref.read(userAgentProvider),
cookie: ref.read(cookieProvider));
return data;
});
final downloadProgressProvider =
StateProvider.autoDispose<double>((ref) => 0.0);
final mirrorStatusProvider = StateProvider.autoDispose<bool>((ref) => false);
final totalFileSizeInBytes = StateProvider.autoDispose<int>((ref) => 0);
final downloadedFileSizeInBytes = StateProvider.autoDispose<int>((ref) => 0);
@ -125,6 +148,25 @@ final deleteFileFromMyLib =
final pdfCurrentPage = StateProvider.autoDispose<int>((ref) => 0);
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);
}
Future<void> saveEpubState(
String fileName, String? position, WidgetRef ref) async {
String pos = position ?? '';
await ref.watch(dbProvider).saveBookState(fileName, pos);
}
final getBookPosition =
FutureProvider.family.autoDispose<String?, String>((ref, fileName) async {
return await ref.read(dbProvider).getBookState(fileName);
});
final openPdfWithExternalAppProvider = StateProvider<bool>((ref) => false);
final openEpubWithExternalAppProvider = StateProvider<bool>((ref) => false);
final filePathProvider =
FutureProvider.family<String, String>((ref, fileName) async {
String path = await getFilePath(fileName);

131
lib/ui/about_page.dart Normal file
View File

@ -0,0 +1,131 @@
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:openlib/ui/components/snack_bar_widget.dart';
import 'package:openlib/ui/components/page_title_widget.dart';
class AboutPage extends StatelessWidget {
const AboutPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.primary,
title: const Text("Openlib"),
titleTextStyle: Theme.of(context).textTheme.displayLarge,
),
body: const SingleChildScrollView(
physics: BouncingScrollPhysics(),
scrollDirection: Axis.vertical,
child: Padding(
padding: EdgeInsets.only(left: 5, right: 5, top: 10),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TitleText("About"),
Padding(
padding:
EdgeInsets.only(left: 7, right: 7, top: 13, bottom: 10),
child: Text(
"An Open source app to download and read books from shadow library (Anna`s Archive)",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
),
Padding(
padding: EdgeInsets.only(left: 7, right: 7, top: 10),
child: Text(
"Version",
style: TextStyle(fontSize: 19, fontWeight: FontWeight.bold),
),
),
Padding(
padding: EdgeInsets.only(left: 7, right: 7, top: 5),
child: Text(
"1.0.3",
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Colors.grey),
),
),
Padding(
padding: EdgeInsets.only(left: 7, right: 7, top: 15),
child: Text(
"Github",
style: TextStyle(fontSize: 19, fontWeight: FontWeight.bold),
),
),
_UrlText(
text: 'Open Github Page',
url: 'https://github.com/dstark5/Openlib',
),
_UrlText(
text: 'Contribute To Openlib',
url:
'https://github.com/dstark5/Openlib/blob/main/CONTRIBUTING.md'),
_UrlText(
text: 'Report An Issue',
url: 'https://github.com/dstark5/Openlib/issues'),
Padding(
padding: EdgeInsets.only(left: 7, right: 7, top: 15),
child: Text(
"Licence",
style: TextStyle(fontSize: 19, fontWeight: FontWeight.bold),
),
),
_UrlText(
text: "GPL v3.0 license",
url: 'https://www.gnu.org/licenses/gpl-3.0.en.html'),
],
),
),
),
);
}
}
class _UrlText extends StatelessWidget {
const _UrlText({Key? key, required this.text, required this.url})
: super(key: key);
final String url;
final String text;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 7, right: 7, top: 5),
child: InkWell(
onTap: () async {
final Uri uri = Uri.parse(url);
if (!await launchUrl(uri, mode: LaunchMode.externalApplication)) {
// ignore: use_build_context_synchronously
showSnackBar(context: context, message: 'Could not launch $uri');
}
},
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
text,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Colors.blueAccent,
),
),
const SizedBox(
width: 2,
),
const Icon(
Icons.launch,
size: 17,
color: Colors.blueAccent,
)
],
),
),
);
}
}

View File

@ -1,5 +1,7 @@
import 'package:dio/dio.dart';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:openlib/services/database.dart';
@ -15,12 +17,15 @@ import 'package:openlib/state/state.dart'
getTotalFileSize,
getDownloadedFileSize,
cancelCurrentDownload,
mirrorStatusProvider,
dbProvider,
checkIdExists,
myLibraryProvider;
import 'package:openlib/ui/components/book_info_widget.dart';
import 'package:openlib/ui/components/file_buttons_widget.dart';
import 'package:openlib/ui/components/snack_bar_widget.dart';
import 'package:flutter_svg/svg.dart';
import 'package:openlib/ui/webview_page.dart';
class BookInfoPage extends ConsumerWidget {
const BookInfoPage({Key? key, required this.url}) : super(key: key);
@ -37,19 +42,97 @@ class BookInfoPage extends ConsumerWidget {
titleTextStyle: Theme.of(context).textTheme.displayLarge,
),
body: bookInfo.when(
skipLoadingOnRefresh: false,
data: (data) {
return BookInfoWidget(
data: data, child: ActionButtonWidget(data: data));
},
error: (err, _) {
return CustomErrorWidget(
error: err,
stackTrace: _,
onRefresh: () {
// ignore: unused_result
ref.refresh(bookInfoProvider(url));
},
);
if (err.toString().contains("403")) {
var errJson = jsonDecode(err.toString());
if (SchedulerBinding.instance.schedulerPhase ==
SchedulerPhase.persistentCallbacks) {
SchedulerBinding.instance.addPostFrameCallback((_) {
Future.delayed(
const Duration(seconds: 3),
() => Navigator.pushReplacement(context,
MaterialPageRoute(builder: (BuildContext context) {
return Webview(url: errJson["url"]);
})));
});
}
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(
width: 210,
child: SvgPicture.asset(
'assets/captcha.svg',
width: 210,
),
),
const SizedBox(
height: 30,
),
Text(
"Captcha required",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Theme.of(context).textTheme.headlineMedium?.color,
overflow: TextOverflow.ellipsis,
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"you will be redirected to solve captcha",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.bold,
color: Theme.of(context).textTheme.headlineSmall?.color,
overflow: TextOverflow.ellipsis,
),
),
),
Padding(
padding: const EdgeInsets.fromLTRB(30, 15, 30, 10),
child: Container(
width: double.infinity,
decoration: BoxDecoration(
color: const Color.fromARGB(255, 255, 186, 186),
borderRadius: BorderRadius.circular(5)),
child: const Padding(
padding: EdgeInsets.all(10.0),
child: Text(
"If you have solved the captcha then you will be automatically redirected to the results page . In case you seeing this page even after completing try using a VPN .",
textAlign: TextAlign.start,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
),
),
)
],
);
} else {
return CustomErrorWidget(
error: err,
stackTrace: _,
onRefresh: () {
// ignore: unused_result
ref.refresh(bookInfoProvider(url));
},
);
}
},
loading: () {
return Center(
@ -161,6 +244,9 @@ Future<void> downloadFileWidget(
cancelDownlaod: (CancelToken downloadToken) {
ref.read(cancelCurrentDownload.notifier).state = downloadToken;
},
mirrorStatus: (val) {
ref.read(mirrorStatusProvider.notifier).state = val;
},
onDownlaodFailed: () {
Navigator.of(context).pop();
showSnackBar(
@ -185,6 +271,7 @@ class _ShowDialog extends ConsumerWidget {
final downloadProgress = ref.watch(downloadProgressProvider);
final fileSize = ref.watch(getTotalFileSize);
final downloadedFileSize = ref.watch(getDownloadedFileSize);
final mirrorStatus = ref.watch(mirrorStatusProvider);
if (downloadProgress == 1.0) {
Navigator.of(context).pop();
@ -197,10 +284,10 @@ class _ShowDialog extends ConsumerWidget {
padding: const EdgeInsets.all(15.0),
child: Container(
width: double.infinity,
height: 255,
height: 285,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
color: Colors.white,
color: Theme.of(context).colorScheme.tertiaryContainer,
),
padding: const EdgeInsets.fromLTRB(20, 20, 20, 20),
child: SingleChildScrollView(
@ -208,14 +295,14 @@ class _ShowDialog extends ConsumerWidget {
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Padding(
padding: EdgeInsets.all(8),
Padding(
padding: const EdgeInsets.all(8),
child: Text(
"Downloading Book",
style: TextStyle(
fontSize: 19,
fontWeight: FontWeight.bold,
color: Color.fromARGB(255, 54, 54, 54),
color: Theme.of(context).colorScheme.tertiary,
decoration: TextDecoration.none),
),
),
@ -223,16 +310,58 @@ class _ShowDialog extends ConsumerWidget {
padding: const EdgeInsets.all(8),
child: Text(
title,
style: const TextStyle(
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.bold,
color: Colors.black54,
color: Theme.of(context)
.colorScheme
.tertiary
.withAlpha(170),
decoration: TextDecoration.none),
overflow: TextOverflow.ellipsis,
maxLines: 2,
textAlign: TextAlign.start,
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
mirrorStatus
? const Icon(
Icons.check_circle,
size: 15,
color: Colors.green,
)
: SizedBox(
width: 9,
height: 9,
child: CircularProgressIndicator(
color:
Theme.of(context).colorScheme.secondary,
strokeWidth: 2.5,
),
),
const SizedBox(
width: 3,
),
Text(
"Checking mirror availability",
style: TextStyle(
fontSize: 11.5,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.colorScheme
.tertiary
.withAlpha(140),
decoration: TextDecoration.none),
overflow: TextOverflow.ellipsis,
maxLines: 2,
textAlign: TextAlign.start,
),
]),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
@ -259,7 +388,10 @@ class _ShowDialog extends ConsumerWidget {
borderRadius: const BorderRadius.all(Radius.circular(50)),
child: LinearProgressIndicator(
color: Theme.of(context).colorScheme.secondary,
backgroundColor: Colors.black26,
backgroundColor: Theme.of(context)
.colorScheme
.tertiary
.withAlpha(50),
value: downloadProgress,
minHeight: 4,
),

View File

@ -2,6 +2,18 @@ import 'package:flutter/material.dart';
import 'package:openlib/ui/extensions.dart';
import 'package:cached_network_image/cached_network_image.dart';
String? getFileType(String? info) {
if (info != null && info.isNotEmpty) {
info = info.toLowerCase();
if (info.contains('pdf')) return "PDF";
if (info.contains('epub')) return "Epub";
if (info.contains('cbr')) return "Cbr";
if (info.contains('cbz')) return "Cbz";
return null;
}
return null;
}
class BookInfoCard extends StatelessWidget {
const BookInfoCard(
{Key? key,
@ -9,6 +21,7 @@ class BookInfoCard extends StatelessWidget {
required this.author,
required this.publisher,
required this.thumbnail,
required this.info,
required this.link,
required this.onClick})
: super(key: key);
@ -17,11 +30,14 @@ class BookInfoCard extends StatelessWidget {
final String author;
final String publisher;
final String? thumbnail;
final String? info;
final String link;
final VoidCallback onClick;
@override
Widget build(BuildContext context) {
String? fileType = getFileType(info);
return InkWell(
onTap: onClick,
child: Container(
@ -81,10 +97,10 @@ class BookInfoCard extends StatelessWidget {
children: [
Text(
title,
style: const TextStyle(
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Colors.black,
color: Theme.of(context).colorScheme.tertiary,
),
overflow: TextOverflow.ellipsis,
maxLines: 2,
@ -94,20 +110,56 @@ class BookInfoCard extends StatelessWidget {
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: "#4D4D4D".toColor(),
color:
Theme.of(context).textTheme.headlineMedium?.color,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
Text(
author,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.bold,
color: "#7B7B7B".toColor(),
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
if (fileType != null)
Container(
decoration: BoxDecoration(
color: "#a5a5a5".toColor(),
borderRadius: BorderRadius.circular(2.5),
),
child: Padding(
padding: const EdgeInsets.fromLTRB(3, 2, 3, 2),
child: Text(
fileType,
style: const TextStyle(
fontSize: 8.5,
fontWeight: FontWeight.bold,
color: Colors.white,
letterSpacing: 0.5,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
),
if (fileType != null)
const SizedBox(
width: 3,
),
Expanded(
child: Text(
author,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.textTheme
.headlineSmall
?.color,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
],
),
],
),

View File

@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:openlib/ui/extensions.dart';
import 'package:cached_network_image/cached_network_image.dart';
class BookInfoWidget extends StatelessWidget {
@ -11,6 +10,9 @@ class BookInfoWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
String description = data.description.toString().length < 3
? "No Description available"
: data.description.toString();
return SingleChildScrollView(
physics: const BouncingScrollPhysics(),
scrollDirection: Axis.vertical,
@ -65,28 +67,32 @@ class BookInfoWidget extends StatelessWidget {
text: data.title,
fontSize: 19,
topPadding: 15,
color: Colors.black,
color: Theme.of(context).colorScheme.tertiary,
maxLines: 7,
),
_TopPaddedText(
text: data.publisher ?? "unknown",
fontSize: 15,
topPadding: 7,
color: "#595E60".toColor(),
color: Theme.of(context).textTheme.headlineMedium!.color!,
maxLines: 4,
),
_TopPaddedText(
text: data.author ?? "unknown",
fontSize: 13,
topPadding: 7,
color: "#7F7F7F".toColor(),
color: Theme.of(context).textTheme.headlineSmall!.color!,
maxLines: 3,
),
_TopPaddedText(
text: data.info ?? "",
fontSize: 11,
topPadding: 9,
color: "#A9A8A2".toColor(),
color: Theme.of(context)
.textTheme
.headlineSmall!
.color!
.withAlpha(155),
maxLines: 4,
),
// child slot of page
@ -108,11 +114,12 @@ class BookInfoWidget extends StatelessWidget {
Padding(
padding: const EdgeInsets.only(top: 7, bottom: 10),
child: Text(
data.description ?? "",
description,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.bold,
color: "#6B6B6B".toColor(),
color:
Theme.of(context).colorScheme.tertiary.withAlpha(150),
letterSpacing: 1.5,
),
),

View File

@ -26,7 +26,7 @@ class ShowDeleteDialog extends ConsumerWidget {
height: 219,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
color: Colors.white,
color: Theme.of(context).colorScheme.tertiaryContainer,
),
padding: const EdgeInsets.fromLTRB(20, 50, 20, 20),
child: SingleChildScrollView(
@ -34,25 +34,28 @@ class ShowDeleteDialog extends ConsumerWidget {
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Padding(
padding: EdgeInsets.all(8),
Padding(
padding: const EdgeInsets.all(8),
child: Text(
"Delete Book",
style: TextStyle(
fontSize: 19,
fontWeight: FontWeight.bold,
color: Color.fromARGB(255, 54, 54, 54),
color: Theme.of(context).colorScheme.tertiary,
decoration: TextDecoration.none),
),
),
const Padding(
padding: EdgeInsets.all(8),
Padding(
padding: const EdgeInsets.all(8),
child: Text(
"This is permanent and cannot be undone",
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.bold,
color: Colors.black54,
color: Theme.of(context)
.colorScheme
.tertiary
.withAlpha(170),
decoration: TextDecoration.none),
overflow: TextOverflow.ellipsis,
maxLines: 2,
@ -88,14 +91,14 @@ class ShowDeleteDialog extends ConsumerWidget {
onDelete();
},
child: const Padding(
padding: EdgeInsets.all(5.0),
child: Padding(
padding: const EdgeInsets.all(5.0),
child: Text(
'Delete',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.bold,
color: Colors.black,
color: Theme.of(context).colorScheme.tertiary,
),
),
),

View File

@ -1,10 +1,14 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:open_file/open_file.dart';
import 'package:openlib/services/files.dart' show getFilePath;
import 'package:openlib/ui/components/snack_bar_widget.dart';
import 'package:openlib/ui/components/delete_dialog_widget.dart';
import 'package:openlib/ui/epub_viewer.dart';
import 'package:openlib/ui/pdf_viewer.dart';
import 'package:openlib/ui/epub_viewer.dart' show launchEpubViewer;
import 'package:openlib/ui/pdf_viewer.dart' show launchPdfViewer;
class FileOpenAndDeleteButtons extends StatelessWidget {
class FileOpenAndDeleteButtons extends ConsumerWidget {
final String id;
final String format;
final Function onDelete;
@ -17,7 +21,7 @@ class FileOpenAndDeleteButtons extends StatelessWidget {
: super(key: key);
@override
Widget build(BuildContext context) {
Widget build(BuildContext context, WidgetRef ref) {
return Padding(
padding: const EdgeInsets.only(top: 21, bottom: 21),
child: Row(
@ -27,30 +31,21 @@ class FileOpenAndDeleteButtons extends StatelessWidget {
TextButton(
style: TextButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.secondary,
textStyle: const TextStyle(
textStyle: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w900,
color: Colors.white,
color: Theme.of(context).colorScheme.primary,
)),
onPressed: () => {
if (format == 'pdf')
{
Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return PdfView(
fileName: '$id.$format',
);
}))
}
else
{
Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return EpubViewerWidget(
fileName: '$id.$format',
);
}))
}
onPressed: () async {
if (format == 'pdf') {
await launchPdfViewer(
fileName: '$id.$format', context: context, ref: ref);
} else if (format == 'epub') {
await launchEpubViewer(
fileName: '$id.$format', context: context, ref: ref);
} else {
await openCbrAndCbz(fileName: '$id.$format', context: context);
}
},
child: const Padding(
padding: EdgeInsets.fromLTRB(17, 8, 17, 8),
@ -82,14 +77,14 @@ class FileOpenAndDeleteButtons extends StatelessWidget {
);
});
},
child: const Padding(
padding: EdgeInsets.all(5.3),
child: Padding(
padding: const EdgeInsets.all(5.3),
child: Text(
'Delete',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.bold,
color: Colors.black,
color: Theme.of(context).colorScheme.tertiary,
),
),
),
@ -99,3 +94,16 @@ class FileOpenAndDeleteButtons extends StatelessWidget {
);
}
}
Future<void> openCbrAndCbz(
{required String fileName, required BuildContext context}) async {
try {
String path = await getFilePath(fileName);
await OpenFile.open(path);
} catch (e) {
// ignore: avoid_print
// print(e);
// ignore: use_build_context_synchronously
showSnackBar(context: context, message: 'Unable to open pdf!');
}
}

View File

@ -1,9 +1,67 @@
import 'dart:io';
import 'dart:convert';
import 'package:flutter/material.dart';
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';
import 'package:openlib/state/state.dart' show filePathProvider;
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;
Future<void> launchEpubViewer(
{required String fileName,
required BuildContext context,
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);
} 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
});
} catch (e) {
// ignore: use_build_context_synchronously
showSnackBar(context: context, message: 'Unable to open pdf!');
}
}
} else {
Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) {
return EpubViewerWidget(
fileName: fileName,
);
}));
}
}
class EpubViewerWidget extends ConsumerStatefulWidget {
const EpubViewerWidget({super.key, required this.fileName});
@ -19,7 +77,7 @@ class _EpubViewState extends ConsumerState<EpubViewerWidget> {
Widget build(BuildContext context) {
final filePath = ref.watch(filePathProvider(widget.fileName));
return filePath.when(data: (data) {
return EpubViewer(filePath: data);
return EpubViewer(filePath: data, fileName: widget.fileName);
}, error: (error, stack) {
return Scaffold(
appBar: AppBar(
@ -37,30 +95,34 @@ class _EpubViewState extends ConsumerState<EpubViewerWidget> {
titleTextStyle: Theme.of(context).textTheme.displayLarge,
),
body: Center(
child: SizedBox(
width: 25,
height: 25,
child: CircularProgressIndicator(
color: Theme.of(context).colorScheme.secondary,
child: SizedBox(
width: 25,
height: 25,
child: CircularProgressIndicator(
color: Theme.of(context).colorScheme.secondary,
),
),
)),
),
);
});
}
}
class EpubViewer extends StatefulWidget {
const EpubViewer({Key? key, required this.filePath}) : super(key: key);
class EpubViewer extends ConsumerStatefulWidget {
const EpubViewer({Key? key, required this.filePath, required this.fileName})
: super(key: key);
final String filePath;
final String fileName;
@override
// ignore: library_private_types_in_public_api
_EpubViewerState createState() => _EpubViewerState();
}
class _EpubViewerState extends State<EpubViewer> {
class _EpubViewerState extends ConsumerState<EpubViewer> {
late EpubController _epubReaderController;
String? epubConf;
@override
void initState() {
@ -70,6 +132,14 @@ class _EpubViewerState extends State<EpubViewer> {
super.initState();
}
@override
void deactivate() {
if (Platform.isAndroid || Platform.isIOS) {
saveEpubState(widget.fileName, epubConf, ref);
}
super.deactivate();
}
@override
void dispose() {
_epubReaderController.dispose();
@ -78,6 +148,7 @@ class _EpubViewerState extends State<EpubViewer> {
@override
Widget build(BuildContext context) {
final position = ref.watch(getBookPosition(widget.fileName));
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.primary,
@ -87,14 +158,48 @@ class _EpubViewerState extends State<EpubViewer> {
endDrawer: Drawer(
child: EpubViewTableOfContents(controller: _epubReaderController),
),
body: EpubView(
onDocumentLoaded: (doc) {},
onChapterChanged: (value) {},
builders: EpubViewBuilders<DefaultBuilderOptions>(
options: const DefaultBuilderOptions(),
chapterDividerBuilder: (_) => const Divider(),
),
controller: _epubReaderController,
body: position.when(
data: (data) {
return EpubView(
onDocumentLoaded: (doc) {
Future.delayed(const Duration(milliseconds: 20), () {
String pos = data ?? "";
_epubReaderController.gotoEpubCfi(pos);
});
},
onChapterChanged: (value) {
epubConf = _epubReaderController.generateEpubCfi();
},
builders: EpubViewBuilders<DefaultBuilderOptions>(
options: const DefaultBuilderOptions(),
chapterDividerBuilder: (_) => const Divider(),
),
controller: _epubReaderController,
);
},
error: (err, _) {
return EpubView(
onChapterChanged: (value) {
epubConf = _epubReaderController.generateEpubCfi();
},
builders: EpubViewBuilders<DefaultBuilderOptions>(
options: const DefaultBuilderOptions(),
chapterDividerBuilder: (_) => const Divider(),
),
controller: _epubReaderController,
);
},
loading: () {
return Center(
child: SizedBox(
width: 25,
height: 25,
child: CircularProgressIndicator(
color: Theme.of(context).colorScheme.secondary,
),
),
);
},
),
);
}

View File

@ -21,6 +21,7 @@ class MyLibraryPage extends ConsumerWidget {
return Padding(
padding: const EdgeInsets.only(left: 5, right: 5, top: 10),
child: CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: <Widget>[
const SliverToBoxAdapter(
child: TitleText("My Library"),
@ -34,6 +35,7 @@ class MyLibraryPage extends ConsumerWidget {
author: i.author ?? "",
publisher: i.publisher ?? "",
thumbnail: i.thumbnail,
info: i.info,
link: i.link,
onClick: () {
Navigator.push(context, MaterialPageRoute(

View File

@ -2,7 +2,35 @@ import 'package:flutter/material.dart';
import 'package:flutter_pdfview/flutter_pdfview.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:openlib/state/state.dart'
show filePathProvider, pdfCurrentPage, totalPdfPage;
show
filePathProvider,
pdfCurrentPage,
totalPdfPage,
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,
required BuildContext context,
required WidgetRef ref}) async {
bool openWithExternalApp = ref.watch(openPdfWithExternalAppProvider);
if (openWithExternalApp) {
String path = await getFilePath(fileName);
await OpenFile.open(path);
} else {
Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) {
return PdfView(
fileName: fileName,
);
}));
}
}
class PdfView extends ConsumerStatefulWidget {
const PdfView({super.key, required this.fileName});
@ -18,7 +46,7 @@ class _PdfViewState extends ConsumerState<PdfView> {
Widget build(BuildContext context) {
final filePath = ref.watch(filePathProvider(widget.fileName));
return filePath.when(data: (data) {
return PdfViewer(filePath: data);
return PdfViewer(filePath: data, fileName: widget.fileName);
}, error: (error, stack) {
return Scaffold(
appBar: AppBar(
@ -49,9 +77,11 @@ class _PdfViewState extends ConsumerState<PdfView> {
}
class PdfViewer extends ConsumerStatefulWidget {
const PdfViewer({Key? key, required this.filePath}) : super(key: key);
const PdfViewer({Key? key, required this.filePath, required this.fileName})
: super(key: key);
final String filePath;
final String fileName;
@override
ConsumerState<ConsumerStatefulWidget> createState() => _PdfViewerState();
@ -60,8 +90,44 @@ class PdfViewer extends ConsumerStatefulWidget {
class _PdfViewerState extends ConsumerState<PdfViewer> {
late PDFViewController controller;
@override
void initState() {
super.initState();
}
@override
void deactivate() {
if (Platform.isAndroid || Platform.isIOS) {
savePdfState(widget.fileName, ref);
}
super.deactivate();
}
@override
void dispose() {
super.dispose();
}
Future<void> _openPdfWithDefaultViewer(String fileName) async {
debugPrint("Opening : $fileName");
final fileUrl = Uri.parse(fileName);
if (await canLaunchUrl(fileUrl)) {
await launchUrl(fileUrl);
} else {
// ignore: use_build_context_synchronously
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Could not open the PDF',
textAlign: TextAlign.center,
)),
);
}
}
@override
Widget build(BuildContext context) {
bool isMobile = Platform.isAndroid || Platform.isIOS;
final currentPage = ref.watch(pdfCurrentPage);
final totalPages = ref.watch(totalPdfPage);
return Scaffold(
@ -69,51 +135,109 @@ class _PdfViewerState extends ConsumerState<PdfViewer> {
backgroundColor: Theme.of(context).colorScheme.primary,
title: const Text("Openlib"),
titleTextStyle: Theme.of(context).textTheme.displayLarge,
actions: [
IconButton(
onPressed: () {
if (currentPage != 0) {
ref.read(pdfCurrentPage.notifier).state = currentPage - 1;
controller.setPage(currentPage - 1);
} else {
ref.read(pdfCurrentPage.notifier).state = totalPages;
controller.setPage(totalPages - 1);
}
},
icon: const Icon(
Icons.arrow_left,
size: 25,
)),
Text('${(currentPage + 1).toString()} / ${totalPages.toString()}'),
IconButton(
onPressed: () {
if (currentPage + 1 < totalPages) {
ref.read(pdfCurrentPage.notifier).state = currentPage + 1;
controller.setPage(currentPage + 1);
} else {
ref.read(pdfCurrentPage.notifier).state = 0;
controller.setPage(0);
}
},
icon: const Icon(
Icons.arrow_right,
size: 25,
)),
],
),
body: PDFView(
swipeHorizontal: true,
fitEachPage: true,
fitPolicy: FitPolicy.BOTH,
filePath: widget.filePath,
onViewCreated: (controller) {
this.controller = controller;
},
onPageChanged: (page, total) {
ref.read(pdfCurrentPage.notifier).state = page ?? 0;
ref.read(totalPdfPage.notifier).state = total ?? 0;
},
actions: isMobile
? [
IconButton(
onPressed: () {
if (currentPage != 0) {
ref.read(pdfCurrentPage.notifier).state =
currentPage - 1;
controller.setPage(currentPage - 1);
} else {
ref.read(pdfCurrentPage.notifier).state = totalPages;
controller.setPage(totalPages - 1);
}
},
icon: const Icon(
Icons.arrow_left,
size: 25,
)),
Text(
'${(currentPage + 1).toString()} / ${totalPages.toString()}'),
IconButton(
onPressed: () {
if (currentPage + 1 < totalPages) {
ref.read(pdfCurrentPage.notifier).state =
currentPage + 1;
controller.setPage(currentPage + 1);
} else {
ref.read(pdfCurrentPage.notifier).state = 0;
controller.setPage(0);
}
},
icon: const Icon(
Icons.arrow_right,
size: 25,
)),
]
: [],
),
body: isMobile
? ref.watch(getBookPosition(widget.fileName)).when(
data: (data) {
return PDFView(
swipeHorizontal: true,
fitEachPage: true,
fitPolicy: FitPolicy.BOTH,
filePath: widget.filePath,
onViewCreated: (controller) {
this.controller = controller;
},
defaultPage: int.parse(data ?? '0'),
onPageChanged: (page, total) {
ref.read(pdfCurrentPage.notifier).state = page ?? 0;
ref.read(totalPdfPage.notifier).state = total ?? 0;
},
);
},
error: (error, stackTrace) {
return PDFView(
swipeHorizontal: true,
fitEachPage: true,
fitPolicy: FitPolicy.BOTH,
filePath: widget.filePath,
onViewCreated: (controller) {
this.controller = controller;
},
onPageChanged: (page, total) {
ref.read(pdfCurrentPage.notifier).state = page ?? 0;
ref.read(totalPdfPage.notifier).state = total ?? 0;
},
);
},
loading: () {
return Center(
child: SizedBox(
width: 25,
height: 25,
child: CircularProgressIndicator(
color: Theme.of(context).colorScheme.secondary,
),
));
},
)
: Center(
child: TextButton(
style: TextButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.secondary,
textStyle: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w900,
color: Colors.white,
)),
onPressed: () async {
await _openPdfWithDefaultViewer("file://${widget.filePath}");
// ignore: use_build_context_synchronously
Navigator.pop(context);
},
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Text(
"Open with System's PDF Viewer",
),
),
),
),
);
}
}

View File

@ -46,6 +46,7 @@ class ResultPage extends ConsumerWidget {
author: i.author ?? "unknown",
publisher: i.publisher ?? "unknown",
thumbnail: i.thumbnail!,
info: i.info,
link: i.link,
onClick: () {
Navigator.push(context, MaterialPageRoute(

View File

@ -7,24 +7,33 @@ import 'package:openlib/state/state.dart'
searchQueryProvider,
selectedTypeState,
selectedSortState,
selectedFileTypeState,
typeValues,
fileType,
sortValues;
import 'components/snack_bar_widget.dart';
class SearchPage extends ConsumerWidget {
const SearchPage({Key? key}) : super(key: key);
void onSubmit(BuildContext context, WidgetRef ref) {
Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) {
return ResultPage(
searchQuery: ref.read(searchQueryProvider),
);
}));
if (ref.read(searchQueryProvider).isNotEmpty) {
Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return ResultPage(
searchQuery: ref.read(searchQueryProvider),
);
}));
} else {
showSnackBar(context: context, message: 'Search field is empty');
}
}
@override
Widget build(BuildContext context, WidgetRef ref) {
final dropdownTypeValue = ref.watch(selectedTypeState);
final dropdownSortValue = ref.watch(selectedSortState);
final dropDownFileTypeValue = ref.watch(selectedFileTypeState);
return SingleChildScrollView(
scrollDirection: Axis.vertical,
@ -42,12 +51,14 @@ class SearchPage extends ConsumerWidget {
cursorColor: Theme.of(context).colorScheme.secondary,
decoration: InputDecoration(
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.black45, width: 2),
borderSide: BorderSide(color: Colors.grey, width: 2),
borderRadius: BorderRadius.all(Radius.circular(50)),
),
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.black54, width: 2),
borderRadius: BorderRadius.all(Radius.circular(50)),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.tertiary,
width: 2),
borderRadius: const BorderRadius.all(Radius.circular(50)),
),
suffixIcon: IconButton(
padding: const EdgeInsets.only(right: 5),
@ -88,12 +99,14 @@ class SearchPage extends ConsumerWidget {
color: Theme.of(context).colorScheme.secondary,
),
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.black54, width: 2),
borderSide: BorderSide(color: Colors.grey, width: 2),
borderRadius: BorderRadius.all(Radius.circular(50)),
),
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.black54, width: 2),
borderRadius: BorderRadius.all(Radius.circular(50)),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.tertiary,
width: 2),
borderRadius: const BorderRadius.all(Radius.circular(50)),
),
),
icon: const Icon(Icons.arrow_drop_down),
@ -105,7 +118,8 @@ class SearchPage extends ConsumerWidget {
value: value,
child: Text(
value,
style: const TextStyle(fontSize: 12),
style: const TextStyle(
fontSize: 12, fontWeight: FontWeight.bold),
),
);
}).toList(),
@ -128,12 +142,14 @@ class SearchPage extends ConsumerWidget {
color: Theme.of(context).colorScheme.secondary,
),
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.black45, width: 2),
borderSide: BorderSide(color: Colors.grey, width: 2),
borderRadius: BorderRadius.all(Radius.circular(50)),
),
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.black54, width: 2),
borderRadius: BorderRadius.all(Radius.circular(50)),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.tertiary,
width: 2),
borderRadius: const BorderRadius.all(Radius.circular(50)),
),
),
value: dropdownSortValue,
@ -144,7 +160,8 @@ class SearchPage extends ConsumerWidget {
value: value,
child: Text(
value,
style: const TextStyle(fontSize: 12),
style: const TextStyle(
fontSize: 12, fontWeight: FontWeight.bold),
),
);
}).toList(),
@ -153,7 +170,48 @@ class SearchPage extends ConsumerWidget {
},
),
),
)
),
Padding(
padding: const EdgeInsets.only(left: 7, right: 7, top: 19),
child: SizedBox(
width: 165,
child: DropdownButtonFormField(
decoration: InputDecoration(
labelText: 'File type',
labelStyle: TextStyle(
fontSize: 11,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.secondary,
),
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey, width: 2),
borderRadius: BorderRadius.all(Radius.circular(50)),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.tertiary,
width: 2),
borderRadius: const BorderRadius.all(Radius.circular(50)),
),
),
value: dropDownFileTypeValue,
items: fileType.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(
value,
style: const TextStyle(
fontSize: 12, fontWeight: FontWeight.bold),
),
);
}).toList(),
onChanged: (String? val) {
ref.read(selectedFileTypeState.notifier).state =
val ?? 'All';
},
),
),
),
],
),
),

163
lib/ui/settings_page.dart Normal file
View File

@ -0,0 +1,163 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:openlib/ui/components/page_title_widget.dart';
import 'package:openlib/ui/about_page.dart';
import 'package:openlib/state/state.dart'
show
themeModeProvider,
openPdfWithExternalAppProvider,
openEpubWithExternalAppProvider,
dbProvider;
class SettingsPage extends ConsumerWidget {
const SettingsPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
return Padding(
padding: const EdgeInsets.only(left: 5, right: 5, top: 10),
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const TitleText("Settings"),
_PaddedContainer(
children: [
Text(
"Dark Mode",
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.tertiary,
),
),
Switch(
// This bool value toggles the switch.
value: ref.watch(themeModeProvider) == ThemeMode.dark,
activeColor: Colors.red,
onChanged: (bool value) {
ref.read(themeModeProvider.notifier).state =
value == true ? ThemeMode.dark : ThemeMode.light;
ref.read(dbProvider).savePreference('darkMode', value);
if (Platform.isAndroid) {
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
systemNavigationBarColor:
value ? Colors.black : Colors.grey.shade200));
}
},
)
],
),
_PaddedContainer(
children: [
Text(
"Open PDF with External Reader",
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.tertiary,
),
),
Switch(
// This bool value toggles the switch.
value: ref.watch(openPdfWithExternalAppProvider),
activeColor: Colors.red,
onChanged: (bool value) {
ref.read(openPdfWithExternalAppProvider.notifier).state =
value;
ref
.read(dbProvider)
.savePreference('openPdfwithExternalApp', value);
},
)
],
),
_PaddedContainer(
children: [
Text(
"Open Epub with External Reader",
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.tertiary,
),
),
Switch(
// This bool value toggles the switch.
value: ref.watch(
openEpubWithExternalAppProvider,
),
activeColor: Colors.red,
onChanged: (bool value) {
ref.read(openEpubWithExternalAppProvider.notifier).state =
value;
ref
.read(dbProvider)
.savePreference('openEpubwithExternalApp', value);
},
)
],
),
_PaddedContainer(
onClick: () {
Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return const AboutPage();
}));
},
children: [
Text(
"About",
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.tertiary,
),
),
],
)
],
),
),
);
}
}
class _PaddedContainer extends StatelessWidget {
const _PaddedContainer({Key? key, this.onClick, required this.children})
: super(key: key);
final VoidCallback? onClick;
final List<Widget> children;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 5, right: 5, top: 10),
child: InkWell(
onTap: onClick,
child: Container(
height: 61,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
color: Theme.of(context).colorScheme.tertiaryContainer,
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: children,
),
),
),
),
);
}
}

72
lib/ui/themes.dart Normal file
View File

@ -0,0 +1,72 @@
import 'package:flutter/material.dart';
import 'package:openlib/ui/extensions.dart';
import 'package:google_fonts/google_fonts.dart';
ThemeData lightTheme = ThemeData(
primaryColor: Colors.white,
colorScheme: ColorScheme.light(
primary: Colors.white,
secondary: '#FB0101'.toColor(),
tertiary: Colors.black,
tertiaryContainer: '#F2F2F2'.toColor(),
),
textTheme: TextTheme(
displayLarge: const TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 21,
),
displayMedium: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.bold,
color: Colors.black,
overflow: TextOverflow.ellipsis,
),
headlineMedium: TextStyle(
color: "#595E60".toColor(),
),
headlineSmall: TextStyle(
color: "#7F7F7F".toColor(),
)),
fontFamily: GoogleFonts.nunito().fontFamily,
useMaterial3: true,
textSelectionTheme: TextSelectionThemeData(
selectionColor: '#FB0101'.toColor(),
selectionHandleColor: '#FB0101'.toColor(),
),
);
ThemeData darkTheme = ThemeData(
primaryColor: Colors.black,
colorScheme: ColorScheme.dark(
primary: Colors.black,
secondary: '#FB0101'.toColor(),
tertiary: Colors.white,
tertiaryContainer: '#2B2B2B'.toColor(),
),
textTheme: TextTheme(
displayLarge: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 21,
),
displayMedium: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.bold,
color: Colors.white,
overflow: TextOverflow.ellipsis,
),
headlineMedium: TextStyle(
color: "#F5F5F5".toColor(),
),
headlineSmall: TextStyle(
color: "#E8E2E2".toColor(),
),
),
fontFamily: GoogleFonts.nunito().fontFamily,
useMaterial3: true,
textSelectionTheme: TextSelectionThemeData(
selectionColor: '#FB0101'.toColor(),
selectionHandleColor: '#FB0101'.toColor(),
),
);

View File

@ -17,127 +17,133 @@ class TrendingPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final trendingBooks = ref.watch(getTrendingBooks);
return trendingBooks.when(data: (data) {
return Padding(
padding: const EdgeInsets.only(left: 5, right: 5, top: 10),
child: CustomScrollView(
slivers: [
const SliverToBoxAdapter(
child: TitleText("Trending"),
),
SliverPadding(
padding: const EdgeInsets.all(5),
sliver: SliverGrid(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 10.0,
crossAxisSpacing: 13.0,
mainAxisExtent: 205,
return trendingBooks.when(
skipLoadingOnRefresh: false,
data: (data) {
return Padding(
padding: const EdgeInsets.only(left: 5, right: 5, top: 10),
child: CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: [
const SliverToBoxAdapter(
child: TitleText("Trending"),
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return InkWell(
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return ResultPage(
searchQuery: data[index].title!,
);
}));
},
child: Container(
width: double.infinity,
height: double.infinity,
color: const Color.fromARGB(255, 255, 255, 255),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CachedNetworkImage(
height: imageHeight,
width: imageWidth,
imageUrl: data[index].thumbnail!,
imageBuilder: (context, imageProvider) =>
Container(
decoration: BoxDecoration(
boxShadow: const [
BoxShadow(
color: Colors.grey,
spreadRadius: 0.1,
blurRadius: 1)
],
borderRadius: const BorderRadius.all(
Radius.circular(5)),
image: DecorationImage(
image: imageProvider,
fit: BoxFit.fill,
),
),
),
placeholder: (context, url) => Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
color: "#E3E8E9".toColor(),
),
height: imageHeight,
width: imageWidth,
),
errorWidget: (context, url, error) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
color: Colors.grey,
),
SliverPadding(
padding: const EdgeInsets.all(5),
sliver: SliverGrid(
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 10.0,
crossAxisSpacing: 13.0,
mainAxisExtent: 205,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return InkWell(
onTap: () {
Navigator.push(context, MaterialPageRoute(
builder: (BuildContext context) {
return ResultPage(
searchQuery: data[index].title!,
);
}));
},
child: SizedBox(
width: double.infinity,
height: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CachedNetworkImage(
height: imageHeight,
width: imageWidth,
child: const Center(
child: Icon(Icons.image_rounded),
imageUrl: data[index].thumbnail!,
imageBuilder: (context, imageProvider) =>
Container(
decoration: BoxDecoration(
boxShadow: const [
BoxShadow(
color: Colors.grey,
spreadRadius: 0.1,
blurRadius: 1)
],
borderRadius: const BorderRadius.all(
Radius.circular(5)),
image: DecorationImage(
image: imageProvider,
fit: BoxFit.fill,
),
),
),
);
},
),
Padding(
padding: const EdgeInsets.only(top: 4),
child: SizedBox(
width: imageWidth,
child: Text(
data[index].title!,
style: Theme.of(context)
.textTheme
.displayMedium,
maxLines: 2,
placeholder: (context, url) => Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
color: "#E3E8E9".toColor(),
),
height: imageHeight,
width: imageWidth,
),
errorWidget: (context, url, error) {
return Container(
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(5),
color: Colors.grey,
),
height: imageHeight,
width: imageWidth,
child: const Center(
child: Icon(Icons.image_rounded),
),
);
},
),
),
),
]),
),
);
},
childCount: data.length,
Padding(
padding: const EdgeInsets.only(top: 4),
child: SizedBox(
width: imageWidth,
child: Text(
data[index].title!,
style: Theme.of(context)
.textTheme
.displayMedium,
maxLines: 2,
),
),
),
]),
),
);
},
childCount: data.length,
),
),
),
),
],
),
],
),
);
}, error: (error, _) {
return CustomErrorWidget(
error: error,
stackTrace: _,
onRefresh: () {
// ignore: unused_result
ref.refresh(getTrendingBooks);
);
},
);
}, loading: () {
return Center(
child: SizedBox(
width: 25,
height: 25,
child: CircularProgressIndicator(
color: Theme.of(context).colorScheme.secondary,
),
));
});
error: (error, _) {
return CustomErrorWidget(
error: error,
stackTrace: _,
onRefresh: () {
// ignore: unused_result
ref.refresh(getTrendingBooks);
},
);
},
loading: () {
return Center(
child: SizedBox(
width: 25,
height: 25,
child: CircularProgressIndicator(
color: Theme.of(context).colorScheme.secondary,
),
));
});
}
}

75
lib/ui/webview_page.dart Normal file
View File

@ -0,0 +1,75 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:webview_cookie_manager/webview_cookie_manager.dart'
as cookiejar;
import 'package:openlib/state/state.dart'
show cookieProvider, userAgentProvider, dbProvider, bookInfoProvider;
class Webview extends ConsumerStatefulWidget {
const Webview({Key? key, required this.url}) : super(key: key);
final String url;
@override
// ignore: library_private_types_in_public_api
_WebviewState createState() => _WebviewState();
}
class _WebviewState extends ConsumerState<Webview> {
WebViewController controller = WebViewController();
final cookieManager = cookiejar.WebviewCookieManager();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: const Text("Solve Captcha"),
),
body: SafeArea(
child: WebViewWidget(
controller: controller
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(const Color(0x00000000))
..loadRequest(Uri.parse(widget.url))
..getUserAgent().then((value) {
ref.read(userAgentProvider.notifier).state = value!;
ref.read(dbProvider).setBrowserOptions('userAgent', value);
})
..setNavigationDelegate(NavigationDelegate(
onPageStarted: (url) async {
var urlStatusCode = await controller.runJavaScriptReturningResult(
"var xhr = new XMLHttpRequest();xhr.open('GET', window.location.href, false);xhr.send(null);xhr.status;");
if (urlStatusCode.toString().contains('200')) {
final cookies = await cookieManager
.getCookies("https://annas-archive.org");
List<String> cookie = [];
for (var element in cookies) {
if (element.name == 'cf_clearance' ||
element.name == 'cf_chl_2') {
cookie.add(element.toString().split(';')[0]);
}
}
String cfClearance = cookie.join('; ');
ref.read(cookieProvider.notifier).state = cfClearance;
await ref
.read(dbProvider)
.setBrowserOptions('cookie', cfClearance);
ref.invalidate(bookInfoProvider);
// ignore: use_build_context_synchronously
Navigator.pop(context);
}
},
)),
),
),
);
}
}

View File

@ -6,6 +6,10 @@
#include "generated_plugin_registrant.h"
#include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
}

View File

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

View File

@ -7,8 +7,10 @@ import Foundation
import path_provider_foundation
import sqflite
import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
}

View File

@ -5,10 +5,10 @@ packages:
dependency: transitive
description:
name: archive
sha256: "0c8368c9b3f0abbc193b9d6133649a614204b528982bebc7026372d61677ce3a"
sha256: "49b1fad315e57ab0bbc15bcbb874e83116a1d78f77ebd500a4af6c9407d6b28e"
url: "https://pub.dev"
source: hosted
version: "3.3.7"
version: "3.3.8"
args:
dependency: transitive
description:
@ -93,10 +93,10 @@ packages:
dependency: transitive
description:
name: collection
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
url: "https://pub.dev"
source: hosted
version: "1.17.1"
version: "1.17.2"
convert:
dependency: transitive
description:
@ -125,10 +125,10 @@ packages:
dependency: "direct main"
description:
name: cupertino_icons
sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
url: "https://pub.dev"
source: hosted
version: "1.0.5"
version: "1.0.6"
dev:
dependency: "direct main"
description:
@ -141,10 +141,10 @@ packages:
dependency: "direct main"
description:
name: dio
sha256: "3866d67f93523161b643187af65f5ac08bc991a5bcdaf41a2d587fe4ccb49993"
sha256: ce75a1b40947fea0a0e16ce73337122a86762e38b982e1ccb909daa3b9bc4197
url: "https://pub.dev"
source: hosted
version: "5.3.0"
version: "5.3.2"
epub_view:
dependency: "direct main"
description:
@ -181,10 +181,10 @@ packages:
dependency: transitive
description:
name: ffi
sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
version: "2.1.0"
file:
dependency: transitive
description:
@ -234,10 +234,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4"
sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04
url: "https://pub.dev"
source: hosted
version: "2.0.2"
version: "2.0.3"
flutter_pdfview:
dependency: "direct main"
description:
@ -250,10 +250,10 @@ packages:
dependency: "direct main"
description:
name: flutter_riverpod
sha256: b83ac5827baadefd331ea1d85110f34645827ea234ccabf53a655f41901a9bf4
sha256: b04d4e9435a563673746ccb328d22018c6c9496bb547e11dd56c1b0cc9829fe5
url: "https://pub.dev"
source: hosted
version: "2.3.6"
version: "2.3.10"
flutter_svg:
dependency: "direct main"
description:
@ -267,6 +267,11 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
gestures:
dependency: transitive
description:
@ -359,18 +364,18 @@ packages:
dependency: transitive
description:
name: matcher
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
url: "https://pub.dev"
source: hosted
version: "0.12.15"
version: "0.12.16"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
version: "0.5.0"
meta:
dependency: transitive
description:
@ -387,6 +392,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.2"
open_file:
dependency: "direct main"
description:
name: open_file
sha256: a5a32d44acb7c899987d0999e1e3cbb0a0f1adebbf41ac813ec6d2d8faa0af20
url: "https://pub.dev"
source: hosted
version: "3.3.2"
path:
dependency: transitive
description:
@ -407,50 +420,90 @@ packages:
dependency: "direct main"
description:
name: path_provider
sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2"
sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa
url: "https://pub.dev"
source: hosted
version: "2.0.15"
version: "2.1.1"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: "2cec049d282c7f13c594b4a73976b0b4f2d7a1838a6dd5aaf7bd9719196bee86"
sha256: "6b8b19bd80da4f11ce91b2d1fb931f3006911477cec227cce23d3253d80df3f1"
url: "https://pub.dev"
source: hosted
version: "2.0.27"
version: "2.2.0"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "916731ccbdce44d545414dd9961f26ba5fbaa74bcbb55237d8e65a623a8c7297"
sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d"
url: "https://pub.dev"
source: hosted
version: "2.2.4"
version: "2.3.1"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: ffbb8cc9ed2c9ec0e4b7a541e56fd79b138e8f47d2fb86815f15358a349b3b57
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.1.11"
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec"
sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c"
url: "https://pub.dev"
source: hosted
version: "2.0.6"
version: "2.1.1"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96"
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
url: "https://pub.dev"
source: hosted
version: "2.1.7"
version: "2.2.1"
permission_handler:
dependency: "direct main"
description:
name: permission_handler
sha256: "63e5216aae014a72fe9579ccd027323395ce7a98271d9defa9d57320d001af81"
url: "https://pub.dev"
source: hosted
version: "10.4.3"
permission_handler_android:
dependency: transitive
description:
name: permission_handler_android
sha256: d74e77a5ecd38649905db0a7d05ef16bed42ff263b9efb73ed794317c5764ec3
url: "https://pub.dev"
source: hosted
version: "10.3.4"
permission_handler_apple:
dependency: transitive
description:
name: permission_handler_apple
sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5"
url: "https://pub.dev"
source: hosted
version: "9.1.4"
permission_handler_platform_interface:
dependency: transitive
description:
name: permission_handler_platform_interface
sha256: "7c6b1500385dd1d2ca61bb89e2488ca178e274a69144d26bbd65e33eae7c02a9"
url: "https://pub.dev"
source: hosted
version: "3.11.3"
permission_handler_windows:
dependency: transitive
description:
name: permission_handler_windows
sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098
url: "https://pub.dev"
source: hosted
version: "0.1.3"
petitparser:
dependency: transitive
description:
@ -463,18 +516,18 @@ packages:
dependency: transitive
description:
name: platform
sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76"
sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102
url: "https://pub.dev"
source: hosted
version: "3.1.0"
version: "3.1.2"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc"
sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d
url: "https://pub.dev"
source: hosted
version: "2.1.4"
version: "2.1.6"
pointycastle:
dependency: transitive
description:
@ -495,10 +548,10 @@ packages:
dependency: transitive
description:
name: riverpod
sha256: "80e48bebc83010d5e67a11c9514af6b44bbac1ec77b4333c8ea65dbc79e2d8ef"
sha256: "6c0a2c30c04206ac05494bcccd8148b76866e1a9248a5a8c84ca7b16fbcb3f6a"
url: "https://pub.dev"
source: hosted
version: "2.3.6"
version: "2.3.10"
rxdart:
dependency: transitive
description:
@ -524,10 +577,18 @@ packages:
dependency: transitive
description:
name: source_span
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
version: "1.10.0"
sprintf:
dependency: transitive
description:
name: sprintf
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
sqflite:
dependency: "direct main"
description:
@ -544,6 +605,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.5.0"
sqflite_common_ffi:
dependency: "direct main"
description:
name: sqflite_common_ffi
sha256: "0d5cc1be2eb18400ac6701c31211d44164393aa75886093002ecdd947be04f93"
url: "https://pub.dev"
source: hosted
version: "2.3.0+2"
sqlite3:
dependency: transitive
description:
name: sqlite3
sha256: db65233e6b99e99b2548932f55a987961bc06d82a31a0665451fa0b4fff4c3fb
url: "https://pub.dev"
source: hosted
version: "2.1.0"
stack_trace:
dependency: transitive
description:
@ -556,10 +633,10 @@ packages:
dependency: transitive
description:
name: state_notifier
sha256: "8fe42610f179b843b12371e40db58c9444f8757f8b69d181c97e50787caed289"
sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb
url: "https://pub.dev"
source: hosted
version: "0.7.2+1"
version: "1.0.0"
stream_channel:
dependency: transitive
description:
@ -596,10 +673,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
url: "https://pub.dev"
source: hosted
version: "0.5.1"
version: "0.6.0"
typed_data:
dependency: transitive
description:
@ -616,14 +693,78 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.0"
url_launcher:
dependency: "direct main"
description:
name: url_launcher
sha256: "47e208a6711459d813ba18af120d9663c20bdf6985d6ad39fe165d2538378d27"
url: "https://pub.dev"
source: hosted
version: "6.1.14"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: b04af59516ab45762b2ca6da40fa830d72d0f6045cd97744450b73493fa76330
url: "https://pub.dev"
source: hosted
version: "6.1.0"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: "7c65021d5dee51813d652357bc65b8dd4a6177082a9966bc8ba6ee477baa795f"
url: "https://pub.dev"
source: hosted
version: "6.1.5"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
sha256: b651aad005e0cb06a01dbd84b428a301916dc75f0e7ea6165f80057fee2d8e8e
url: "https://pub.dev"
source: hosted
version: "3.0.6"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
sha256: b55486791f666e62e0e8ff825e58a023fd6b1f71c49926483f1128d3bbd8fe88
url: "https://pub.dev"
source: hosted
version: "3.0.7"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
sha256: "95465b39f83bfe95fcb9d174829d6476216f2d548b79c38ab2506e0458787618"
url: "https://pub.dev"
source: hosted
version: "2.1.5"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
sha256: "2942294a500b4fa0b918685aff406773ba0a4cd34b7f42198742a94083020ce5"
url: "https://pub.dev"
source: hosted
version: "2.0.20"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
sha256: "95fef3129dc7cfaba2bc3d5ba2e16063bb561fc6d78e63eee16162bc70029069"
url: "https://pub.dev"
source: hosted
version: "3.0.8"
uuid:
dependency: transitive
description:
name: uuid
sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313"
sha256: e03928880bdbcbf496fb415573f5ab7b1ea99b9b04f669c01104d085893c3134
url: "https://pub.dev"
source: hosted
version: "3.0.7"
version: "4.0.0"
vector_graphics:
dependency: transitive
description:
@ -656,22 +797,78 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
vocsy_epub_viewer:
dependency: "direct main"
description:
name: vocsy_epub_viewer
sha256: "30aab4a5be22e8c98f22fed3345f6989caaca861178c38cab2a8b8cd66ad53de"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
web:
dependency: transitive
description:
name: web
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
url: "https://pub.dev"
source: hosted
version: "0.1.4-beta"
webview_cookie_manager:
dependency: "direct main"
description:
name: webview_cookie_manager
sha256: "425a9feac5cd2cb62a71da3dda5ac2eaf9ece5481ee8d79f3868dc5ba8223ad3"
url: "https://pub.dev"
source: hosted
version: "2.0.6"
webview_flutter:
dependency: "direct main"
description:
name: webview_flutter
sha256: c1ab9b81090705c6069197d9fdc1625e587b52b8d70cdde2339d177ad0dbb98e
url: "https://pub.dev"
source: hosted
version: "4.4.1"
webview_flutter_android:
dependency: transitive
description:
name: webview_flutter_android
sha256: b0cd33dd7d3dd8e5f664e11a19e17ba12c352647269921a3b568406b001f1dff
url: "https://pub.dev"
source: hosted
version: "3.12.0"
webview_flutter_platform_interface:
dependency: transitive
description:
name: webview_flutter_platform_interface
sha256: "6d9213c65f1060116757a7c473247c60f3f7f332cac33dc417c9e362a9a13e4f"
url: "https://pub.dev"
source: hosted
version: "2.6.0"
webview_flutter_wkwebview:
dependency: transitive
description:
name: webview_flutter_wkwebview
sha256: "30b9af6bdd457b44c08748b9190d23208b5165357cc2eb57914fee1366c42974"
url: "https://pub.dev"
source: hosted
version: "3.9.1"
win32:
dependency: transitive
description:
name: win32
sha256: dfdf0136e0aa7a1b474ea133e67cb0154a0acd2599c4f3ada3b49d38d38793ee
sha256: "9e82a402b7f3d518fb9c02d0e9ae45952df31b9bf34d77baf19da2de03fc2aaa"
url: "https://pub.dev"
source: hosted
version: "5.0.5"
version: "5.0.7"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: e0b1147eec179d3911f1f19b59206448f78195ca1d20514134e10641b7d7fbff
sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
version: "1.0.3"
xml:
dependency: transitive
description:
@ -689,5 +886,5 @@ packages:
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.0.5 <4.0.0"
flutter: ">=3.7.0-0"
dart: ">=3.1.0 <4.0.0"
flutter: ">=3.13.0"

View File

@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1
version: 1.0.3+5
environment:
sdk: '>=3.0.5 <4.0.0'
@ -36,6 +36,7 @@ dependencies:
epub_view: ^3.2.0
flutter_pdfview: ^1.2.7
vocsy_epub_viewer: ^2.0.0
# syncfusion_flutter_pdfviewer: ^22.2.5
# pdfx: ^2.4.0
@ -44,11 +45,18 @@ dependencies:
sqflite: ^2.3.0
path_provider: ^2.0.15
permission_handler: ^10.4.3
open_file: ^3.3.2
webview_flutter: ^4.4.1
webview_cookie_manager: ^2.0.6
flutter_svg: ^2.0.7
google_fonts: ^5.1.0
cached_network_image: ^3.2.3
sqflite_common_ffi: ^2.3.0+2
url_launcher: ^6.1.12
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
@ -115,4 +123,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

View File

@ -6,6 +6,12 @@
#include "generated_plugin_registrant.h"
#include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
}

View File

@ -3,6 +3,8 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
permission_handler_windows
url_launcher_windows
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST

View File

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