mirror of
https://github.com/dstark5/Openlib.git
synced 2025-09-19 13:33:35 +08:00
Compare commits
38 Commits
material-y
...
v1.0.3-bet
Author | SHA1 | Date | |
---|---|---|---|
51283c3410 | |||
3a95d79c4a | |||
9aeea38192 | |||
76c80be783 | |||
f71535438b | |||
b536e1600a | |||
2ddc71d830 | |||
013cce078f | |||
7f3e255259 | |||
0825857231 | |||
9255201049 | |||
ed8a6e01e9 | |||
9def607737 | |||
6d0d417ce0 | |||
b127683003 | |||
a6775b95a9 | |||
647687c0c9 | |||
9dc7c67440 | |||
ccd260d451 | |||
6ac9f0c77a | |||
546805be20 | |||
250f0f8b7b | |||
f0d0178a2d | |||
f4f86a1353 | |||
93013fe6bb | |||
1de126104d | |||
e4c8ddaa88 | |||
82798bb749 | |||
16fc051219 | |||
da399604bf | |||
1b87e89470 | |||
0db7794455 | |||
6b31a63e86 | |||
27061a1922 | |||
162ea41196 | |||
010dc1dfdb | |||
b2a1be7a7e | |||
1507656944 |
@ -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.
|
24
README.md
24
README.md
@ -1,15 +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 ([Anna’s Archive](https://annas-archive.org/)).
|
||||
|
||||
[](https://flutter.dev/) [](https://opensource.org/licenses/) 
|
||||
[](https://flutter.dev/) [](https://opensource.org/licenses/) 
|
||||
|
||||
[<img src="github_releases.png"
|
||||
alt="Download from GitHub"
|
||||
height="80">](https://github.com/dstark5/Openlib/releases) [<img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png"
|
||||
height="60">](https://github.com/dstark5/Openlib/releases) [<img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png"
|
||||
alt="Get it on IzzyDroid"
|
||||
height="80">](https://android.izzysoft.de/repo/apk/com.app.openlib)
|
||||
height="60">](https://android.izzysoft.de/repo/apk/com.app.openlib)
|
||||
|
||||
|
||||
## Note
|
||||
@ -18,6 +18,10 @@
|
||||
|
||||
#### Publishing Openlib, Or Any Fork Of It In The Google Play Store Violates Their Terms And Conditions.
|
||||
|
||||
## Download
|
||||
- Download and install APK from [GitHub Releases](https://github.com/dstark5/Openlib/releases).
|
||||
- Download and install APK from [ IzzyOnDroid ](https://android.izzysoft.de/repo/apk/com.app.openlib).
|
||||
|
||||
|
||||
## Screenshots
|
||||
|
||||
@ -33,11 +37,13 @@
|
||||
## Description
|
||||
##### Openlib Is An Open Source App To Download And Read Books From Shadow Library ([Anna’s Archive](https://annas-archive.org/)) . The App Has Built In Reader to Read Books.
|
||||
|
||||
##### As [Anna’s Archive](https://annas-archive.org/) Doesn't Have An API.The App Works By Sending Request To Anna’s Archive And Parses The Response To objects.The App Extracts The Mirrors From Response And Downloads The Book And Store It In The Application Document Directory.
|
||||
##### As [Anna’s Archive](https://annas-archive.org/) Doesn't Have An API.The App Works By Sending Request To Anna’s Archive And Parses The Response To objects.The App Extracts The Mirrors From Response And Downloads The Book.
|
||||
|
||||
## Features
|
||||
- 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
|
||||
|
||||
@ -48,10 +54,6 @@
|
||||
- Adding Support For Background Downloads
|
||||
- Adding Support For Multiple Downloads
|
||||
|
||||
## Installation and updates
|
||||
- Download the APK from [ IzzyOnDroid ](https://android.izzysoft.de/repo/apk/com.app.openlib) and install it.
|
||||
- Download the APK from [GitHub Releases](https://github.com/dstark5/Openlib/releases) and install it.
|
||||
|
||||
## Building from Source
|
||||
|
||||
- If you don't have Flutter SDK installed, please visit official [Flutter](https://flutter.dev) site.
|
||||
@ -76,7 +78,9 @@ flutter build
|
||||
- The Build Will Be In './build/app/outputs/flutter-apk/app-release.apk'
|
||||
|
||||
## Contribution
|
||||
Whether you have ideas, design changes or even major code changes, help is always welcome. The app gets better and better with each contribution, no matter how big or small! If you'd like to get involved ,Please read our [CONTRIBUTING.md](https://github.com/dstark5/Openlib/blob/main/CONTRIBUTING.md)
|
||||
Whether you have ideas, design changes or even major code changes, help is always welcome. The app gets better and better with each contribution, no matter how big or small!
|
||||
|
||||
If you'd like to get involved See [CONTRIBUTING.md](./CONTRIBUTING.md) for the guidelines.
|
||||
|
||||
## Issues
|
||||
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
@ -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.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
1
assets/captcha.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 11 KiB |
@ -1 +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'>Anna’s Archive</a>). The App Has Built In Reader to Read Books.</p><p>As <i>Anna’s Archive</i> doesn't have an API, the app works by sending requests to <i>Anna’s Archive</i> and parses the response to objects. The app extracts the mirrors from the responses, downloads the book and stores it in the application's document directory.</p><p>Features include a.o.:</p><ul><li>Trending Books</li><li>Download And Read Books With In-Built Viewer</li><li>Supports Epub And Pdf Formats</li><li>Filter Books</li><li>Sort Books</li></ul>
|
||||
<p>
|
||||
<i>Openlib</i> is an open source app to download and read books from shadow library (<a href='https://annas-archive.org/' target='_blank' rel='nofollow noopener'>Anna’s Archive</a>). The App Has Built In Reader to Read Books.
|
||||
</p>
|
||||
<p>
|
||||
As <i>Anna’s Archive</i> doesn't have an API, the app works by sending requests to <i>Anna’s Archive</i> and parses the response to objects. The app extracts the mirrors from the responses, downloads the book and stores it in the application's document directory.
|
||||
</p>
|
||||
<p>Main Features:</p>
|
||||
<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>
|
190
lib/main.dart
190
lib/main.dart
@ -1,19 +1,27 @@
|
||||
import 'package:dynamic_color/dynamic_color.dart';
|
||||
import 'dart:io' show Platform;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'dart:io' show Platform;
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:google_nav_bar/google_nav_bar.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
import 'package:google_fonts/google_fonts.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();
|
||||
@ -23,43 +31,63 @@ void main() async {
|
||||
databaseFactory = databaseFactoryFfi;
|
||||
}
|
||||
|
||||
Database db = await Sqlite.initDb();
|
||||
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(MyLibraryDb(dbInstance: db))],
|
||||
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) {
|
||||
return DynamicColorBuilder(
|
||||
builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
|
||||
return MaterialApp(
|
||||
// builder: (BuildContext context, Widget? child) {
|
||||
// return MediaQuery(
|
||||
// data: MediaQuery.of(context).copyWith(
|
||||
// textScaleFactor: 1.0,
|
||||
// ),
|
||||
// child: child!,
|
||||
// );
|
||||
// },
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: 'Openlib',
|
||||
theme: ThemeData(
|
||||
useMaterial3: true,
|
||||
colorScheme: lightDynamic,
|
||||
),
|
||||
darkTheme: ThemeData(
|
||||
useMaterial3: true,
|
||||
colorScheme: darkDynamic,
|
||||
),
|
||||
home: const HomePage(),
|
||||
);
|
||||
});
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return MaterialApp(
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
return MediaQuery(
|
||||
data: MediaQuery.of(context).copyWith(
|
||||
textScaleFactor: 1.0,
|
||||
),
|
||||
child: child!,
|
||||
);
|
||||
},
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: 'Openlib',
|
||||
theme: lightTheme,
|
||||
darkTheme: darkTheme,
|
||||
themeMode: ref.watch(themeModeProvider),
|
||||
home: const HomePage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,44 +102,86 @@ 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 AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle(
|
||||
systemNavigationBarColor: ElevationOverlay.applySurfaceTint(
|
||||
Theme.of(context).colorScheme.surface,
|
||||
Theme.of(context).colorScheme.surfaceTint,
|
||||
3)),
|
||||
child: Scaffold(
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
appBar: AppBar(
|
||||
leading:
|
||||
Icon(Icons.book, color: Theme.of(context).colorScheme.primary),
|
||||
title: const Text(
|
||||
"Openlib",
|
||||
style: TextStyle(fontWeight: FontWeight.w600),
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
title: const Text("Openlib"),
|
||||
titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
),
|
||||
body: _widgetOptions.elementAt(selectedIndex),
|
||||
bottomNavigationBar: SafeArea(
|
||||
child: GNav(
|
||||
backgroundColor: isDarkMode ? Colors.black : Colors.grey.shade200,
|
||||
haptic: true,
|
||||
tabBorderRadius: 50,
|
||||
tabActiveBorder: Border.all(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
titleSpacing: 1,
|
||||
),
|
||||
body: _widgetOptions.elementAt(selectedIndex),
|
||||
bottomNavigationBar: NavigationBar(
|
||||
destinations: const [
|
||||
NavigationDestination(
|
||||
icon: Icon(Icons.trending_up), label: "Trending"),
|
||||
NavigationDestination(icon: Icon(Icons.search), label: "Search"),
|
||||
NavigationDestination(
|
||||
icon: Icon(Icons.collections_bookmark), label: "My Library"),
|
||||
tabMargin: const EdgeInsets.fromLTRB(13, 6, 13, 2.5),
|
||||
curve: Curves.fastLinearToSlowEaseIn,
|
||||
duration: const Duration(milliseconds: 25),
|
||||
gap: 5,
|
||||
color: const Color.fromARGB(255, 255, 255, 255),
|
||||
activeColor: const Color.fromARGB(255, 255, 255, 255),
|
||||
iconSize: 19, // tab button icon size
|
||||
tabBackgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 13, vertical: 6.5),
|
||||
tabs: [
|
||||
GButton(
|
||||
icon: Icons.trending_up,
|
||||
text: 'Trending',
|
||||
iconColor: isDarkMode ? Colors.white : Colors.black,
|
||||
textStyle: const TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.white,
|
||||
fontSize: 11,
|
||||
),
|
||||
),
|
||||
GButton(
|
||||
icon: Icons.search,
|
||||
text: 'Search',
|
||||
iconColor: isDarkMode ? Colors.white : Colors.black,
|
||||
textStyle: const TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.white,
|
||||
fontSize: 11,
|
||||
),
|
||||
),
|
||||
GButton(
|
||||
icon: Icons.collections_bookmark,
|
||||
text: 'My Library',
|
||||
iconColor: isDarkMode ? Colors.white : Colors.black,
|
||||
textStyle: const TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.white,
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
selectedIndex: selectedIndex,
|
||||
onDestinationSelected: (index) async {
|
||||
onTabChange: (index) async {
|
||||
ref.read(selectedIndexProvider.notifier).state = index;
|
||||
},
|
||||
labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -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());
|
||||
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.querySelector('p[class="mb-4"]');
|
||||
String? link = pTag?.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 {
|
||||
|
@ -9,22 +9,38 @@ class Sqlite {
|
||||
|
||||
Database dbInstance = await openDatabase(
|
||||
path,
|
||||
version: 2,
|
||||
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;
|
||||
@ -164,4 +180,47 @@ class MyLibraryDb {
|
||||
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 "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
||||
@ -141,6 +164,9 @@ final getBookPosition =
|
||||
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
131
lib/ui/about_page.dart
Normal 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,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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,44 +17,122 @@ 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, required this.title})
|
||||
: super(key: key);
|
||||
const BookInfoPage({Key? key, required this.url}) : super(key: key);
|
||||
|
||||
final String url;
|
||||
final String title;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final bookInfo = ref.watch(bookInfoProvider(url));
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
// backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
title: Text(title),
|
||||
titleSpacing: 0,
|
||||
// titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
title: const Text("Openlib"),
|
||||
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(
|
||||
@ -98,23 +178,21 @@ class _ActionButtonWidgetState extends ConsumerState<ActionButtonWidget> {
|
||||
} else {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 21, bottom: 21),
|
||||
child: ElevatedButton(
|
||||
// style: ElevatedButton.styleFrom(
|
||||
// // backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
// textStyle: const TextStyle(
|
||||
// // fontSize: 13,
|
||||
// fontWeight: FontWeight.w500,
|
||||
// // color: Colors.white,
|
||||
// )),
|
||||
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 downloadFileWidget(ref, context, widget.data);
|
||||
},
|
||||
child: const Text('Add To My Library',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
// color: Colors.white,
|
||||
)),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Text('Add To My Library'),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -166,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(
|
||||
@ -190,38 +271,162 @@ 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();
|
||||
}
|
||||
|
||||
return AlertDialog(
|
||||
title: const Text("Downloading Book"),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(title),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(15.0),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: 285,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
color: Theme.of(context).colorScheme.tertiaryContainer,
|
||||
),
|
||||
padding: const EdgeInsets.fromLTRB(20, 20, 20, 20),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Text(
|
||||
"Downloading Book",
|
||||
style: TextStyle(
|
||||
fontSize: 19,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
decoration: TextDecoration.none),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.bold,
|
||||
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: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Text(
|
||||
'$downloadedFileSize/$fileSize',
|
||||
style: TextStyle(
|
||||
fontSize: 9,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
decoration: TextDecoration.none,
|
||||
letterSpacing: 1),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(50)),
|
||||
child: LinearProgressIndicator(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
backgroundColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.tertiary
|
||||
.withAlpha(50),
|
||||
value: downloadProgress,
|
||||
minHeight: 4,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.secondary,
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.white,
|
||||
)),
|
||||
onPressed: () {
|
||||
ref.read(cancelCurrentDownload).cancel();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(3.0),
|
||||
child: Text('Cancel'),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
LinearProgressIndicator(
|
||||
value: downloadProgress,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Text(
|
||||
'$downloadedFileSize/$fileSize',
|
||||
)
|
||||
],
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
ref.read(cancelCurrentDownload).cancel();
|
||||
Navigator.pop(context, 'Cancel');
|
||||
},
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
@ -2,7 +2,18 @@ import 'package:flutter/material.dart';
|
||||
import 'package:openlib/ui/extensions.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
|
||||
// TODO: Redesign this widget
|
||||
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,
|
||||
@ -10,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);
|
||||
@ -18,34 +30,34 @@ 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) {
|
||||
return Card(
|
||||
shadowColor: Colors.black.withOpacity(0),
|
||||
margin: const EdgeInsets.only(bottom: 20),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
side: BorderSide(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
width: 1,
|
||||
String? fileType = getFileType(info);
|
||||
|
||||
return InkWell(
|
||||
onTap: onClick,
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: 120,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
color: Theme.of(context).colorScheme.tertiaryContainer,
|
||||
),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: onClick,
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
margin: const EdgeInsets.only(bottom: 10),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
CachedNetworkImage(
|
||||
height: 140,
|
||||
width: 105,
|
||||
height: 120,
|
||||
width: 90,
|
||||
imageUrl: thumbnail ?? "",
|
||||
imageBuilder: (context, imageProvider) => Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(5)),
|
||||
image: DecorationImage(
|
||||
image: imageProvider,
|
||||
fit: BoxFit.fill,
|
||||
@ -55,7 +67,7 @@ class BookInfoCard extends StatelessWidget {
|
||||
placeholder: (context, url) => Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
color: "#F8C0C8".toColor(),
|
||||
),
|
||||
height: 120,
|
||||
width: 90,
|
||||
@ -64,7 +76,7 @@ class BookInfoCard extends StatelessWidget {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
color: "#F8C0C8".toColor(),
|
||||
),
|
||||
height: 120,
|
||||
width: 90,
|
||||
@ -76,7 +88,7 @@ class BookInfoCard extends StatelessWidget {
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(18),
|
||||
padding: const EdgeInsets.all(5),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
@ -85,38 +97,70 @@ class BookInfoCard extends StatelessWidget {
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.bold,
|
||||
// color: Colors.black,
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Text(
|
||||
publisher,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
// color: "#4D4D4D".toColor(),
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
author,
|
||||
publisher,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
// color: "#7B7B7B".toColor(),
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color:
|
||||
Theme.of(context).textTheme.headlineMedium?.color,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
if (fileType != null)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: "#a5a5a5".toColor(),
|
||||
borderRadius: BorderRadius.circular(2.5),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(3, 2, 3, 2),
|
||||
child: Text(
|
||||
fileType,
|
||||
style: const TextStyle(
|
||||
fontSize: 8.5,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (fileType != null)
|
||||
const SizedBox(
|
||||
width: 3,
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
author,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineSmall
|
||||
?.color,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -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,11 +10,14 @@ 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(),
|
||||
physics: const BouncingScrollPhysics(),
|
||||
scrollDirection: Axis.vertical,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 20, right: 20, top: 10),
|
||||
padding: const EdgeInsets.only(left: 15, right: 15, top: 10),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@ -26,8 +28,8 @@ class BookInfoWidget extends StatelessWidget {
|
||||
),
|
||||
Center(
|
||||
child: CachedNetworkImage(
|
||||
height: 240,
|
||||
width: 180,
|
||||
height: 230,
|
||||
width: 170,
|
||||
imageUrl: data.thumbnail,
|
||||
imageBuilder: (context, imageProvider) => Container(
|
||||
decoration: BoxDecoration(
|
||||
@ -43,8 +45,8 @@ class BookInfoWidget extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: Colors.grey,
|
||||
),
|
||||
height: 240,
|
||||
width: 180,
|
||||
height: 230,
|
||||
width: 170,
|
||||
),
|
||||
errorWidget: (context, url, error) {
|
||||
return Container(
|
||||
@ -63,70 +65,66 @@ class BookInfoWidget extends StatelessWidget {
|
||||
),
|
||||
_TopPaddedText(
|
||||
text: data.title,
|
||||
fontSize: 22,
|
||||
fontSize: 19,
|
||||
topPadding: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
// color: Colors.black,
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
maxLines: 7,
|
||||
),
|
||||
_TopPaddedText(
|
||||
text: data.publisher ?? "unknown",
|
||||
fontSize: 17,
|
||||
fontSize: 15,
|
||||
topPadding: 7,
|
||||
color: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||
color: Theme.of(context).textTheme.headlineMedium!.color!,
|
||||
maxLines: 4,
|
||||
),
|
||||
_TopPaddedText(
|
||||
text: data.author ?? "unknown",
|
||||
fontSize: 20,
|
||||
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
|
||||
child,
|
||||
Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Description",
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 15, bottom: 10),
|
||||
child: Text(
|
||||
data.description ?? "",
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Description",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 7, bottom: 10),
|
||||
child: Text(
|
||||
description,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.bold,
|
||||
color:
|
||||
Theme.of(context).colorScheme.tertiary.withAlpha(150),
|
||||
letterSpacing: 1.5,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
@ -139,16 +137,14 @@ class _TopPaddedText extends StatelessWidget {
|
||||
final String text;
|
||||
final double fontSize;
|
||||
final double topPadding;
|
||||
final Color? color;
|
||||
final Color color;
|
||||
final int maxLines;
|
||||
final FontWeight fontWeight;
|
||||
|
||||
const _TopPaddedText(
|
||||
{required this.text,
|
||||
required this.fontSize,
|
||||
required this.topPadding,
|
||||
this.color,
|
||||
this.fontWeight = FontWeight.w400,
|
||||
required this.color,
|
||||
required this.maxLines,
|
||||
Key? key})
|
||||
: super(key: key);
|
||||
@ -161,9 +157,9 @@ class _TopPaddedText extends StatelessWidget {
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontSize: fontSize,
|
||||
fontWeight: fontWeight,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: color,
|
||||
// letterSpacing: 0.5,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: maxLines,
|
||||
|
@ -16,28 +16,120 @@ class ShowDeleteDialog extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
// if (false) {
|
||||
// return
|
||||
// }
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(15.0),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: 219,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
color: Theme.of(context).colorScheme.tertiaryContainer,
|
||||
),
|
||||
padding: const EdgeInsets.fromLTRB(20, 50, 20, 20),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Text(
|
||||
"Delete Book",
|
||||
style: TextStyle(
|
||||
fontSize: 19,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
decoration: TextDecoration.none),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Text(
|
||||
"This is permanent and cannot be undone",
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.tertiary
|
||||
.withAlpha(170),
|
||||
decoration: TextDecoration.none),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
style: ButtonStyle(
|
||||
shape: MaterialStateProperty.all(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(50.0),
|
||||
side: BorderSide(
|
||||
width: 3,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.secondary),
|
||||
),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
ref.read(deleteFileFromMyLib(
|
||||
FileName(md5: id, format: format)));
|
||||
Navigator.of(context).pop();
|
||||
|
||||
return AlertDialog(
|
||||
title: const Text("Delete Book?"),
|
||||
content: const Text("This is permanent and cannot be undone"),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
ref.read(deleteFileFromMyLib(FileName(md5: id, format: format)));
|
||||
Navigator.of(context).pop();
|
||||
showSnackBar(
|
||||
context: context,
|
||||
message: 'Book has been Deleted!');
|
||||
|
||||
showSnackBar(context: context, message: 'Book has been Deleted!');
|
||||
|
||||
onDelete();
|
||||
},
|
||||
child: const Text('Delete'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, 'Cancel'),
|
||||
child: const Text('Cancel'),
|
||||
onDelete();
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(5.0),
|
||||
child: Text(
|
||||
'Delete',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.secondary,
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.white,
|
||||
)),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(5.0),
|
||||
child: Text('Cancel'),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
@ -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,40 +21,31 @@ 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(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
FilledButton(
|
||||
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),
|
||||
@ -60,7 +55,7 @@ class FileOpenAndDeleteButtons extends StatelessWidget {
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
OutlinedButton(
|
||||
TextButton(
|
||||
style: ButtonStyle(
|
||||
shape: MaterialStateProperty.all(
|
||||
RoundedRectangleBorder(
|
||||
@ -89,7 +84,7 @@ class FileOpenAndDeleteButtons extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
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!');
|
||||
}
|
||||
}
|
||||
|
@ -7,13 +7,13 @@ class TitleText extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 10, right: 10, top: 20, bottom: 10),
|
||||
padding: const EdgeInsets.only(left: 10, bottom: 7),
|
||||
child: Text(
|
||||
text,
|
||||
style: const TextStyle(
|
||||
fontSize: 25,
|
||||
fontWeight: FontWeight.w500,
|
||||
// color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
style: TextStyle(
|
||||
fontSize: 19,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -5,14 +5,14 @@ void showSnackBar({required BuildContext context, required String message}) {
|
||||
content: Text(
|
||||
message,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
// fontWeight: FontWeight.bold,
|
||||
// color: Colors.white,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
// ignore: use_build_context_synchronously
|
||||
// backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
width: 300,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(50))),
|
||||
|
@ -1,10 +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/services/files.dart' show getFilePath;
|
||||
import 'package:openlib/ui/components/snack_bar_widget.dart';
|
||||
import 'package:openlib/state/state.dart'
|
||||
show filePathProvider, saveEpubState, getBookPosition;
|
||||
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});
|
||||
@ -24,18 +81,18 @@ class _EpubViewState extends ConsumerState<EpubViewerWidget> {
|
||||
}, error: (error, stack) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
// backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
title: const Text("Openlib"),
|
||||
// titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
),
|
||||
body: Center(child: Text(error.toString())),
|
||||
);
|
||||
}, loading: () {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
// backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
title: const Text("Openlib"),
|
||||
// titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
),
|
||||
body: Center(
|
||||
child: SizedBox(
|
||||
@ -94,10 +151,9 @@ class _EpubViewerState extends ConsumerState<EpubViewer> {
|
||||
final position = ref.watch(getBookPosition(widget.fileName));
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
// backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
title: const Text("Openlib"),
|
||||
titleSpacing: 0,
|
||||
// titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
),
|
||||
endDrawer: Drawer(
|
||||
child: EpubViewTableOfContents(controller: _epubReaderController),
|
||||
|
@ -7,18 +7,17 @@ import 'package:openlib/ui/components/book_info_widget.dart';
|
||||
import 'package:openlib/ui/components/file_buttons_widget.dart';
|
||||
|
||||
class BookPage extends StatelessWidget {
|
||||
const BookPage({Key? key, required this.id, required this.title})
|
||||
: super(key: key);
|
||||
const BookPage({Key? key, required this.id}) : super(key: key);
|
||||
|
||||
final String id;
|
||||
final String title;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(title),
|
||||
titleSpacing: 0,
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
title: const Text("Openlib"),
|
||||
titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
),
|
||||
body: Consumer(
|
||||
builder: (BuildContext context, WidgetRef ref, _) {
|
||||
@ -43,8 +42,12 @@ class BookPage extends StatelessWidget {
|
||||
);
|
||||
} else {
|
||||
return Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
child: SizedBox(
|
||||
width: 25,
|
||||
height: 25,
|
||||
child: CircularProgressIndicator(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
));
|
||||
}
|
||||
});
|
||||
|
@ -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,11 +35,12 @@ class MyLibraryPage extends ConsumerWidget {
|
||||
author: i.author ?? "",
|
||||
publisher: i.publisher ?? "",
|
||||
thumbnail: i.thumbnail,
|
||||
info: i.info,
|
||||
link: i.link,
|
||||
onClick: () {
|
||||
Navigator.push(context, MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return BookPage(id: i.id, title: i.title);
|
||||
return BookPage(id: i.id);
|
||||
}));
|
||||
}))
|
||||
.toList()),
|
||||
@ -81,8 +83,12 @@ class MyLibraryPage extends ConsumerWidget {
|
||||
},
|
||||
loading: () {
|
||||
return Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
child: SizedBox(
|
||||
width: 25,
|
||||
height: 25,
|
||||
child: CircularProgressIndicator(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
));
|
||||
},
|
||||
);
|
||||
|
@ -7,10 +7,31 @@ import 'package:openlib/state/state.dart'
|
||||
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});
|
||||
|
||||
@ -38,9 +59,9 @@ class _PdfViewState extends ConsumerState<PdfView> {
|
||||
}, loading: () {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
// backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
title: const Text("Openlib"),
|
||||
// titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
),
|
||||
body: Center(
|
||||
child: SizedBox(
|
||||
@ -111,10 +132,9 @@ class _PdfViewerState extends ConsumerState<PdfViewer> {
|
||||
final totalPages = ref.watch(totalPdfPage);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
// backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
title: const Text("Openlib"),
|
||||
titleSpacing: 0,
|
||||
// titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
actions: isMobile
|
||||
? [
|
||||
IconButton(
|
||||
@ -187,8 +207,12 @@ class _PdfViewerState extends ConsumerState<PdfViewer> {
|
||||
},
|
||||
loading: () {
|
||||
return Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
child: SizedBox(
|
||||
width: 25,
|
||||
height: 25,
|
||||
child: CircularProgressIndicator(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
));
|
||||
},
|
||||
)
|
||||
|
@ -23,30 +23,22 @@ class ResultPage extends ConsumerWidget {
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
// backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
title: const Text(
|
||||
"Results",
|
||||
style: TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
titleSpacing: 0,
|
||||
|
||||
// titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
title: const Text("Openlib"),
|
||||
titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
),
|
||||
body: searchBooks.when(
|
||||
data: (data) {
|
||||
if (data.isNotEmpty) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 5,
|
||||
right: 5,
|
||||
),
|
||||
padding: const EdgeInsets.only(left: 5, right: 5, top: 10),
|
||||
child: CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
// const SliverToBoxAdapter(
|
||||
// child: TitleText("Results"),
|
||||
// ),
|
||||
const SliverToBoxAdapter(
|
||||
child: TitleText("Results"),
|
||||
),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.only(left: 5, right: 5, top: 15),
|
||||
padding: const EdgeInsets.only(left: 5, right: 5, top: 10),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildListDelegate(data
|
||||
.map((i) => BookInfoCard(
|
||||
@ -54,12 +46,12 @@ 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(
|
||||
builder: (BuildContext context) {
|
||||
return BookInfoPage(
|
||||
url: i.link, title: i.title);
|
||||
return BookInfoPage(url: i.link);
|
||||
}));
|
||||
},
|
||||
))
|
||||
@ -112,14 +104,22 @@ class ResultPage extends ConsumerWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(left: 5, right: 5, top: 10),
|
||||
child: TitleText("Results"),
|
||||
),
|
||||
Expanded(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
child: SizedBox(
|
||||
width: 25,
|
||||
height: 25,
|
||||
child: CircularProgressIndicator(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
))
|
||||
],
|
||||
),
|
||||
|
@ -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,
|
||||
@ -39,23 +48,21 @@ class SearchPage extends ConsumerWidget {
|
||||
padding: const EdgeInsets.only(left: 7, right: 7, top: 10),
|
||||
child: TextField(
|
||||
showCursor: true,
|
||||
// cursorColor: Theme.of(context).colorScheme.secondary,
|
||||
|
||||
cursorColor: Theme.of(context).colorScheme.secondary,
|
||||
decoration: InputDecoration(
|
||||
label: const Text("Search"),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).colorScheme.outline, width: 2),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(50)),
|
||||
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.outline, width: 2),
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
width: 2),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(50)),
|
||||
),
|
||||
suffixIcon: IconButton(
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
// color: Theme.of(context).colorScheme.secondary,
|
||||
padding: const EdgeInsets.only(right: 5),
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
icon: const Icon(
|
||||
Icons.search,
|
||||
size: 23,
|
||||
@ -63,16 +70,16 @@ class SearchPage extends ConsumerWidget {
|
||||
onPressed: () => onSubmit(context, ref),
|
||||
),
|
||||
filled: true,
|
||||
// hintStyle: const TextStyle(
|
||||
// color: Colors.grey, fontWeight: FontWeight.bold),
|
||||
hintText: "Search books ,author or ISBN",
|
||||
// fillColor: Theme.of(context).colorScheme.primary,
|
||||
hintStyle: const TextStyle(
|
||||
color: Colors.grey, fontWeight: FontWeight.bold),
|
||||
hintText: "Search",
|
||||
fillColor: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
onSubmitted: (String value) => onSubmit(context, ref),
|
||||
style: const TextStyle(
|
||||
// color: Theme.of(context).colorScheme.tertiary,
|
||||
// fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12,
|
||||
),
|
||||
onChanged: (String value) {
|
||||
ref.read(searchQueryProvider.notifier).state = value;
|
||||
@ -87,19 +94,17 @@ class SearchPage extends ConsumerWidget {
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Type',
|
||||
labelStyle: TextStyle(
|
||||
fontSize: 12,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
width: 2),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(50)),
|
||||
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.outline,
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
width: 2),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(50)),
|
||||
),
|
||||
@ -113,7 +118,8 @@ class SearchPage extends ConsumerWidget {
|
||||
value: value,
|
||||
child: Text(
|
||||
value,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
style: const TextStyle(
|
||||
fontSize: 12, fontWeight: FontWeight.bold),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
@ -131,19 +137,17 @@ class SearchPage extends ConsumerWidget {
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Sort by',
|
||||
labelStyle: TextStyle(
|
||||
fontSize: 12,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
width: 2),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(50)),
|
||||
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.outline,
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
width: 2),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(50)),
|
||||
),
|
||||
@ -156,7 +160,8 @@ class SearchPage extends ConsumerWidget {
|
||||
value: value,
|
||||
child: Text(
|
||||
value,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
style: const TextStyle(
|
||||
fontSize: 12, fontWeight: FontWeight.bold),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
@ -166,6 +171,47 @@ 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
163
lib/ui/settings_page.dart
Normal 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
72
lib/ui/themes.dart
Normal 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(),
|
||||
),
|
||||
);
|
@ -17,122 +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: 0),
|
||||
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(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
onTap: () {
|
||||
Navigator.push(context,
|
||||
MaterialPageRoute(builder: (BuildContext context) {
|
||||
return ResultPage(
|
||||
searchQuery: data[index].title!,
|
||||
);
|
||||
}));
|
||||
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,
|
||||
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,
|
||||
),
|
||||
height: imageHeight,
|
||||
width: imageWidth,
|
||||
child: const Center(
|
||||
child: Icon(Icons.image_rounded),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
child: SizedBox(
|
||||
width: imageWidth,
|
||||
child: Text(
|
||||
data[index].title!,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.displayMedium,
|
||||
maxLines: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
CachedNetworkImage(
|
||||
height: imageHeight,
|
||||
width: imageWidth,
|
||||
imageUrl: data[index].thumbnail!,
|
||||
imageBuilder: (context, imageProvider) =>
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
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(),
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.surfaceVariant,
|
||||
),
|
||||
height: imageHeight,
|
||||
width: imageWidth,
|
||||
),
|
||||
errorWidget: (context, url, error) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.surfaceVariant,
|
||||
),
|
||||
height: imageHeight,
|
||||
width: imageWidth,
|
||||
child: const Center(
|
||||
child: Icon(Icons.image_rounded),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: SizedBox(
|
||||
width: imageWidth,
|
||||
child: Text(
|
||||
data[index].title!,
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w400),
|
||||
maxLines: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
]),
|
||||
);
|
||||
},
|
||||
childCount: data.length,
|
||||
childCount: data.length,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}, error: (error, _) {
|
||||
return CustomErrorWidget(
|
||||
error: error,
|
||||
stackTrace: _,
|
||||
onRefresh: () {
|
||||
// ignore: unused_result
|
||||
ref.refresh(getTrendingBooks);
|
||||
);
|
||||
},
|
||||
);
|
||||
}, loading: () {
|
||||
return const 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
75
lib/ui/webview_page.dart
Normal 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);
|
||||
}
|
||||
},
|
||||
)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -6,13 +6,9 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <dynamic_color/dynamic_color_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) dynamic_color_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "DynamicColorPlugin");
|
||||
dynamic_color_plugin_register_with_registrar(dynamic_color_registrar);
|
||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||
|
@ -3,7 +3,6 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
dynamic_color
|
||||
url_launcher_linux
|
||||
)
|
||||
|
||||
|
@ -5,13 +5,11 @@
|
||||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import dynamic_color
|
||||
import path_provider_foundation
|
||||
import sqflite
|
||||
import url_launcher_macos
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||
|
252
pubspec.lock
252
pubspec.lock
@ -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,18 +141,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dio
|
||||
sha256: "3866d67f93523161b643187af65f5ac08bc991a5bcdaf41a2d587fe4ccb49993"
|
||||
sha256: ce75a1b40947fea0a0e16ce73337122a86762e38b982e1ccb909daa3b9bc4197
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.3.0"
|
||||
dynamic_color:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dynamic_color
|
||||
sha256: de4798a7069121aee12d5895315680258415de9b00e717723a1bd73d58f0126d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.6.6"
|
||||
version: "5.3.2"
|
||||
epub_view:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -189,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:
|
||||
@ -242,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:
|
||||
@ -258,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:
|
||||
@ -372,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:
|
||||
@ -400,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:
|
||||
@ -420,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:
|
||||
@ -476,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:
|
||||
@ -508,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:
|
||||
@ -537,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:
|
||||
@ -585,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:
|
||||
@ -625,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:
|
||||
@ -649,74 +697,74 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: url_launcher
|
||||
sha256: "781bd58a1eb16069412365c98597726cd8810ae27435f04b3b4d3a470bacd61e"
|
||||
sha256: "47e208a6711459d813ba18af120d9663c20bdf6985d6ad39fe165d2538378d27"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.12"
|
||||
version: "6.1.14"
|
||||
url_launcher_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "3dd2388cc0c42912eee04434531a26a82512b9cb1827e0214430c9bcbddfe025"
|
||||
sha256: b04af59516ab45762b2ca6da40fa830d72d0f6045cd97744450b73493fa76330
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.38"
|
||||
version: "6.1.0"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2"
|
||||
sha256: "7c65021d5dee51813d652357bc65b8dd4a6177082a9966bc8ba6ee477baa795f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.4"
|
||||
version: "6.1.5"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_linux
|
||||
sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5"
|
||||
sha256: b651aad005e0cb06a01dbd84b428a301916dc75f0e7ea6165f80057fee2d8e8e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.5"
|
||||
version: "3.0.6"
|
||||
url_launcher_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_macos
|
||||
sha256: "1c4fdc0bfea61a70792ce97157e5cc17260f61abbe4f39354513f39ec6fd73b1"
|
||||
sha256: b55486791f666e62e0e8ff825e58a023fd6b1f71c49926483f1128d3bbd8fe88
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.6"
|
||||
version: "3.0.7"
|
||||
url_launcher_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_platform_interface
|
||||
sha256: bfdfa402f1f3298637d71ca8ecfe840b4696698213d5346e9d12d4ab647ee2ea
|
||||
sha256: "95465b39f83bfe95fcb9d174829d6476216f2d548b79c38ab2506e0458787618"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
version: "2.1.5"
|
||||
url_launcher_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_web
|
||||
sha256: cc26720eefe98c1b71d85f9dc7ef0cada5132617046369d9dc296b3ecaa5cbb4
|
||||
sha256: "2942294a500b4fa0b918685aff406773ba0a4cd34b7f42198742a94083020ce5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.18"
|
||||
version: "2.0.20"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_windows
|
||||
sha256: "7967065dd2b5fccc18c653b97958fdf839c5478c28e767c61ee879f4e7882422"
|
||||
sha256: "95fef3129dc7cfaba2bc3d5ba2e16063bb561fc6d78e63eee16162bc70029069"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.7"
|
||||
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:
|
||||
@ -749,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:
|
||||
@ -782,5 +886,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
sdks:
|
||||
dart: ">=3.0.5 <4.0.0"
|
||||
flutter: ">=3.10.0"
|
||||
dart: ">=3.1.0 <4.0.0"
|
||||
flutter: ">=3.13.0"
|
||||
|
10
pubspec.yaml
10
pubspec.yaml
@ -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+1
|
||||
|
||||
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,6 +45,10 @@ 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
|
||||
@ -56,7 +61,6 @@ dependencies:
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.2
|
||||
dev: ^1.0.0
|
||||
dynamic_color: ^1.6.6
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@ -119,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
|
@ -6,12 +6,12 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <dynamic_color/dynamic_color_plugin_c_api.h>
|
||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
DynamicColorPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("DynamicColorPluginCApi"));
|
||||
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
dynamic_color
|
||||
permission_handler_windows
|
||||
url_launcher_windows
|
||||
)
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user