Skip to content

Commit

Permalink
feat(file): add file picker
Browse files Browse the repository at this point in the history
  • Loading branch information
PiotrFLEURY committed Mar 10, 2024
1 parent b3f502e commit 3d44c8f
Show file tree
Hide file tree
Showing 19 changed files with 417 additions and 68 deletions.
Empty file removed assets/books/.gitkeep
Empty file.
4 changes: 2 additions & 2 deletions lib/data/book_library.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
class BookLibrary {
const BookLibrary({
required this.bookFileNames,
required this.bookPathList,
});

final List<String> bookFileNames;
final List<String> bookPathList;
}
1 change: 1 addition & 0 deletions lib/data/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:flutter/widgets.dart';

const sharedPreferencesFontSizeKey = 'fontSize';
const sharedPreferencesThemeKey = 'theme';
const sharedPreferencesLibraryKey = 'library';

bool get isMobile => Platform.isAndroid || Platform.isIOS;

Expand Down
10 changes: 5 additions & 5 deletions lib/presentation/book/book_cover.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import 'dart:typed_data';

import 'package:epubx/epubx.dart' show EpubBook;
import 'package:flipub/providers/epub/cover_provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:image/image.dart' as image;

class BookCover extends StatelessWidget {
class BookCover extends ConsumerWidget {
const BookCover({
super.key,
required this.book,
Expand All @@ -13,12 +15,10 @@ class BookCover extends StatelessWidget {
final EpubBook book;

@override
Widget build(BuildContext context) {
Widget build(BuildContext context, WidgetRef ref) {
return book.CoverImage != null
? Image.memory(
Uint8List.fromList(
image.encodePng(book.CoverImage!),
),
ref.read(bookCoverProvider(book.CoverImage!)),
width: 200,
height: 200,
)
Expand Down
6 changes: 3 additions & 3 deletions lib/presentation/book/book_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class BookView extends ConsumerWidget {
final String bookFileName;
final String bookPath;

const BookView({
super.key,
required this.bookFileName,
required this.bookPath,
});

@override
Widget build(BuildContext context, WidgetRef ref) {
final AsyncValue<EpubBook> book = ref.watch(bookProvider(bookFileName));
final AsyncValue<EpubBook> book = ref.watch(bookProvider(bookPath));
return book.when(
data: (EpubBook book) {
return _BookViewContent(
Expand Down
140 changes: 110 additions & 30 deletions lib/presentation/library/library_view.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import 'dart:io';

import 'package:epubx/epubx.dart' show EpubBook;
import 'package:file_selector/file_selector.dart';
import 'package:flipub/data/book_library.dart';
import 'package:flipub/data/constants.dart';
import 'package:flipub/providers/epub/book_provider.dart';
import 'package:flipub/providers/epub/library_provider.dart';
import 'package:flipub/providers/preferences/preferences_provider.dart';
import 'package:flipub/providers/theme/theme_provider.dart';
import 'package:flipub/presentation/book/book_cover.dart';
import 'package:flipub/presentation/book/book_view.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';

class LibrayView extends ConsumerWidget {
const LibrayView({super.key});
Expand Down Expand Up @@ -56,38 +60,66 @@ class _LibraryViewContent extends ConsumerWidget {
ref.read(themeNotifierProvider.notifier).toggle();
},
),
IconButton(
icon: const Icon(Icons.file_open_outlined),
onPressed: () => _pickFile(ref),
),
],
),
body: Padding(
padding: const EdgeInsets.all(48.0),
child: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: Platform.isAndroid || Platform.isIOS ? 2 : 4,
crossAxisCount: MediaQuery.of(context).isPortrait ? 2 : 4,
),
itemCount: library.bookFileNames.length,
itemCount: library.bookPathList.length,
itemBuilder: (context, index) {
return LibraryTile(
bookFileName: library.bookFileNames[index],
bookPath: library.bookPathList[index],
);
},
),
),
);
}

void _pickFile(WidgetRef ref) async {
const XTypeGroup epubTypeGroup = XTypeGroup(
label: 'epub',
extensions: <String>['epub'],
);
final files = await openFiles(
acceptedTypeGroups: <XTypeGroup>[
epubTypeGroup,
],
);
if (files.isNotEmpty) {
final path = files.single.path;
final SharedPreferences preferences =
await ref.read(preferencesProvider.future);
final library =
preferences.getStringList(sharedPreferencesLibraryKey) ?? [];
if (!library.contains(path)) {
library.add(path);
preferences.setStringList(sharedPreferencesLibraryKey, library);
ref.invalidate(bookLibraryProvider);
}
}
}
}

class LibraryTile extends ConsumerWidget {
const LibraryTile({super.key, required this.bookFileName});
const LibraryTile({super.key, required this.bookPath});

final String bookFileName;
final String bookPath;

@override
Widget build(BuildContext context, WidgetRef ref) {
final book = ref.watch(bookProvider(bookFileName));
final book = ref.watch(bookProvider(bookPath));
return book.when(
data: (book) {
return _LibraryTileContent(
bookFileName: bookFileName,
bookPath: bookPath,
book: book,
);
},
Expand All @@ -103,42 +135,90 @@ class LibraryTile extends ConsumerWidget {
}
}

class _LibraryTileContent extends StatelessWidget {
final String bookFileName;
class _LibraryTileContent extends ConsumerStatefulWidget {
final String bookPath;
final EpubBook book;

const _LibraryTileContent({
required this.bookFileName,
required this.bookPath,
required this.book,
});

@override
_LibraryTileContentState createState() => _LibraryTileContentState();
}

class _LibraryTileContentState extends ConsumerState<_LibraryTileContent> {
bool _isHovering = false;

void _onMouseEnter() {
setState(() {
_isHovering = true;
});
}

void _onMouseExit() {
setState(() {
_isHovering = false;
});
}

@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BookView(
bookFileName: bookFileName,
return MouseRegion(
onEnter: (_) => _onMouseEnter(),
onExit: (_) => _onMouseExit(),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BookView(
bookPath: widget.bookPath,
),
),
);
},
child: FittedBox(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_isHovering || isMobile
? InkWell(
onTap: () => _removeBook(ref, widget.bookPath),
child: CircleAvatar(
radius: 16,
backgroundColor:
Theme.of(context).brightness == Brightness.dark
? Colors.grey[800]
: Colors.grey[400],
child: const Icon(Icons.close),
),
)
: const SizedBox(
height: 32,
),
BookCover(book: widget.book),
Text(
widget.book.Title ?? 'Unknown title',
),
],
),
);
},
child: FittedBox(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
BookCover(book: book),
Text(
book.Title ?? 'Unknown title',
),
],
),
),
),
);
}

void _removeBook(WidgetRef ref, String bookPath) async {
final SharedPreferences preferences =
await ref.read(preferencesProvider.future);
final library =
preferences.getStringList(sharedPreferencesLibraryKey) ?? [];
library.remove(bookPath);
preferences.setStringList(sharedPreferencesLibraryKey, library);
ref.invalidate(bookLibraryProvider);
}
}
15 changes: 11 additions & 4 deletions lib/providers/epub/book_provider.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:io';

import 'package:epubx/epubx.dart';
import 'package:flutter/services.dart';
Expand All @@ -7,9 +8,15 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'book_provider.g.dart';

@riverpod
Future<EpubBook> book(BookRef ref, String fileName) async {
String fullPath = 'assets/books/$fileName';
ByteData byteData = await rootBundle.load(fullPath);
List<int> bytes = byteData.buffer.asUint8List();
Future<EpubBook> book(BookRef ref, String path) async {
// load local file
final EpubBook epubBook = await _loadEpubBook(path);
return epubBook;
}

Future<EpubBook> _loadEpubBook(String path) {
// load local file
final File file = File(path);
final Uint8List bytes = file.readAsBytesSync();
return EpubReader.readBook(bytes);
}
Loading

0 comments on commit 3d44c8f

Please sign in to comment.