Compare commits
30 Commits
v1.0-beta.
...
material-y
Author | SHA1 | Date | |
---|---|---|---|
fbe1ee70c9 | |||
c7b1fec73e | |||
9cc3c3dde5 | |||
72abde09e0 | |||
3d3097152d | |||
c5b1fc54d9 | |||
d253e52971 | |||
0b502629fb | |||
8ec03ba5d7 | |||
068a2c68e0 | |||
59d467c692 | |||
9fffb3829f | |||
e25a12b684 | |||
86684c5436 | |||
e7b6a9d40f | |||
60df4afac2 | |||
bcfb35ab14 | |||
4ac80f08d8 | |||
a9fde1c561 | |||
110d089b8d | |||
4445fc4d84 | |||
11d19b3001 | |||
052c5993b1 | |||
bc36a350ab | |||
9102b0be7b | |||
365d29c19e | |||
52c742e8d0 | |||
d2c992bf80 | |||
39a91b9992 | |||
19df3b4f7b |
11
README.md
@ -3,7 +3,13 @@
|
||||
|
||||
#### 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"
|
||||
alt="Get it on IzzyDroid"
|
||||
height="80">](https://android.izzysoft.de/repo/apk/com.app.openlib)
|
||||
|
||||
|
||||
## Note
|
||||
@ -43,6 +49,7 @@
|
||||
- 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
|
||||
@ -69,7 +76,7 @@ flutter build
|
||||
- The Build Will Be In './build/app/outputs/flutter-apk/app-release.apk'
|
||||
|
||||
## Contribution
|
||||
Whether you have ideas, design changes or even major code changes, help is always welcome. The app gets better and better with each contribution, no matter how big or small! If you'd like to get involved just fork the app make a pull request.
|
||||
Whether you have ideas, design changes or even major code changes, help is always welcome. The app gets better and better with each contribution, no matter how big or small! If you'd like to get involved ,Please read our [CONTRIBUTING.md](https://github.com/dstark5/Openlib/blob/main/CONTRIBUTING.md)
|
||||
|
||||
## Issues
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<application
|
||||
android:label="Openlib"
|
||||
android:name="${applicationName}"
|
||||
|
1
fastlane/metadata/android/en-US/full_description.txt
Normal file
@ -0,0 +1 @@
|
||||
<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>
|
BIN
fastlane/metadata/android/en-US/images/icon.png
Normal file
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 17 KiB |
1
fastlane/metadata/android/en-US/short_description.txt
Normal file
@ -0,0 +1 @@
|
||||
download and read books from shadow library (Anna’s Archive)
|
BIN
github_releases.png
Normal file
After Width: | Height: | Size: 15 KiB |
145
lib/main.dart
@ -1,7 +1,10 @@
|
||||
import 'package:dynamic_color/dynamic_color.dart';
|
||||
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/sqflite.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
import 'package:openlib/ui/extensions.dart';
|
||||
@ -14,55 +17,49 @@ import 'package:openlib/state/state.dart'
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
|
||||
sqfliteFfiInit();
|
||||
databaseFactory = databaseFactoryFfi;
|
||||
}
|
||||
|
||||
Database db = await Sqlite.initDb();
|
||||
runApp(ProviderScope(
|
||||
runApp(
|
||||
ProviderScope(
|
||||
overrides: [dbProvider.overrideWithValue(MyLibraryDb(dbInstance: db))],
|
||||
child: const MyApp()));
|
||||
child: const MyApp(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
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!,
|
||||
);
|
||||
},
|
||||
// builder: (BuildContext context, Widget? child) {
|
||||
// return MediaQuery(
|
||||
// data: MediaQuery.of(context).copyWith(
|
||||
// textScaleFactor: 1.0,
|
||||
// ),
|
||||
// child: child!,
|
||||
// );
|
||||
// },
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: 'Openlib',
|
||||
theme: ThemeData(
|
||||
primaryColor: Colors.white,
|
||||
colorScheme: ColorScheme.light(
|
||||
primary: Colors.white,
|
||||
secondary: '#FB0101'.toColor(),
|
||||
tertiary: Colors.black,
|
||||
tertiaryContainer: '#F2F2F2'.toColor(),
|
||||
),
|
||||
textTheme: const TextTheme(
|
||||
displayLarge: TextStyle(
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 21,
|
||||
),
|
||||
displayMedium: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
fontFamily: GoogleFonts.nunito().fontFamily,
|
||||
useMaterial3: true,
|
||||
textSelectionTheme: TextSelectionThemeData(
|
||||
selectionColor: '#FB0101'.toColor(),
|
||||
selectionHandleColor: '#FB0101'.toColor())),
|
||||
colorScheme: lightDynamic,
|
||||
),
|
||||
darkTheme: ThemeData(
|
||||
useMaterial3: true,
|
||||
colorScheme: darkDynamic,
|
||||
),
|
||||
home: const HomePage(),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,67 +81,37 @@ class _HomePageState extends ConsumerState<HomePage> {
|
||||
Widget build(BuildContext context) {
|
||||
final selectedIndex = ref.watch(selectedIndexProvider);
|
||||
|
||||
return Scaffold(
|
||||
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(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
title: const Text("Openlib"),
|
||||
titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
leading:
|
||||
Icon(Icons.book, color: Theme.of(context).colorScheme.primary),
|
||||
title: const Text(
|
||||
"Openlib",
|
||||
style: TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
titleSpacing: 1,
|
||||
),
|
||||
body: _widgetOptions.elementAt(selectedIndex),
|
||||
bottomNavigationBar: SafeArea(
|
||||
child: GNav(
|
||||
rippleColor: Colors.redAccent,
|
||||
backgroundColor: Colors.black,
|
||||
haptic: true,
|
||||
tabBorderRadius: 50,
|
||||
tabActiveBorder: Border.all(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
tabMargin: const EdgeInsets.fromLTRB(13, 6, 13, 2.5),
|
||||
curve: Curves.easeInOut, // tab animation curves
|
||||
duration: const Duration(milliseconds: 150),
|
||||
gap: 5,
|
||||
color: const Color.fromARGB(255, 255, 255, 255),
|
||||
activeColor: const Color.fromARGB(255, 255, 255, 255),
|
||||
iconSize: 21, // tab button icon size
|
||||
tabBackgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 6.5),
|
||||
tabs: const [
|
||||
GButton(
|
||||
icon: Icons.trending_up,
|
||||
text: 'Trending',
|
||||
iconColor: Colors.white,
|
||||
textStyle: TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.white,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
GButton(
|
||||
icon: Icons.search,
|
||||
text: 'Search',
|
||||
iconColor: Colors.white,
|
||||
textStyle: TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.white,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
GButton(
|
||||
icon: Icons.collections_bookmark,
|
||||
text: 'My Library',
|
||||
iconColor: Colors.white,
|
||||
textStyle: TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.white,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
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"),
|
||||
],
|
||||
selectedIndex: selectedIndex,
|
||||
onTabChange: (index) async {
|
||||
onDestinationSelected: (index) async {
|
||||
ref.read(selectedIndexProvider.notifier).state = index;
|
||||
},
|
||||
labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -127,7 +127,7 @@ class AnnasArchieve {
|
||||
String? link = pTag?.querySelector('a')?.attributes['href'];
|
||||
return link;
|
||||
} catch (e) {
|
||||
print(e);
|
||||
// print(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,32 @@
|
||||
import 'dart:io';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
|
||||
class Sqlite {
|
||||
static Future<Database> initDb() async {
|
||||
var databasesPath = await getDatabasesPath();
|
||||
String path = '$databasesPath/mylibrary.db';
|
||||
bool isMobile = Platform.isAndroid || Platform.isIOS;
|
||||
|
||||
Database dbInstance = await openDatabase(path, version: 1,
|
||||
Database dbInstance = await openDatabase(
|
||||
path,
|
||||
version: 2,
|
||||
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)');
|
||||
});
|
||||
if (isMobile) {
|
||||
await db.execute(
|
||||
'CREATE TABLE bookposition (fileName TEXT PRIMARY KEY,position TEXT)');
|
||||
}
|
||||
},
|
||||
onUpgrade: (db, oldVersion, newVersion) async {
|
||||
List<dynamic> isTableExist = await db.query('sqlite_master',
|
||||
where: 'name = ?', whereArgs: ['bookposition']);
|
||||
if (isMobile && isTableExist.isEmpty) {
|
||||
await db.execute(
|
||||
'CREATE TABLE bookposition (fileName TEXT PRIMARY KEY,position TEXT)');
|
||||
}
|
||||
},
|
||||
);
|
||||
return dbInstance;
|
||||
}
|
||||
}
|
||||
@ -118,4 +135,33 @@ class MyLibraryDb {
|
||||
});
|
||||
return myBookList.reversed.toList();
|
||||
}
|
||||
|
||||
Future<void> saveBookState(String fileName, String position) async {
|
||||
await dbInstance.insert(
|
||||
'bookposition',
|
||||
{'fileName': fileName, 'position': position},
|
||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> deleteBookState(String fileName) async {
|
||||
await dbInstance.delete(
|
||||
'bookposition',
|
||||
where: 'fileName = ?',
|
||||
whereArgs: [fileName],
|
||||
);
|
||||
}
|
||||
|
||||
Future<String?> getBookState(String fileName) async {
|
||||
List<Map<String, dynamic>> data = await dbInstance
|
||||
.query('bookposition', where: 'fileName = ?', whereArgs: [fileName]);
|
||||
List<dynamic> dataList = List.generate(data.length, (i) {
|
||||
return {'fileName': data[i]['fileName'], 'position': data[i]['position']};
|
||||
});
|
||||
if (dataList.isNotEmpty) {
|
||||
return dataList[0]['position'];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,9 +35,11 @@ Future<void> deleteFileWithDbData(
|
||||
String appDirPath = await getAppDirectoryPath;
|
||||
await deleteFile('$appDirPath/$fileName');
|
||||
await ref.read(dbProvider).delete(md5);
|
||||
await ref.read(dbProvider).deleteBookState(fileName);
|
||||
// ignore: unused_result
|
||||
ref.refresh(myLibraryProvider);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
// print(e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
@ -125,6 +125,22 @@ final deleteFileFromMyLib =
|
||||
final pdfCurrentPage = StateProvider.autoDispose<int>((ref) => 0);
|
||||
final totalPdfPage = StateProvider.autoDispose<int>((ref) => 0);
|
||||
|
||||
Future<void> savePdfState(String fileName, WidgetRef ref) async {
|
||||
String position = ref.watch(pdfCurrentPage).toString();
|
||||
await ref.watch(dbProvider).saveBookState(fileName, position);
|
||||
}
|
||||
|
||||
Future<void> saveEpubState(
|
||||
String fileName, String? position, WidgetRef ref) async {
|
||||
String pos = position ?? '';
|
||||
await ref.watch(dbProvider).saveBookState(fileName, pos);
|
||||
}
|
||||
|
||||
final getBookPosition =
|
||||
FutureProvider.family.autoDispose<String?, String>((ref, fileName) async {
|
||||
return await ref.read(dbProvider).getBookState(fileName);
|
||||
});
|
||||
|
||||
final filePathProvider =
|
||||
FutureProvider.family<String, String>((ref, fileName) async {
|
||||
String path = await getFilePath(fileName);
|
||||
|
@ -23,18 +23,21 @@ import 'package:openlib/ui/components/file_buttons_widget.dart';
|
||||
import 'package:openlib/ui/components/snack_bar_widget.dart';
|
||||
|
||||
class BookInfoPage extends ConsumerWidget {
|
||||
const BookInfoPage({Key? key, required this.url}) : super(key: key);
|
||||
const BookInfoPage({Key? key, required this.url, required this.title})
|
||||
: 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: const Text("Openlib"),
|
||||
titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
// backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
title: Text(title),
|
||||
titleSpacing: 0,
|
||||
// titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
),
|
||||
body: bookInfo.when(
|
||||
data: (data) {
|
||||
@ -95,21 +98,23 @@ class _ActionButtonWidgetState extends ConsumerState<ActionButtonWidget> {
|
||||
} else {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 21, bottom: 21),
|
||||
child: TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.white,
|
||||
)),
|
||||
child: ElevatedButton(
|
||||
// style: ElevatedButton.styleFrom(
|
||||
// // backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
// textStyle: const TextStyle(
|
||||
// // fontSize: 13,
|
||||
// fontWeight: FontWeight.w500,
|
||||
// // color: Colors.white,
|
||||
// )),
|
||||
onPressed: () async {
|
||||
await downloadFileWidget(ref, context, widget.data);
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Text('Add To My Library'),
|
||||
),
|
||||
child: const Text('Add To My Library',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
// color: Colors.white,
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -190,111 +195,33 @@ class _ShowDialog extends ConsumerWidget {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
return AlertDialog(
|
||||
title: const Text("Downloading Book"),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(15.0),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: 255,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
color: Colors.white,
|
||||
Text(title),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
padding: const EdgeInsets.fromLTRB(20, 20, 20, 20),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: Text(
|
||||
"Downloading Book",
|
||||
style: TextStyle(
|
||||
fontSize: 19,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color.fromARGB(255, 54, 54, 54),
|
||||
decoration: TextDecoration.none),
|
||||
LinearProgressIndicator(
|
||||
value: downloadProgress,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black54,
|
||||
decoration: TextDecoration.none),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Text(
|
||||
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: Colors.black26,
|
||||
value: downloadProgress,
|
||||
minHeight: 4,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
actions: <Widget>[
|
||||
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();
|
||||
Navigator.pop(context, 'Cancel');
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(3.0),
|
||||
child: Text('Cancel'),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:openlib/ui/extensions.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
|
||||
// TODO: Redesign this widget
|
||||
class BookInfoCard extends StatelessWidget {
|
||||
const BookInfoCard(
|
||||
{Key? key,
|
||||
@ -22,26 +23,29 @@ class BookInfoCard extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onClick,
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: 120,
|
||||
decoration: BoxDecoration(
|
||||
return Card(
|
||||
shadowColor: Colors.black.withOpacity(0),
|
||||
margin: const EdgeInsets.only(bottom: 20),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
color: Theme.of(context).colorScheme.tertiaryContainer,
|
||||
side: BorderSide(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
width: 1,
|
||||
),
|
||||
margin: const EdgeInsets.only(bottom: 10),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: onClick,
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
CachedNetworkImage(
|
||||
height: 120,
|
||||
width: 90,
|
||||
height: 140,
|
||||
width: 105,
|
||||
imageUrl: thumbnail ?? "",
|
||||
imageBuilder: (context, imageProvider) => Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(5)),
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
image: DecorationImage(
|
||||
image: imageProvider,
|
||||
fit: BoxFit.fill,
|
||||
@ -51,7 +55,7 @@ class BookInfoCard extends StatelessWidget {
|
||||
placeholder: (context, url) => Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
color: "#F8C0C8".toColor(),
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
),
|
||||
height: 120,
|
||||
width: 90,
|
||||
@ -60,7 +64,7 @@ class BookInfoCard extends StatelessWidget {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
color: "#F8C0C8".toColor(),
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
),
|
||||
height: 120,
|
||||
width: 90,
|
||||
@ -72,7 +76,7 @@ class BookInfoCard extends StatelessWidget {
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(5),
|
||||
padding: const EdgeInsets.all(18),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
@ -82,29 +86,33 @@ class BookInfoCard extends StatelessWidget {
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
// color: Colors.black,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
),
|
||||
Text(
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Text(
|
||||
publisher,
|
||||
style: TextStyle(
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: "#4D4D4D".toColor(),
|
||||
fontWeight: FontWeight.w500,
|
||||
// color: "#4D4D4D".toColor(),
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
author,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: "#7B7B7B".toColor(),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
// color: "#7B7B7B".toColor(),
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
|
@ -12,10 +12,10 @@ class BookInfoWidget extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
// physics: const BouncingScrollPhysics(),
|
||||
scrollDirection: Axis.vertical,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 15, right: 15, top: 10),
|
||||
padding: const EdgeInsets.only(left: 20, right: 20, top: 10),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@ -26,8 +26,8 @@ class BookInfoWidget extends StatelessWidget {
|
||||
),
|
||||
Center(
|
||||
child: CachedNetworkImage(
|
||||
height: 230,
|
||||
width: 170,
|
||||
height: 240,
|
||||
width: 180,
|
||||
imageUrl: data.thumbnail,
|
||||
imageBuilder: (context, imageProvider) => Container(
|
||||
decoration: BoxDecoration(
|
||||
@ -43,8 +43,8 @@ class BookInfoWidget extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: Colors.grey,
|
||||
),
|
||||
height: 230,
|
||||
width: 170,
|
||||
height: 240,
|
||||
width: 180,
|
||||
),
|
||||
errorWidget: (context, url, error) {
|
||||
return Container(
|
||||
@ -63,21 +63,22 @@ class BookInfoWidget extends StatelessWidget {
|
||||
),
|
||||
_TopPaddedText(
|
||||
text: data.title,
|
||||
fontSize: 19,
|
||||
fontSize: 22,
|
||||
topPadding: 15,
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.w600,
|
||||
// color: Colors.black,
|
||||
maxLines: 7,
|
||||
),
|
||||
_TopPaddedText(
|
||||
text: data.publisher ?? "unknown",
|
||||
fontSize: 15,
|
||||
fontSize: 17,
|
||||
topPadding: 7,
|
||||
color: "#595E60".toColor(),
|
||||
color: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||
maxLines: 4,
|
||||
),
|
||||
_TopPaddedText(
|
||||
text: data.author ?? "unknown",
|
||||
fontSize: 13,
|
||||
fontSize: 20,
|
||||
topPadding: 7,
|
||||
color: "#7F7F7F".toColor(),
|
||||
maxLines: 3,
|
||||
@ -91,33 +92,41 @@ class BookInfoWidget extends StatelessWidget {
|
||||
),
|
||||
// child slot of page
|
||||
child,
|
||||
Column(
|
||||
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: 16,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 7, bottom: 10),
|
||||
padding: const EdgeInsets.only(top: 15, bottom: 10),
|
||||
child: Text(
|
||||
data.description ?? "",
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: "#6B6B6B".toColor(),
|
||||
letterSpacing: 1.5,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
@ -130,14 +139,16 @@ 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,
|
||||
required this.color,
|
||||
this.color,
|
||||
this.fontWeight = FontWeight.w400,
|
||||
required this.maxLines,
|
||||
Key? key})
|
||||
: super(key: key);
|
||||
@ -150,9 +161,9 @@ class _TopPaddedText extends StatelessWidget {
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontSize: fontSize,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontWeight: fontWeight,
|
||||
color: color,
|
||||
letterSpacing: 0.5,
|
||||
// letterSpacing: 0.5,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: maxLines,
|
||||
|
@ -16,117 +16,28 @@ class ShowDeleteDialog extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
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: Colors.white,
|
||||
),
|
||||
padding: const EdgeInsets.fromLTRB(20, 50, 20, 20),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: Text(
|
||||
"Delete Book",
|
||||
style: TextStyle(
|
||||
fontSize: 19,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color.fromARGB(255, 54, 54, 54),
|
||||
decoration: TextDecoration.none),
|
||||
),
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: Text(
|
||||
"This is permanent and cannot be undone",
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black54,
|
||||
decoration: TextDecoration.none),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
// if (false) {
|
||||
// return
|
||||
// }
|
||||
|
||||
return AlertDialog(
|
||||
title: const Text("Delete Book?"),
|
||||
content: const Text("This is permanent and cannot be undone"),
|
||||
actions: <Widget>[
|
||||
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)));
|
||||
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 Padding(
|
||||
padding: EdgeInsets.all(5.0),
|
||||
child: Text(
|
||||
'Delete',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
child: const Text('Delete'),
|
||||
),
|
||||
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'),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
onPressed: () => Navigator.pop(context, 'Cancel'),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
@ -24,7 +24,7 @@ class FileOpenAndDeleteButtons extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TextButton(
|
||||
FilledButton(
|
||||
style: TextButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
textStyle: const TextStyle(
|
||||
@ -60,7 +60,7 @@ class FileOpenAndDeleteButtons extends StatelessWidget {
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
TextButton(
|
||||
OutlinedButton(
|
||||
style: ButtonStyle(
|
||||
shape: MaterialStateProperty.all(
|
||||
RoundedRectangleBorder(
|
||||
@ -82,14 +82,14 @@ class FileOpenAndDeleteButtons extends StatelessWidget {
|
||||
);
|
||||
});
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(5.3),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(5.3),
|
||||
child: Text(
|
||||
'Delete',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -7,13 +7,13 @@ class TitleText extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 10, bottom: 7),
|
||||
padding: const EdgeInsets.only(left: 10, right: 10, top: 20, bottom: 10),
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontSize: 19,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
style: const TextStyle(
|
||||
fontSize: 25,
|
||||
fontWeight: FontWeight.w500,
|
||||
// color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -5,14 +5,14 @@ void showSnackBar({required BuildContext context, required String message}) {
|
||||
content: Text(
|
||||
message,
|
||||
style: const TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
// 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))),
|
||||
|
@ -3,7 +3,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:epub_view/epub_view.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:openlib/state/state.dart' show filePathProvider;
|
||||
import 'package:openlib/state/state.dart'
|
||||
show filePathProvider, saveEpubState, getBookPosition;
|
||||
|
||||
class EpubViewerWidget extends ConsumerStatefulWidget {
|
||||
const EpubViewerWidget({super.key, required this.fileName});
|
||||
@ -19,22 +20,22 @@ class _EpubViewState extends ConsumerState<EpubViewerWidget> {
|
||||
Widget build(BuildContext context) {
|
||||
final filePath = ref.watch(filePathProvider(widget.fileName));
|
||||
return filePath.when(data: (data) {
|
||||
return EpubViewer(filePath: data);
|
||||
return EpubViewer(filePath: data, fileName: widget.fileName);
|
||||
}, error: (error, stack) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
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(
|
||||
@ -43,24 +44,28 @@ class _EpubViewState extends ConsumerState<EpubViewerWidget> {
|
||||
child: CircularProgressIndicator(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
)),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class EpubViewer extends StatefulWidget {
|
||||
const EpubViewer({Key? key, required this.filePath}) : super(key: key);
|
||||
class EpubViewer extends ConsumerStatefulWidget {
|
||||
const EpubViewer({Key? key, required this.filePath, required this.fileName})
|
||||
: super(key: key);
|
||||
|
||||
final String filePath;
|
||||
final String fileName;
|
||||
|
||||
@override
|
||||
// ignore: library_private_types_in_public_api
|
||||
_EpubViewerState createState() => _EpubViewerState();
|
||||
}
|
||||
|
||||
class _EpubViewerState extends State<EpubViewer> {
|
||||
class _EpubViewerState extends ConsumerState<EpubViewer> {
|
||||
late EpubController _epubReaderController;
|
||||
String? epubConf;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -70,6 +75,14 @@ class _EpubViewerState extends State<EpubViewer> {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void deactivate() {
|
||||
if (Platform.isAndroid || Platform.isIOS) {
|
||||
saveEpubState(widget.fileName, epubConf, ref);
|
||||
}
|
||||
super.deactivate();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_epubReaderController.dispose();
|
||||
@ -78,23 +91,59 @@ class _EpubViewerState extends State<EpubViewer> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final position = ref.watch(getBookPosition(widget.fileName));
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
// backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
title: const Text("Openlib"),
|
||||
titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
titleSpacing: 0,
|
||||
// titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
),
|
||||
endDrawer: Drawer(
|
||||
child: EpubViewTableOfContents(controller: _epubReaderController),
|
||||
),
|
||||
body: EpubView(
|
||||
onDocumentLoaded: (doc) {},
|
||||
onChapterChanged: (value) {},
|
||||
body: position.when(
|
||||
data: (data) {
|
||||
return EpubView(
|
||||
onDocumentLoaded: (doc) {
|
||||
Future.delayed(const Duration(milliseconds: 20), () {
|
||||
String pos = data ?? "";
|
||||
_epubReaderController.gotoEpubCfi(pos);
|
||||
});
|
||||
},
|
||||
onChapterChanged: (value) {
|
||||
epubConf = _epubReaderController.generateEpubCfi();
|
||||
},
|
||||
builders: EpubViewBuilders<DefaultBuilderOptions>(
|
||||
options: const DefaultBuilderOptions(),
|
||||
chapterDividerBuilder: (_) => const Divider(),
|
||||
),
|
||||
controller: _epubReaderController,
|
||||
);
|
||||
},
|
||||
error: (err, _) {
|
||||
return EpubView(
|
||||
onChapterChanged: (value) {
|
||||
epubConf = _epubReaderController.generateEpubCfi();
|
||||
},
|
||||
builders: EpubViewBuilders<DefaultBuilderOptions>(
|
||||
options: const DefaultBuilderOptions(),
|
||||
chapterDividerBuilder: (_) => const Divider(),
|
||||
),
|
||||
controller: _epubReaderController,
|
||||
);
|
||||
},
|
||||
loading: () {
|
||||
return Center(
|
||||
child: SizedBox(
|
||||
width: 25,
|
||||
height: 25,
|
||||
child: CircularProgressIndicator(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -7,17 +7,18 @@ import 'package:openlib/ui/components/book_info_widget.dart';
|
||||
import 'package:openlib/ui/components/file_buttons_widget.dart';
|
||||
|
||||
class BookPage extends StatelessWidget {
|
||||
const BookPage({Key? key, required this.id}) : super(key: key);
|
||||
const BookPage({Key? key, required this.id, required this.title})
|
||||
: super(key: key);
|
||||
|
||||
final String id;
|
||||
final String title;
|
||||
|
||||
@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,
|
||||
title: Text(title),
|
||||
titleSpacing: 0,
|
||||
),
|
||||
body: Consumer(
|
||||
builder: (BuildContext context, WidgetRef ref, _) {
|
||||
@ -42,12 +43,8 @@ class BookPage extends StatelessWidget {
|
||||
);
|
||||
} else {
|
||||
return Center(
|
||||
child: SizedBox(
|
||||
width: 25,
|
||||
height: 25,
|
||||
child: CircularProgressIndicator(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
));
|
||||
}
|
||||
});
|
||||
|
@ -38,7 +38,7 @@ class MyLibraryPage extends ConsumerWidget {
|
||||
onClick: () {
|
||||
Navigator.push(context, MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return BookPage(id: i.id);
|
||||
return BookPage(id: i.id, title: i.title);
|
||||
}));
|
||||
}))
|
||||
.toList()),
|
||||
@ -81,12 +81,8 @@ class MyLibraryPage extends ConsumerWidget {
|
||||
},
|
||||
loading: () {
|
||||
return Center(
|
||||
child: SizedBox(
|
||||
width: 25,
|
||||
height: 25,
|
||||
child: CircularProgressIndicator(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
));
|
||||
},
|
||||
);
|
||||
|
@ -2,7 +2,14 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_pdfview/flutter_pdfview.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:openlib/state/state.dart'
|
||||
show filePathProvider, pdfCurrentPage, totalPdfPage;
|
||||
show
|
||||
filePathProvider,
|
||||
pdfCurrentPage,
|
||||
totalPdfPage,
|
||||
savePdfState,
|
||||
getBookPosition;
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'dart:io' show Platform;
|
||||
|
||||
class PdfView extends ConsumerStatefulWidget {
|
||||
const PdfView({super.key, required this.fileName});
|
||||
@ -18,7 +25,7 @@ class _PdfViewState extends ConsumerState<PdfView> {
|
||||
Widget build(BuildContext context) {
|
||||
final filePath = ref.watch(filePathProvider(widget.fileName));
|
||||
return filePath.when(data: (data) {
|
||||
return PdfViewer(filePath: data);
|
||||
return PdfViewer(filePath: data, fileName: widget.fileName);
|
||||
}, error: (error, stack) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
@ -31,9 +38,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(
|
||||
@ -49,9 +56,11 @@ class _PdfViewState extends ConsumerState<PdfView> {
|
||||
}
|
||||
|
||||
class PdfViewer extends ConsumerStatefulWidget {
|
||||
const PdfViewer({Key? key, required this.filePath}) : super(key: key);
|
||||
const PdfViewer({Key? key, required this.filePath, required this.fileName})
|
||||
: super(key: key);
|
||||
|
||||
final String filePath;
|
||||
final String fileName;
|
||||
|
||||
@override
|
||||
ConsumerState<ConsumerStatefulWidget> createState() => _PdfViewerState();
|
||||
@ -60,20 +69,59 @@ class PdfViewer extends ConsumerStatefulWidget {
|
||||
class _PdfViewerState extends ConsumerState<PdfViewer> {
|
||||
late PDFViewController controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void deactivate() {
|
||||
if (Platform.isAndroid || Platform.isIOS) {
|
||||
savePdfState(widget.fileName, ref);
|
||||
}
|
||||
super.deactivate();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _openPdfWithDefaultViewer(String fileName) async {
|
||||
debugPrint("Opening : $fileName");
|
||||
final fileUrl = Uri.parse(fileName);
|
||||
if (await canLaunchUrl(fileUrl)) {
|
||||
await launchUrl(fileUrl);
|
||||
} else {
|
||||
// ignore: use_build_context_synchronously
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text(
|
||||
'Could not open the PDF',
|
||||
textAlign: TextAlign.center,
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool isMobile = Platform.isAndroid || Platform.isIOS;
|
||||
final currentPage = ref.watch(pdfCurrentPage);
|
||||
final totalPages = ref.watch(totalPdfPage);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
// backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
title: const Text("Openlib"),
|
||||
titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
actions: [
|
||||
titleSpacing: 0,
|
||||
// titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
actions: isMobile
|
||||
? [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
if (currentPage != 0) {
|
||||
ref.read(pdfCurrentPage.notifier).state = currentPage - 1;
|
||||
ref.read(pdfCurrentPage.notifier).state =
|
||||
currentPage - 1;
|
||||
controller.setPage(currentPage - 1);
|
||||
} else {
|
||||
ref.read(pdfCurrentPage.notifier).state = totalPages;
|
||||
@ -84,11 +132,13 @@ class _PdfViewerState extends ConsumerState<PdfViewer> {
|
||||
Icons.arrow_left,
|
||||
size: 25,
|
||||
)),
|
||||
Text('${(currentPage + 1).toString()} / ${totalPages.toString()}'),
|
||||
Text(
|
||||
'${(currentPage + 1).toString()} / ${totalPages.toString()}'),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
if (currentPage + 1 < totalPages) {
|
||||
ref.read(pdfCurrentPage.notifier).state = currentPage + 1;
|
||||
ref.read(pdfCurrentPage.notifier).state =
|
||||
currentPage + 1;
|
||||
controller.setPage(currentPage + 1);
|
||||
} else {
|
||||
ref.read(pdfCurrentPage.notifier).state = 0;
|
||||
@ -99,9 +149,29 @@ class _PdfViewerState extends ConsumerState<PdfViewer> {
|
||||
Icons.arrow_right,
|
||||
size: 25,
|
||||
)),
|
||||
],
|
||||
]
|
||||
: [],
|
||||
),
|
||||
body: PDFView(
|
||||
body: isMobile
|
||||
? ref.watch(getBookPosition(widget.fileName)).when(
|
||||
data: (data) {
|
||||
return PDFView(
|
||||
swipeHorizontal: true,
|
||||
fitEachPage: true,
|
||||
fitPolicy: FitPolicy.BOTH,
|
||||
filePath: widget.filePath,
|
||||
onViewCreated: (controller) {
|
||||
this.controller = controller;
|
||||
},
|
||||
defaultPage: int.parse(data ?? '0'),
|
||||
onPageChanged: (page, total) {
|
||||
ref.read(pdfCurrentPage.notifier).state = page ?? 0;
|
||||
ref.read(totalPdfPage.notifier).state = total ?? 0;
|
||||
},
|
||||
);
|
||||
},
|
||||
error: (error, stackTrace) {
|
||||
return PDFView(
|
||||
swipeHorizontal: true,
|
||||
fitEachPage: true,
|
||||
fitPolicy: FitPolicy.BOTH,
|
||||
@ -113,6 +183,36 @@ class _PdfViewerState extends ConsumerState<PdfViewer> {
|
||||
ref.read(pdfCurrentPage.notifier).state = page ?? 0;
|
||||
ref.read(totalPdfPage.notifier).state = total ?? 0;
|
||||
},
|
||||
);
|
||||
},
|
||||
loading: () {
|
||||
return Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
));
|
||||
},
|
||||
)
|
||||
: Center(
|
||||
child: TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.white,
|
||||
)),
|
||||
onPressed: () async {
|
||||
await _openPdfWithDefaultViewer("file://${widget.filePath}");
|
||||
// ignore: use_build_context_synchronously
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
"Open with System's PDF Viewer",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -23,22 +23,30 @@ class ResultPage extends ConsumerWidget {
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
title: const Text("Openlib"),
|
||||
titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
// backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
title: const Text(
|
||||
"Results",
|
||||
style: TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
titleSpacing: 0,
|
||||
|
||||
// titleTextStyle: Theme.of(context).textTheme.displayLarge,
|
||||
),
|
||||
body: searchBooks.when(
|
||||
data: (data) {
|
||||
if (data.isNotEmpty) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 5, right: 5, top: 10),
|
||||
padding: const EdgeInsets.only(
|
||||
left: 5,
|
||||
right: 5,
|
||||
),
|
||||
child: CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
const SliverToBoxAdapter(
|
||||
child: TitleText("Results"),
|
||||
),
|
||||
// const SliverToBoxAdapter(
|
||||
// child: TitleText("Results"),
|
||||
// ),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.only(left: 5, right: 5, top: 10),
|
||||
padding: const EdgeInsets.only(left: 5, right: 5, top: 15),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildListDelegate(data
|
||||
.map((i) => BookInfoCard(
|
||||
@ -50,7 +58,8 @@ class ResultPage extends ConsumerWidget {
|
||||
onClick: () {
|
||||
Navigator.push(context, MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return BookInfoPage(url: i.link);
|
||||
return BookInfoPage(
|
||||
url: i.link, title: i.title);
|
||||
}));
|
||||
},
|
||||
))
|
||||
@ -103,22 +112,14 @@ 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: SizedBox(
|
||||
width: 25,
|
||||
height: 25,
|
||||
child: CircularProgressIndicator(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
))
|
||||
],
|
||||
),
|
||||
|
@ -39,19 +39,23 @@ 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(
|
||||
enabledBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.black45, width: 2),
|
||||
borderRadius: BorderRadius.all(Radius.circular(50)),
|
||||
label: const Text("Search"),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).colorScheme.outline, width: 2),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(50)),
|
||||
),
|
||||
focusedBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.black54, width: 2),
|
||||
borderRadius: BorderRadius.all(Radius.circular(50)),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).colorScheme.outline, width: 2),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(50)),
|
||||
),
|
||||
suffixIcon: IconButton(
|
||||
padding: const EdgeInsets.only(right: 5),
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
// color: Theme.of(context).colorScheme.secondary,
|
||||
icon: const Icon(
|
||||
Icons.search,
|
||||
size: 23,
|
||||
@ -59,16 +63,16 @@ class SearchPage extends ConsumerWidget {
|
||||
onPressed: () => onSubmit(context, ref),
|
||||
),
|
||||
filled: true,
|
||||
hintStyle: const TextStyle(
|
||||
color: Colors.grey, fontWeight: FontWeight.bold),
|
||||
hintText: "Search",
|
||||
fillColor: Theme.of(context).colorScheme.primary,
|
||||
// hintStyle: const TextStyle(
|
||||
// color: Colors.grey, fontWeight: FontWeight.bold),
|
||||
hintText: "Search books ,author or ISBN",
|
||||
// fillColor: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
onSubmitted: (String value) => onSubmit(context, ref),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12,
|
||||
style: const TextStyle(
|
||||
// color: Theme.of(context).colorScheme.tertiary,
|
||||
// fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
),
|
||||
onChanged: (String value) {
|
||||
ref.read(searchQueryProvider.notifier).state = value;
|
||||
@ -83,17 +87,21 @@ class SearchPage extends ConsumerWidget {
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Type',
|
||||
labelStyle: TextStyle(
|
||||
fontSize: 11,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
enabledBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.black54, width: 2),
|
||||
borderRadius: BorderRadius.all(Radius.circular(50)),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
width: 2),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(50)),
|
||||
),
|
||||
focusedBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.black54, width: 2),
|
||||
borderRadius: BorderRadius.all(Radius.circular(50)),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
width: 2),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(50)),
|
||||
),
|
||||
),
|
||||
icon: const Icon(Icons.arrow_drop_down),
|
||||
@ -105,7 +113,7 @@ class SearchPage extends ConsumerWidget {
|
||||
value: value,
|
||||
child: Text(
|
||||
value,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
@ -123,17 +131,21 @@ class SearchPage extends ConsumerWidget {
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Sort by',
|
||||
labelStyle: TextStyle(
|
||||
fontSize: 11,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
enabledBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.black45, width: 2),
|
||||
borderRadius: BorderRadius.all(Radius.circular(50)),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
width: 2),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(50)),
|
||||
),
|
||||
focusedBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.black54, width: 2),
|
||||
borderRadius: BorderRadius.all(Radius.circular(50)),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
width: 2),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(50)),
|
||||
),
|
||||
),
|
||||
value: dropdownSortValue,
|
||||
@ -144,7 +156,7 @@ class SearchPage extends ConsumerWidget {
|
||||
value: value,
|
||||
child: Text(
|
||||
value,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
@ -153,7 +165,7 @@ class SearchPage extends ConsumerWidget {
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -19,7 +19,7 @@ class TrendingPage extends ConsumerWidget {
|
||||
final trendingBooks = ref.watch(getTrendingBooks);
|
||||
return trendingBooks.when(data: (data) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 5, right: 5, top: 10),
|
||||
padding: const EdgeInsets.only(left: 5, right: 5, top: 0),
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
const SliverToBoxAdapter(
|
||||
@ -37,6 +37,7 @@ class TrendingPage extends ConsumerWidget {
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
return InkWell(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
onTap: () {
|
||||
Navigator.push(context,
|
||||
MaterialPageRoute(builder: (BuildContext context) {
|
||||
@ -45,10 +46,6 @@ class TrendingPage extends ConsumerWidget {
|
||||
);
|
||||
}));
|
||||
},
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
color: const Color.fromARGB(255, 255, 255, 255),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
@ -60,12 +57,6 @@ class TrendingPage extends ConsumerWidget {
|
||||
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(
|
||||
@ -77,7 +68,10 @@ class TrendingPage extends ConsumerWidget {
|
||||
placeholder: (context, url) => Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
color: "#E3E8E9".toColor(),
|
||||
// color: "#E3E8E9".toColor(),
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.surfaceVariant,
|
||||
),
|
||||
height: imageHeight,
|
||||
width: imageWidth,
|
||||
@ -86,7 +80,9 @@ class TrendingPage extends ConsumerWidget {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
color: Colors.grey,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.surfaceVariant,
|
||||
),
|
||||
height: imageHeight,
|
||||
width: imageWidth,
|
||||
@ -97,20 +93,19 @@ class TrendingPage extends ConsumerWidget {
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: SizedBox(
|
||||
width: imageWidth,
|
||||
child: Text(
|
||||
data[index].title!,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.displayMedium,
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w400),
|
||||
maxLines: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
);
|
||||
},
|
||||
childCount: data.length,
|
||||
@ -130,12 +125,12 @@ class TrendingPage extends ConsumerWidget {
|
||||
},
|
||||
);
|
||||
}, loading: () {
|
||||
return Center(
|
||||
return const Center(
|
||||
child: SizedBox(
|
||||
width: 25,
|
||||
height: 25,
|
||||
child: CircularProgressIndicator(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
// color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
));
|
||||
});
|
||||
|
@ -6,6 +6,14 @@
|
||||
|
||||
#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,6 +3,8 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
dynamic_color
|
||||
url_launcher_linux
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
@ -5,10 +5,14 @@
|
||||
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"))
|
||||
}
|
||||
|
95
pubspec.lock
@ -145,6 +145,14 @@ packages:
|
||||
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"
|
||||
epub_view:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -267,6 +275,11 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
gestures:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -544,6 +557,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.0"
|
||||
sqflite_common_ffi:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sqflite_common_ffi
|
||||
sha256: "0d5cc1be2eb18400ac6701c31211d44164393aa75886093002ecdd947be04f93"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0+2"
|
||||
sqlite3:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqlite3
|
||||
sha256: db65233e6b99e99b2548932f55a987961bc06d82a31a0665451fa0b4fff4c3fb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -616,6 +645,70 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
url_launcher:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: url_launcher
|
||||
sha256: "781bd58a1eb16069412365c98597726cd8810ae27435f04b3b4d3a470bacd61e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.12"
|
||||
url_launcher_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "3dd2388cc0c42912eee04434531a26a82512b9cb1827e0214430c9bcbddfe025"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.38"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.4"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_linux
|
||||
sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.5"
|
||||
url_launcher_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_macos
|
||||
sha256: "1c4fdc0bfea61a70792ce97157e5cc17260f61abbe4f39354513f39ec6fd73b1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.6"
|
||||
url_launcher_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_platform_interface
|
||||
sha256: bfdfa402f1f3298637d71ca8ecfe840b4696698213d5346e9d12d4ab647ee2ea
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
url_launcher_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_web
|
||||
sha256: cc26720eefe98c1b71d85f9dc7ef0cada5132617046369d9dc296b3ecaa5cbb4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.18"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_windows
|
||||
sha256: "7967065dd2b5fccc18c653b97958fdf839c5478c28e767c61ee879f4e7882422"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.7"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -690,4 +783,4 @@ packages:
|
||||
version: "3.1.2"
|
||||
sdks:
|
||||
dart: ">=3.0.5 <4.0.0"
|
||||
flutter: ">=3.7.0-0"
|
||||
flutter: ">=3.10.0"
|
||||
|
@ -49,10 +49,14 @@ dependencies:
|
||||
google_fonts: ^5.1.0
|
||||
|
||||
cached_network_image: ^3.2.3
|
||||
|
||||
sqflite_common_ffi: ^2.3.0+2
|
||||
url_launcher: ^6.1.12
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.2
|
||||
dev: ^1.0.0
|
||||
dynamic_color: ^1.6.6
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
@ -6,6 +6,12 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <dynamic_color/dynamic_color_plugin_c_api.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
DynamicColorPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("DynamicColorPluginCApi"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||
}
|
||||
|
@ -3,6 +3,8 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
dynamic_color
|
||||
url_launcher_windows
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|