Skip to content

Commit

Permalink
Added favorites page
Browse files Browse the repository at this point in the history
  • Loading branch information
WeebNetsu committed Oct 22, 2023
1 parent 51dcfbe commit 6f84a4f
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 68 deletions.
1 change: 1 addition & 0 deletions lib/utils/strings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ String capitalizeFirstLetter(String input) {
/// Generate a tag url, `/tag/value/`
String generateTagUrl(String name) => '/tag/$name/';

/// Generates a search query string based on the given parameters.
String generateSearchQueryString(String originalQuery, UserPreferencesModel userPreferences, {nh.Tag? searchTag}) {
String newQuery = originalQuery;

Expand Down
270 changes: 205 additions & 65 deletions lib/views/favorites/favorites_view.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import 'dart:io';
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:nclientv3/models/saved_book.dart';
import 'package:nclientv3/utils/app.dart';
import 'package:nclientv3/constants/constants.dart';
import 'package:nclientv3/models/models.dart';
import 'package:nclientv3/widgets/widgets.dart';

import 'widgets/favorited_book_cover_widget.dart';
import 'package:nhentai/nhentai.dart' as nh;

class FavoritesView extends StatefulWidget {
const FavoritesView({super.key});
Expand All @@ -21,99 +20,240 @@ class FavoritesView extends StatefulWidget {
}

class _FavoritesViewState extends State<FavoritesView> {
List<SavedBookModel> _bookList = [];
bool _loading = true;
final _userPreferences = UserPreferencesModel();
// final ScrollController _scrollController = ScrollController();

Map<String, dynamic>? arguments;
String? _errorMessage;
nh.API? _api;
int _currentPage = 1;

/// if searching for a specific tag
final List<nh.Book> _searchedBooks = [];

/// Similar to _loadingBooks, but does not store a function
bool _loading = false;
// bool _loadingNextPage = false;

late Future<void> _loadingBooks = _searchBooks();

Future<void> _fetchBook(int bookId) async {
if (_api == null) {
_errorMessage = "Did not get the api... Coding bug, gomen!";
return;
}

try {
final book = await _api!.getBook(bookId!);
await _userPreferences.loadDataFromFile();

setState(() {
_searchedBooks.add(book);
});
} on nh.ApiException catch (error) {
setState(() {
_errorMessage = "Oh no, the API said '${error.message}'!";
});
} catch (error) {
print(error);
setState(() {
_errorMessage = "Oh no! something went wrong...";
});
}
}

void reloadData() {
Future<void> _searchBooks({int? selectedBookId, nh.API? api, bool nextPage = false}) async {
setState(() {
_loading = true;
_bookList = [];
_errorMessage = null;
});

getDownloadedBooks().then((value) {
if (api == null && _api == null) {
_errorMessage = "Did not get api... Coding bug, gomen!";
return;
}

try {
setState(() {
if (nextPage) {
// _loadingNextPage = true;
_currentPage += 1;
} else {
_currentPage = 1;
_loading = true;
}
});

if (selectedBookId != null) {
await Navigator.pushNamed(context, "/read", arguments: {"bookId": selectedBookId, "api": _api});
// if user searched for a code, then after they have opened the book,
// they will be redirected back to the page before they made the search,
// so they don't see an empty search page
(() => Navigator.pop(context))();
}

await _userPreferences.loadDataFromFile();
await Future.wait(_userPreferences.favoriteBooks.map((book) => _fetchBook(book)));

setState(() {
// if (!nextPage) _searchedBooks.clear();
_loading = false;
// _loadingNextPage = false;
});
}).catchError((e) {
debugPrint(e);
});
} catch (error) {
setState(() {
_errorMessage = "Oh no! An unknown error occurred, what a tragedy...";
});
// Handle any errors that occur during the stream
debugPrint('Error: $error');
} finally {
setState(() {
_loading = false;
// _loadingNextPage = false;
});
}
}

// void _scrollListener() {
// if (_userPreferences.slowInternetMode) return;

// if (_scrollController.position.atEdge) {
// if (_scrollController.position.pixels == 0) {
// // Reached the top of the scroll view
// } else {
// // Reached the bottom of the scroll view
// setState(() {
// _searchNextPage();
// });
// }
// }
// }

@override
void initState() {
super.initState();

reloadData();
}
_loadingBooks = _searchBooks(api: null);

Future<void> getDownloadedBooks() async {
final appDir = await getAppDir();
if (appDir == null) return;
WidgetsBinding.instance.addPostFrameCallback((_) {
try {
arguments = ModalRoute.of(context)?.settings.arguments as Map<String, dynamic>;
final nh.API? api = arguments?['api'];

for (var fileEntity in appDir.listSync()) {
if (fileEntity is Directory) {
final code = int.tryParse(fileEntity.path.split("/").last);
if (code == null) continue;
_api = api;
_loadingBooks = _searchBooks(api: api);
} catch (e) {
_errorMessage = "Could not get API... Coding bug, gomen!";
}
});

final book = SavedBookModel(code);
await book.loadBookData();
// _scrollController.addListener(_scrollListener);
}

_bookList.add(book);
}
}
@override
void dispose() {
// _scrollController.removeListener(_scrollListener);
// _scrollController.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
if (_loading) {
return const Scaffold(
body: Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Column(
children: [
PageTitleDisplay(title: "Favorites"),
Center(child: CircularProgressIndicator()),
],
),
if (_api == null) {
return const Center(
child: CircularProgressIndicator(),
);
}

if (_errorMessage != null) {
return Scaffold(
body: Stack(
children: [
MessagePageWidget(text: _errorMessage!),
],
),
);
}

return Scaffold(
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: SingleChildScrollView(
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.only(bottom: 20),
child: Column(
children: [
const PageTitleDisplay(title: "Downloads"),
ListView.builder(
shrinkWrap: true, // Allow the ListView to take only the space it needs
physics: const NeverScrollableScrollPhysics(), // Disable scrolling for the ListView
itemCount: _bookList.length,
itemBuilder: (BuildContext context, int index) {
if (index % 2 == 0) {
// Create a new row after every 2nd item
return Row(
children: [
DownloadedBookCoverWidget(
book: _bookList[index],
lastBookFullWidth: index == _bookList.length - 1,
reloadPage: reloadData,
),
if (index + 1 < _bookList.length)
DownloadedBookCoverWidget(
book: _bookList[index + 1],
reloadPage: reloadData,
const PageTitleDisplay(
title: "Favorites",
removeBottomPadding: true,
),
FutureBuilder<void>(
future: _loadingBooks,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
// Display a loader while the future is executing
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
debugPrint("Error occurred: ${snapshot.error}");
// Handle any error that occurred during the future execution
return const MessagePageWidget(text: "Could not fetch the books, I am broken!");
}

if (_searchedBooks.isEmpty) {
return const Scaffold(
body: Stack(
children: [
MessagePageWidget(
text: "It's all empty here! No books were found.",
statusEmoji: StatusEmojis.thinking,
),
],
],
),
);
} else {
// Skip rendering for odd-indexed items
return Row(children: [Container()]);
}

if (_loading) {
return const Center(child: CircularProgressIndicator());
}

return Stack(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Column(
children: [
ListView.builder(
shrinkWrap: true, // Allow the ListView to take only the space it needs
physics: const NeverScrollableScrollPhysics(), // Disable scrolling for the ListView
itemCount: _searchedBooks.length,
itemBuilder: (BuildContext context, int index) {
if (index % 2 == 0) {
// Create a new row after every 2nd item
return Row(
children: [
BookCoverWidget(
book: _searchedBooks[index],
api: _api!,
lastBookFullWidth: index == _searchedBooks.length - 1,
userPreferences: _userPreferences,
),
if (index + 1 < _searchedBooks.length)
BookCoverWidget(
book: _searchedBooks[index + 1],
api: _api!,
userPreferences: _userPreferences,
),
],
);
}
// Skip rendering for odd-indexed items
return Row(children: [Container()]);
},
),
],
),
),
],
);
},
),
const SizedBox(height: 20),
],
),
),
Expand Down
4 changes: 3 additions & 1 deletion lib/widgets/bottom_search_bar_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,9 @@ class _BottomSearchBarWidget extends State<BottomSearchBarWidget> {
IconButton(
icon: const Icon(Icons.star),
onPressed: () {
Navigator.pushNamed(context, "/favorites");
Navigator.pushNamed(context, "/favorites", arguments: {
"api": widget._api,
});
},
),
PopupMenuButton(
Expand Down
4 changes: 3 additions & 1 deletion lib/widgets/page_title_display.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ class PageTitleDisplay extends StatelessWidget {
const PageTitleDisplay({
super.key,
required this.title,
this.removeBottomPadding = false,
});

final String title;
final bool removeBottomPadding;

@override
Widget build(BuildContext context) {
Expand All @@ -28,7 +30,7 @@ class PageTitleDisplay extends StatelessWidget {
),
),
]),
const SizedBox(height: 20),
SizedBox(height: removeBottomPadding ? 0 : 20),
],
);
}
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: New and improved nHentai browsing app!
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: "none" # Remove this line if you wish to publish to pub.dev

version: 0.8.2+4
version: 0.8.3+0

environment:
sdk: ">=3.0.5 <4.0.0"
Expand Down

0 comments on commit 6f84a4f

Please sign in to comment.