mirror of
https://github.com/Uuttssaavv/flutter-clean-architecture-riverpod.git
synced 2025-08-06 16:19:42 +08:00
181 lines
6.5 KiB
Dart
181 lines
6.5 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_project/features/dashboard/presentation/providers/dashboard_state_provider.dart';
|
|
import 'package:flutter_project/features/dashboard/presentation/providers/state/dashboard_state.dart';
|
|
import 'package:flutter_project/features/dashboard/presentation/widgets/dashboard_drawer.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
class DashboardScreen extends ConsumerStatefulWidget {
|
|
static const String routeName = 'DashboardScreen';
|
|
|
|
const DashboardScreen({Key? key}) : super(key: key);
|
|
|
|
@override
|
|
ConsumerState<DashboardScreen> createState() => _DashboardScreenState();
|
|
}
|
|
|
|
class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
|
final scrollController = ScrollController();
|
|
final TextEditingController searchController = TextEditingController();
|
|
bool isSearchActive = false;
|
|
Timer? _debounce;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
scrollController.addListener(scrollControllerListener);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_debounce?.cancel();
|
|
super.dispose();
|
|
}
|
|
|
|
void scrollControllerListener() {
|
|
if (scrollController.position.maxScrollExtent == scrollController.offset) {
|
|
final notifier = ref.read(dashboardNotifierProvider.notifier);
|
|
if (isSearchActive) {
|
|
notifier.searchProducts(searchController.text);
|
|
} else {
|
|
notifier.fetchProducts();
|
|
}
|
|
}
|
|
}
|
|
|
|
void refreshScrollControllerListener() {
|
|
scrollController.removeListener(scrollControllerListener);
|
|
scrollController.addListener(scrollControllerListener);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final state = ref.watch(dashboardNotifierProvider);
|
|
|
|
ref.listen(
|
|
dashboardNotifierProvider.select((value) => value),
|
|
((DashboardState? previous, DashboardState next) {
|
|
//show Snackbar on failure
|
|
if (next.state == DashboardConcreteState.fetchedAllProducts) {
|
|
if (next.message.isNotEmpty) {
|
|
ScaffoldMessenger.of(context)
|
|
.showSnackBar(SnackBar(content: Text(next.message.toString())));
|
|
}
|
|
}
|
|
}),
|
|
);
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: isSearchActive
|
|
? TextField(
|
|
style: Theme.of(context).textTheme.bodyMedium,
|
|
decoration: InputDecoration(
|
|
hintText: 'Search here',
|
|
hintStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
color: Theme.of(context).colorScheme.onBackground,
|
|
),
|
|
focusedBorder: UnderlineInputBorder(
|
|
borderSide: BorderSide(
|
|
color: Theme.of(context).colorScheme.onBackground,
|
|
),
|
|
),
|
|
border: UnderlineInputBorder(
|
|
borderSide: BorderSide(
|
|
color: Theme.of(context).colorScheme.onBackground,
|
|
),
|
|
),
|
|
),
|
|
controller: searchController,
|
|
onChanged: _onSearchChanged,
|
|
)
|
|
: const Text('Dashboard'),
|
|
actions: [
|
|
IconButton(
|
|
onPressed: () {
|
|
searchController.clear();
|
|
setState(() {
|
|
isSearchActive = !isSearchActive;
|
|
});
|
|
|
|
ref.read(dashboardNotifierProvider.notifier).resetState();
|
|
if (!isSearchActive) {
|
|
ref.read(dashboardNotifierProvider.notifier).fetchProducts();
|
|
}
|
|
refreshScrollControllerListener();
|
|
},
|
|
icon: Icon(
|
|
isSearchActive ? Icons.clear : Icons.search,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
drawer: const DashboardDrawer(),
|
|
body: state.state == DashboardConcreteState.loading
|
|
? const Center(child: CircularProgressIndicator())
|
|
: state.hasData
|
|
? Column(
|
|
children: [
|
|
Expanded(
|
|
child: Scrollbar(
|
|
controller: scrollController,
|
|
child: ListView.separated(
|
|
separatorBuilder: (_, __) => const Divider(),
|
|
controller: scrollController,
|
|
itemCount: state.productList.length,
|
|
itemBuilder: (context, index) {
|
|
final product = state.productList[index];
|
|
return ListTile(
|
|
leading: CircleAvatar(
|
|
backgroundImage:
|
|
NetworkImage(product.thumbnail)),
|
|
title: Text(
|
|
product.title,
|
|
style: Theme.of(context).textTheme.bodyLarge,
|
|
),
|
|
trailing: Text(
|
|
'\$${product.price}',
|
|
style: Theme.of(context).textTheme.bodyMedium,
|
|
),
|
|
subtitle: Text(
|
|
product.description,
|
|
style: Theme.of(context).textTheme.bodyMedium,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
if (state.state == DashboardConcreteState.fetchingMore)
|
|
const Padding(
|
|
padding: EdgeInsets.only(bottom: 16.0),
|
|
child: CircularProgressIndicator(),
|
|
),
|
|
],
|
|
)
|
|
: Center(
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 22.0),
|
|
child: Text(
|
|
state.message,
|
|
textAlign: TextAlign.center,
|
|
style: const TextStyle(
|
|
fontSize: 18.0,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
_onSearchChanged(String query) {
|
|
if (_debounce?.isActive ?? false) _debounce?.cancel();
|
|
_debounce = Timer(const Duration(milliseconds: 500), () {
|
|
ref.read(dashboardNotifierProvider.notifier).searchProducts(query);
|
|
});
|
|
}
|
|
}
|