Unverified Commit 7bcbe2db authored by Ilham Maulana Pratama's avatar Ilham Maulana Pratama Committed by GitHub

Merge pull request #4 from impfundev/development

Development
parents 6d383880 a80e1707
......@@ -3,6 +3,7 @@ import 'package:flutter/gestures.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:library_app/src/screens/profile_edit_screen.dart';
import 'package:provider/provider.dart';
import 'package:library_app/src/providers/auth_provider.dart';
import 'package:library_app/src/providers/navigations_provider.dart';
......@@ -54,7 +55,11 @@ class _LibraryApp extends State<LibraryApp> {
GoRoute(
path: "/change-password",
builder: (context, state) => const ChangePasswordScreen(),
)
),
GoRoute(
path: "/profile-edit",
builder: (context, state) => const ProfileEditScreen(),
),
],
);
......
......@@ -17,16 +17,4 @@ class Loan {
this.remainingDays,
this.isOverdue,
);
factory Loan.fromJson(Map<String, dynamic> data) {
final book = Book.fromJson(data["book_detail"]);
return Loan(
book,
null,
data["loan_date"],
data["due_date"],
data["remaining_loan_time"],
data["is_overdue"],
);
}
}
class User {
int id;
int accountId;
String username;
String email;
String? firstName;
String? lastName;
bool isStaff;
User(this.id, this.accountId, this.username, this.email, this.firstName,
this.lastName, this.isStaff);
User(this.id, this.username, this.email, this.firstName, this.lastName,
this.isStaff);
factory User.fromJson(Map<String, dynamic> data) {
return User(
data['id'] as int,
data['account_id'] as int,
data['username'] as String,
data['email'] as String,
data['first_name'] as String?,
......@@ -36,7 +34,6 @@ class User {
final User initialUser = User(
1,
2,
"test_user",
"test@email.com",
"Test",
......
This diff is collapsed.
......@@ -12,6 +12,11 @@ class BookProvider with ChangeNotifier {
String? searchKeyword;
String? filterByCategory;
bool hasNextPage = false;
bool hasPrevPage = false;
int pageNumber = 1;
int? totalPages;
bool isLoading = false;
void setLoading(bool value) {
......@@ -23,9 +28,11 @@ class BookProvider with ChangeNotifier {
setLoading(true);
String url = '$baseUrl/books';
if (filterByCategory != null) {
url += '?category__name=$filterByCategory';
url += '?category=$filterByCategory';
} else if (searchKeyword != null) {
url += "?search=$searchKeyword";
} else if (pageNumber > 1) {
url += "?page=$pageNumber";
}
final response = await http.get(
......@@ -35,7 +42,11 @@ class BookProvider with ChangeNotifier {
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
books = data["results"];
books = data["data"];
hasNextPage = data["has_next"];
hasPrevPage = data["has_prev"];
pageNumber = data["page_number"];
totalPages = data["total_pages"];
} else {
final code = response.statusCode;
debugPrint("Error: Fetch books failed, $code");
......@@ -58,8 +69,14 @@ class BookProvider with ChangeNotifier {
notifyListeners();
}
void setPage(int value) {
pageNumber = value;
notifyListeners();
}
Future<void> getCategories() async {
try {
setLoading(true);
final response = await http.get(
Uri.parse('$baseUrl/categories'),
headers: {'Content-Type': 'application/json'},
......@@ -67,11 +84,12 @@ class BookProvider with ChangeNotifier {
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
categories = data["results"];
categories = data;
} else {
debugPrint("Error: Fetch books failed, ${response.statusCode}");
}
setLoading(false);
notifyListeners();
} catch (error) {
debugPrint("Error: Fetch books failed, $error");
......
......@@ -41,7 +41,9 @@ class _FormScreen extends State<FormScreen> {
title: Text(title),
leading: withBack
? BackButton(
onPressed: () => context.push(backRoute ?? ""),
onPressed: () => backRoute != null
? context.push(backRoute!)
: context.pop(),
)
: null,
actions: action,
......@@ -99,7 +101,7 @@ class ChangePasswordScreen extends StatelessWidget {
return FormScreen(
title: title,
backRoute: "/",
backRoute: null,
body: const ChangePasswordForm(),
);
}
......
import 'package:flutter/material.dart';
import 'package:library_app/src/providers/auth_provider.dart';
import 'package:library_app/src/providers/navigations_provider.dart';
import 'package:library_app/src/screens/list/admin_list_screen.dart';
import 'package:library_app/src/screens/list/member_list_screen.dart';
import 'package:provider/provider.dart';
......@@ -12,6 +13,17 @@ class ListScreen extends StatefulWidget {
}
class _ListScreen extends State<ListScreen> {
@override
void initState() {
Future.delayed(
Duration.zero,
() {
Provider.of<NavigationsProvider>(context, listen: false).navigate(0);
},
);
super.initState();
}
@override
Widget build(BuildContext context) {
final user = Provider.of<AuthProvider>(context).user;
......
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:library_app/src/models/category.dart';
import 'package:library_app/src/providers/auth_provider.dart';
import 'package:library_app/src/providers/book_provider.dart';
......@@ -73,17 +74,37 @@ class _MemberListScreen extends State<MemberListScreen> {
const BookList(),
// Loans
LoanList(
memberId: authProvider.user?.accountId ?? 0,
memberId: authProvider.user?.id ?? 0,
),
// Profile
const Profile(),
][navProvider.currentPageIndex],
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: List.generate(
category != null ? category.length : 0,
(index) {
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10.0, vertical: 20.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
ElevatedButton(
child: const Row(
children: [Icon(Icons.arrow_back), Text("Back")],
),
onPressed: () {
bookProvider.filterBookByCategory(null);
bookProvider.getBooks();
context.pop();
},
),
],
),
),
Expanded(
child: ListView.builder(
itemCount: category != null ? category.length : 0,
itemBuilder: (context, index) {
if (category != null) {
return ListTile(
title: Text(category.elementAt(index).name),
......@@ -99,6 +120,8 @@ class _MemberListScreen extends State<MemberListScreen> {
},
),
),
],
),
),
);
},
......
......@@ -21,15 +21,58 @@ class _BookList extends State<BookList> {
Provider.of<BookProvider>(context, listen: false).getBooks();
}
@override
void dispose() {
super.dispose();
}
ScrollController listScrollController = ScrollController();
void scrollToTop() {
if (listScrollController.hasClients) {
final position = listScrollController.position.minScrollExtent;
listScrollController.jumpTo(position);
}
}
Future<void> nextPage() async {
if (Provider.of<BookProvider>(context, listen: false).hasNextPage) {
Provider.of<BookProvider>(context, listen: false).setPage(
Provider.of<BookProvider>(context, listen: false).pageNumber + 1,
);
} else {
Provider.of<BookProvider>(context, listen: false).setPage(
Provider.of<BookProvider>(context, listen: false).totalPages!);
}
Provider.of<BookProvider>(context, listen: false).getBooks();
scrollToTop();
}
Future<void> prevPage() async {
if (Provider.of<BookProvider>(context, listen: false).hasPrevPage) {
Provider.of<BookProvider>(context, listen: false).setPage(
Provider.of<BookProvider>(context, listen: false).pageNumber - 1,
);
} else {
Provider.of<BookProvider>(context, listen: false).setPage(1);
}
Provider.of<BookProvider>(context, listen: false).getBooks();
scrollToTop();
}
@override
Widget build(BuildContext context) {
return Consumer<BookProvider>(
builder: (context, bookProvider, child) {
if (!bookProvider.isLoading) {
if (bookProvider.books == null) {
return const Center(
child: CircularProgressIndicator(),
);
}
final Iterable<Book> books = bookProvider.books!.map((book) {
if (book["category_detail"] != null) {
if (book["category"] != null) {
final Category category = Category.fromJson(
book["category_detail"],
book["category"],
);
return Book(
book["id"],
......@@ -52,29 +95,37 @@ class _BookList extends State<BookList> {
});
return NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return [const TopAppBar(title: "Books")];
},
body: ListView(
children: List.generate(books.length, (index) {
return BookItem(
books.elementAt(index),
);
}),
body: ListView.builder(
controller: listScrollController,
itemCount: books.length + 1,
itemBuilder: (context, index) {
if (index < books.length) {
return BookItem(books.elementAt(index));
} else {
return Container(
padding: const EdgeInsets.all(10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ElevatedButton(
onPressed: prevPage,
child: const Text('Prev'),
),
Text(bookProvider.pageNumber.toString()),
ElevatedButton(
onPressed: nextPage,
child: const Text('Next'),
),
],
),
);
} else {
return NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return [const TopAppBar(title: "Books")];
}
},
body: const Center(
child: CircularProgressIndicator(),
),
);
}
},
);
}
......@@ -127,14 +178,9 @@ class _TopAppBar extends State<TopAppBar> {
leading: !showWidget
? IconButton(
onPressed: () {
if (category != null) {
bookProvider.filterBookByCategory(null);
bookProvider.getBooks();
} else {
Scaffold.of(context).openDrawer();
}
},
icon: Icon(category != null ? Icons.arrow_back : Icons.menu),
icon: const Icon(Icons.menu),
)
: null,
elevation: 10.0,
......
......@@ -91,7 +91,7 @@ class _LoanBookForm extends State<LoanBookForm> {
if (_formKey.currentState!.validate()) {}
authProvider
.createMemberLoan(
authProvider.user!.accountId,
authProvider.user!.id,
widget.bookId,
int.parse(loanDayController.text),
)
......
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart';
import 'package:library_app/src/providers/auth_provider.dart';
import 'package:library_app/src/widgets/loading.dart';
import 'package:provider/provider.dart';
......@@ -15,7 +14,8 @@ class ChangePasswordForm extends StatefulWidget {
class _ChangePasswordForm extends State<ChangePasswordForm> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final oldPasswordController = TextEditingController();
final newPasswordController = TextEditingController();
final newPasswordController1 = TextEditingController();
final newPasswordController2 = TextEditingController();
bool passwordVisible = false;
......@@ -28,7 +28,8 @@ class _ChangePasswordForm extends State<ChangePasswordForm> {
@override
void dispose() {
oldPasswordController.dispose();
newPasswordController.dispose();
newPasswordController1.dispose();
newPasswordController2.dispose();
super.dispose();
}
......@@ -37,6 +38,7 @@ class _ChangePasswordForm extends State<ChangePasswordForm> {
Size screenSize = MediaQuery.of(context).size;
return Consumer<AuthProvider>(builder: (context, authProvider, child) {
final message = authProvider.message;
return Column(
children: [
Padding(
......@@ -79,7 +81,7 @@ class _ChangePasswordForm extends State<ChangePasswordForm> {
),
),
validator: (String? value) {
if (value == null || value.isEmpty) {
if (value == null) {
return "Please enter your old password";
} else {
return null;
......@@ -88,7 +90,7 @@ class _ChangePasswordForm extends State<ChangePasswordForm> {
keyboardType: TextInputType.visiblePassword,
),
TextFormField(
controller: newPasswordController,
controller: newPasswordController1,
obscureText: passwordVisible,
decoration: InputDecoration(
hintText: "Enter your New Password",
......@@ -107,7 +109,7 @@ class _ChangePasswordForm extends State<ChangePasswordForm> {
),
),
validator: (String? value) {
if (value == null || value.isEmpty) {
if (value == null) {
return "Please enter your new password";
} else {
return null;
......@@ -115,11 +117,27 @@ class _ChangePasswordForm extends State<ChangePasswordForm> {
},
keyboardType: TextInputType.visiblePassword,
),
TextFormField(
controller: newPasswordController2,
decoration: const InputDecoration(
hintText: "Confirm your New Password",
labelText: "Confirm New Password",
),
validator: (String? value) {
if (value == null) {
return "Please enter your confirm new password";
} else {
return null;
}
},
),
Text(
message ?? "",
style: const TextStyle(color: Colors.red),
),
const SizedBox(
height: 20.0,
),
Column(
children: [
SizedBox(
width: double.infinity,
child: FilledButton(
......@@ -127,9 +145,9 @@ class _ChangePasswordForm extends State<ChangePasswordForm> {
if (_formKey.currentState!.validate()) {}
authProvider.changePassword(
context,
authProvider.user!.accountId,
oldPasswordController.text,
newPasswordController.text,
newPasswordController1.text,
newPasswordController2.text,
);
},
child: authProvider.isLoading
......@@ -137,15 +155,6 @@ class _ChangePasswordForm extends State<ChangePasswordForm> {
: const Text("Submit"),
),
),
SizedBox(
width: double.infinity,
child: TextButton(
child: const Text("Cancel"),
onPressed: () => context.pop("/"),
),
),
],
),
],
),
),
......
......@@ -39,7 +39,8 @@ class _LoginForm extends State<LoginForm> {
const String formText = "Log In to continue";
return Consumer<AuthProvider>(builder: (context, authProvider, child) {
final isInvalid = authProvider.invalidUsernameOrPassword;
final isInvalidUsernameOrPassword =
authProvider.invalidUsernameOrPassword;
return Column(
children: [
......@@ -106,28 +107,21 @@ class _LoginForm extends State<LoginForm> {
},
keyboardType: TextInputType.visiblePassword,
),
Visibility(
visible: isInvalid,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Theme.of(context).highlightColor,
),
margin: const EdgeInsets.symmetric(vertical: 20.0),
padding: const EdgeInsets.symmetric(
horizontal: 10.0, vertical: 8.0),
child: const Text("Invalid username or password"),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 20.0),
child: Text(
isInvalidUsernameOrPassword
? "Invalid username or password"
: "",
style: const TextStyle(color: Colors.red),
),
const SizedBox(
height: 20.0,
),
Column(
children: [
SizedBox(
width: double.infinity,
child: FilledButton(
onPressed: () {
onPressed: () async {
if (_formKey.currentState!.validate()) {}
authProvider.signIn(
context,
......
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:library_app/src/providers/auth_provider.dart';
import 'package:library_app/src/models/user.dart';
......@@ -43,6 +42,7 @@ class _ProfileEditForm extends State<ProfileEditForm> {
lastNameControler.text = user?.lastName ?? "";
return Consumer<AuthProvider>(builder: (context, authProvider, child) {
final message = authProvider.message;
return Column(
children: [
Form(
......@@ -62,6 +62,7 @@ class _ProfileEditForm extends State<ProfileEditForm> {
if (value == null || value.isEmpty) {
return "Please enter your username";
}
return null;
},
),
......@@ -75,6 +76,7 @@ class _ProfileEditForm extends State<ProfileEditForm> {
if (value == null || value.isEmpty) {
return "Please enter your email";
}
return null;
},
),
......@@ -92,6 +94,10 @@ class _ProfileEditForm extends State<ProfileEditForm> {
labelText: "Last Name",
),
),
Text(
message ?? "",
style: const TextStyle(color: Colors.red),
),
Padding(
padding: const EdgeInsets.only(top: 40.0),
child: SizedBox(
......@@ -99,33 +105,20 @@ class _ProfileEditForm extends State<ProfileEditForm> {
child: FilledButton(
onPressed: () {
if (_formKey.currentState!.validate()) {}
authProvider
.updateUserDetail(
authProvider.updateUserDetail(
context,
authProvider.user!.id,
usernameControler.text,
emailControler.text,
firstNameControler.text,
lastNameControler.text,
authProvider.user!.isStaff,
)
.then(
(res) => Navigator.pop(context),
);
},
child: const Text("Submit"),
),
),
),
Padding(
padding: const EdgeInsets.only(top: 10.0),
child: SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () => context.go("/change-password"),
child: const Text("Change Password"),
),
),
)
],
),
),
......
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:library_app/src/providers/auth_provider.dart';
import 'package:library_app/src/screens/form_screen.dart';
import 'package:library_app/src/widgets/loading.dart';
import 'package:provider/provider.dart';
......@@ -22,6 +22,8 @@ class _ResetPasswordForm extends State<ResetPasswordForm> {
const String formText = "Confirm your email to continue reset password";
return Consumer<AuthProvider>(builder: (context, authProvider, child) {
final message = authProvider.message;
return Column(
children: [
Container(
......@@ -67,13 +69,14 @@ class _ResetPasswordForm extends State<ResetPasswordForm> {
validator: (String? value) {
if (value == null || value.isEmpty) {
return "Please enter your email";
} else if (!value.contains("@")) {
return "Email should include '@'";
}
return null;
},
),
// Flutter, iwant to go to ConfirmResetPasswordScreen after authProvider.resetPassword succeed with response 200
Text(
message ?? "",
style: const TextStyle(color: Colors.red),
),
Container(
padding: const EdgeInsets.symmetric(
vertical: 20.0,
......@@ -85,22 +88,8 @@ class _ResetPasswordForm extends State<ResetPasswordForm> {
child: FilledButton(
onPressed: () {
if (_formKey.currentState!.validate()) {}
authProvider
.resetPassword(emailController.text)
.then(
(response) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>
const ConfirmResetPasswordScreen(),
),
);
},
).catchError(
(error) {
debugPrint('Exception: $error');
},
);
authProvider.resetPassword(
context, emailController.text);
},
child: authProvider.isLoading
? const Loading()
......@@ -142,12 +131,7 @@ class _ConfirmResetPasswordForm extends State<ConfirmResetPasswordForm> {
const String formText = "Enter the pin that we just sent to your email";
return Consumer<AuthProvider>(builder: (context, authProvider, child) {
if (authProvider.isLoading) {
return const Center(
child: CircularProgressIndicator(),
);
}
final message = authProvider.message;
return Column(
children: [
Container(
......@@ -190,6 +174,17 @@ class _ConfirmResetPasswordForm extends State<ConfirmResetPasswordForm> {
labelText: "confirmation pin",
suffixIcon: Icon(Icons.password),
),
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
validator: (String? value) {
if (value == null) {
return "Please enter your pin";
} else if (value is int) {
return "Please enter pin in number";
}
return null;
},
),
TextFormField(
controller: password1Controller,
......@@ -241,12 +236,16 @@ class _ConfirmResetPasswordForm extends State<ConfirmResetPasswordForm> {
validator: (String? value) {
if (value == null || value.isEmpty) {
return "Please enter your password";
} else {
return null;
}
return null;
},
keyboardType: TextInputType.visiblePassword,
),
Text(
message ?? "",
style: const TextStyle(color: Colors.red),
),
Container(
padding: const EdgeInsets.symmetric(
vertical: 20.0,
......@@ -258,28 +257,16 @@ class _ConfirmResetPasswordForm extends State<ConfirmResetPasswordForm> {
child: FilledButton(
onPressed: () {
if (_formKey.currentState!.validate()) {}
authProvider
.confirmResetPassword(
authProvider.confirmResetPassword(
context,
int.parse(pinController.text),
password1Controller.text,
password2Controller.text,
)
.then(
(response) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>
const LoginScreen(),
),
);
},
).catchError(
(error) {
debugPrint('Exception: $error');
},
);
},
child: const Text("Submit"),
child: authProvider.isLoading
? const Loading()
: const Text("Submit"),
),
),
],
......
......@@ -24,9 +24,10 @@ class _SearchForm extends State<SearchForm> {
width: queryData.size.width * 0.8,
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: SearchBar(
hintText: "Enter keywords...",
hintText: "Enter book title...",
elevation: WidgetStateProperty.all(0),
onChanged: (value) {
if (value.length >= 3) {
Future.delayed(
Duration.zero,
() {
......@@ -35,6 +36,7 @@ class _SearchForm extends State<SearchForm> {
Provider.of<BookProvider>(context, listen: false).getBooks();
},
);
}
},
leading: const Icon(Icons.search),
),
......
......@@ -40,6 +40,8 @@ class _SignUpForm extends State<SignUpForm> {
const String formText = "Sign In to get started";
return Consumer<AuthProvider>(builder: (context, authProvider, child) {
final message = authProvider.message;
return Column(
children: [
Padding(
......@@ -120,8 +122,10 @@ class _SignUpForm extends State<SignUpForm> {
},
keyboardType: TextInputType.visiblePassword,
),
const SizedBox(
height: 20.0,
Padding(
padding: const EdgeInsets.symmetric(vertical: 20.0),
child: Text(message ?? "",
style: const TextStyle(color: Colors.red)),
),
Column(
children: [
......
......@@ -36,6 +36,40 @@ class _AdminLoanList extends State<AdminLoanList> {
super.initState();
}
ScrollController listScrollController = ScrollController();
void scrollToTop() {
if (listScrollController.hasClients) {
final position = listScrollController.position.minScrollExtent;
listScrollController.jumpTo(position);
}
}
Future<void> nextPage() async {
if (Provider.of<AuthProvider>(context, listen: false).hasNextPage) {
Provider.of<AuthProvider>(context, listen: false).setPage(
Provider.of<AuthProvider>(context, listen: false).pageNumber + 1,
);
} else {
Provider.of<AuthProvider>(context, listen: false).setPage(
Provider.of<AuthProvider>(context, listen: false).totalPages!);
}
Provider.of<AuthProvider>(context, listen: false).getMemberLoan();
scrollToTop();
}
Future<void> prevPage() async {
if (Provider.of<AuthProvider>(context, listen: false).hasPrevPage) {
Provider.of<AuthProvider>(context, listen: false).setPage(
Provider.of<AuthProvider>(context, listen: false).pageNumber - 1,
);
} else {
Provider.of<AuthProvider>(context, listen: false).setPage(1);
}
Provider.of<AuthProvider>(context, listen: false).getMemberLoan();
scrollToTop();
}
@override
Widget build(BuildContext context) {
return Consumer<AuthProvider>(builder: (context, loanProvider, child) {
......@@ -45,18 +79,8 @@ class _AdminLoanList extends State<AdminLoanList> {
if (getLoans != null) {
var loans = getLoans.map(
(loan) {
var book = Book.fromJson(loan["book_detail"]);
var memberData = loan["member_detail"];
var userData = memberData["user"];
var user = User(
userData["id"],
memberData["id"],
userData["username"],
userData["email"],
userData["first_name"],
userData["last_name"],
userData["is_staff"],
);
var book = Book.fromJson(loan["book"]);
var user = User.fromJson(loan["user"]);
return Loan(
book,
......@@ -73,13 +97,34 @@ class _AdminLoanList extends State<AdminLoanList> {
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return [TopAppBar(title: title)];
},
body: ListView(
children: List.generate(loans.length, (index) {
body: ListView.builder(
controller: listScrollController,
itemCount: loans.length + 1,
itemBuilder: (context, index) {
if (index < loans.length) {
return LoanItem(
loans.elementAt(index),
user: loans.elementAt(index).user,
);
}),
} else {
return Container(
padding: const EdgeInsets.all(10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ElevatedButton(
onPressed: prevPage,
child: const Text('Prev'),
),
Text(loanProvider.pageNumber.toString()),
ElevatedButton(
onPressed: nextPage,
child: const Text('Next'),
),
],
),
);
}
},
),
);
} else {
......
......@@ -26,13 +26,47 @@ class _LoanList extends State<LoanList> {
super.initState();
}
ScrollController listScrollController = ScrollController();
void scrollToTop() {
if (listScrollController.hasClients) {
final position = listScrollController.position.minScrollExtent;
listScrollController.jumpTo(position);
}
}
Future<void> nextPage() async {
if (Provider.of<AuthProvider>(context, listen: false).hasNextPage) {
Provider.of<AuthProvider>(context, listen: false).setPage(
Provider.of<AuthProvider>(context, listen: false).pageNumber + 1,
);
} else {
Provider.of<AuthProvider>(context, listen: false).setPage(
Provider.of<AuthProvider>(context, listen: false).totalPages!);
}
Provider.of<AuthProvider>(context, listen: false).getMemberLoan();
scrollToTop();
}
Future<void> prevPage() async {
if (Provider.of<AuthProvider>(context, listen: false).hasPrevPage) {
Provider.of<AuthProvider>(context, listen: false).setPage(
Provider.of<AuthProvider>(context, listen: false).pageNumber - 1,
);
} else {
Provider.of<AuthProvider>(context, listen: false).setPage(1);
}
Provider.of<AuthProvider>(context, listen: false).getMemberLoan();
scrollToTop();
}
@override
Widget build(BuildContext context) {
return Consumer<AuthProvider>(builder: (context, authProvider, child) {
if (authProvider.memberLoans != null) {
var loans = authProvider.memberLoans!.map(
(loan) {
var book = Book.fromJson(loan["book_detail"]);
var book = Book.fromJson(loan["book"]);
return Loan(
book,
null,
......@@ -48,10 +82,35 @@ class _LoanList extends State<LoanList> {
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return [const TopAppBar(title: "Book Loans")];
},
body: ListView(
children: List.generate(loans.length, (index) {
return LoanItem(loans.elementAt(index));
}),
body: ListView.builder(
controller: listScrollController,
itemCount: loans.length + 1,
itemBuilder: (context, index) {
if (index < loans.length) {
return LoanItem(
loans.elementAt(index),
user: loans.elementAt(index).user,
);
} else {
return Container(
padding: const EdgeInsets.all(10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ElevatedButton(
onPressed: prevPage,
child: const Text('Prev'),
),
Text(authProvider.pageNumber.toString()),
ElevatedButton(
onPressed: nextPage,
child: const Text('Next'),
),
],
),
);
}
},
),
);
} else {
......
import 'package:flutter/material.dart';
import 'package:library_app/src/providers/auth_provider.dart';
import 'package:provider/provider.dart';
import 'package:go_router/go_router.dart';
import 'package:library_app/src/screens/profile_edit_screen.dart';
import 'package:library_app/src/providers/auth_provider.dart';
import 'package:library_app/src/widgets/navigations.dart';
import 'package:provider/provider.dart';
class Profile extends StatefulWidget {
const Profile({super.key});
......@@ -78,19 +78,25 @@ class _Profile extends State<Profile> {
textAlign: TextAlign.center,
),
),
const SizedBox(
height: 10.0,
),
FilledButton(
child: const Text("Edit Profile"),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const ProfileEditScreen(),
),
);
context.push("/profile-edit");
},
),
const SizedBox(
height: 10.0,
),
ElevatedButton(
onPressed: () => context.push("/change-password"),
child: const Text("Change Password"),
),
const SizedBox(
height: 10.0,
),
OutlinedButton(
child: const Text("Log Out"),
onPressed: () {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment