diff --git a/covas_mobile/lib/pages/ListItemByOrganizers.dart b/covas_mobile/lib/pages/ListItemByOrganizers.dart index 3a89580..86ade76 100644 --- a/covas_mobile/lib/pages/ListItemByOrganizers.dart +++ b/covas_mobile/lib/pages/ListItemByOrganizers.dart @@ -42,17 +42,53 @@ class ListItemOrganizers extends StatefulWidget { // homepage state class _MyHomePageState extends State { + int _fetchCount = 0; + bool _isLoading = false; + late ScrollController _scrollController; + final AuthService _authService = AuthService(); + void _incrementFetchCount() { + setState(() { + _fetchCount++; + }); + } + + void _scrollListener() { + if (_scrollController.position.pixels == + _scrollController.position.maxScrollExtent) { + _incrementFetchCount(); + _fetchData(); + } + } + + Future _fetchData() async { + print("Counter : ${_fetchCount}"); + if (_isLoading) return; + setState(() { + _isLoading = true; + }); + + await Future.delayed(Duration(seconds: 2)); + getPosts(widget.organizer, count: _fetchCount); + + setState(() { + _isLoading = false; + }); + } + // variable to call and store future list of posts // function to fetch data from api and return future list of posts - static Future> getPosts(organizer) async { + static Future> getPosts(organizer, {count = 0}) async { SharedPreferences prefs = await SharedPreferences.getInstance(); var accessToken = prefs.getString("access_token") ?? ""; final List body = []; if (accessToken.isNotEmpty) { - var url = Uri.parse("${globals.api}/events?organizers=${organizer}"); + DateTime currentDatetime = DateTime.now(); + num limit = 20 * (count + 1); + var url = Uri.parse( + "${globals.api}/events?organizers=${organizer}&limit=${limit}¤t_datetime=${currentDatetime.toString()}"); final response = await http.get(url, headers: { "Content-Type": "application/json", HttpHeaders.cookieHeader: "access_token=${accessToken}" @@ -67,6 +103,14 @@ class _MyHomePageState extends State { void initState() { super.initState(); _authService.checkTokenStatus(context); + _scrollController = ScrollController(); + _scrollController.addListener(_scrollListener); + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); } // build function @@ -109,7 +153,10 @@ class _MyHomePageState extends State { foregroundColor: Colors.white, ), body: ListView.separated( - itemCount: posts.length, + controller: _scrollController, + itemCount: posts.isNotEmpty + ? posts.length + (_isLoading ? 1 : 0) // Add 1 only if loading + : 0, itemBuilder: (context, index) { final post = posts[index]; final startDate = DateTime.parse(post.startDate!); diff --git a/covas_mobile/lib/pages/ListItemByTags.dart b/covas_mobile/lib/pages/ListItemByTags.dart index 3866cc9..475b4ed 100644 --- a/covas_mobile/lib/pages/ListItemByTags.dart +++ b/covas_mobile/lib/pages/ListItemByTags.dart @@ -44,15 +44,53 @@ class ListItemTags extends StatefulWidget { class _MyHomePageState extends State { // variable to call and store future list of posts + int _fetchCount = 0; + bool _isLoading = false; + late ScrollController _scrollController; + final AuthService _authService = AuthService(); + void _incrementFetchCount() { + setState(() { + _fetchCount++; + }); + } + + void _scrollListener() { + if (_scrollController.position.pixels == + _scrollController.position.maxScrollExtent) { + _incrementFetchCount(); + + _fetchData(); + } + } + + Future _fetchData() async { + print("Counter : ${_fetchCount}"); + if (_isLoading) return; + setState(() { + _isLoading = true; + }); + + await Future.delayed(Duration(seconds: 2)); + getPosts(widget.tags, count: _fetchCount); + + setState(() { + _isLoading = false; + }); + } + // function to fetch data from api and return future list of posts - static Future> getPosts(tags) async { + static Future> getPosts(tags, {count = 0}) async { SharedPreferences prefs = await SharedPreferences.getInstance(); var accessToken = prefs.getString("access_token") ?? ""; final List body = []; if (accessToken.isNotEmpty) { - var url = Uri.parse("${globals.api}/events?tags=${tags}"); + DateTime currentDatetime = DateTime.now(); + num limit = 20 * (count + 1); + + var url = Uri.parse( + "${globals.api}/events?tags=${tags}&limit=${limit}¤t_datetime=${currentDatetime.toString()}"); final response = await http.get(url, headers: { "Content-Type": "application/json", HttpHeaders.cookieHeader: "access_token=${accessToken}" @@ -67,6 +105,15 @@ class _MyHomePageState extends State { void initState() { super.initState(); _authService.checkTokenStatus(context); + _authService.checkTokenStatus(context); + _scrollController = ScrollController(); + _scrollController.addListener(_scrollListener); + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); } // build function @@ -108,7 +155,10 @@ class _MyHomePageState extends State { foregroundColor: Colors.white, ), body: ListView.separated( - itemCount: posts.length, + controller: _scrollController, + itemCount: posts.isNotEmpty + ? posts.length + (_isLoading ? 1 : 0) // Add 1 only if loading + : 0, itemBuilder: (context, index) { final post = posts[index]; final startDate = DateTime.parse(post.startDate!); diff --git a/covas_mobile/lib/pages/ListItemMenu.dart b/covas_mobile/lib/pages/ListItemMenu.dart index 5ffa41d..4614044 100644 --- a/covas_mobile/lib/pages/ListItemMenu.dart +++ b/covas_mobile/lib/pages/ListItemMenu.dart @@ -48,6 +48,9 @@ class ListItemMenu extends StatefulWidget { class _MyHomePageState extends State { BannerAd? _bannerAd; final AuthService _authService = AuthService(); + late ScrollController _scrollController; + int _fetchCount = 0; + bool _isLoading = false; Future> postsFuture = getPosts(); List filteredPosts = []; @@ -144,6 +147,37 @@ class _MyHomePageState extends State { return "${year}-${month}-${day}"; } + void _incrementFetchCount() { + setState(() { + _fetchCount++; + }); + _fetchData(); + } + + void _scrollListener() { + if (_scrollController.position.pixels == + _scrollController.position.maxScrollExtent) { + _incrementFetchCount(); + } + + // Scroll to top + } + + Future _fetchData() async { + print("Counter : ${_fetchCount}"); + if (_isLoading) return; + setState(() { + _isLoading = true; + }); + + await Future.delayed(Duration(seconds: 2)); + fetchPostsByLocation(); + + setState(() { + _isLoading = false; + }); + } + @override void initState() { super.initState(); @@ -153,10 +187,19 @@ class _MyHomePageState extends State { _bannerAd = ad; }); }); + _scrollController = ScrollController(); + _scrollController.addListener(_scrollListener); + // Initialize data fetch when the page loads _getCurrentLocation(); } + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + // Get the device's current location Future _getCurrentLocation() async { PermissionStatus status = await Permission.location.status; @@ -346,8 +389,10 @@ class _MyHomePageState extends State { } else { queryParameters = dateParameter; } + int limit = 20 * (_fetchCount + 1); - return Uri.parse("${globals.api}/$endpoint?$queryParameters"); + return Uri.parse( + "${globals.api}/$endpoint?$queryParameters&limit=${limit}"); } Future searchSuggestionsByItem(String input) async { @@ -358,7 +403,7 @@ class _MyHomePageState extends State { var url = await getUrlForEvents(); final response = await http.get(url, headers: { "Content-Type": "application/json", - HttpHeaders.cookieHeader: "access_token=$accessToken" + HttpHeaders.cookieHeader: "acce0ss_token=$accessToken" }); if (response.statusCode == 200) { @@ -422,16 +467,17 @@ class _MyHomePageState extends State { if (response.statusCode == 200) { final List body = json.decode(utf8.decode(response.bodyBytes)); print("results fetch : ${body}"); + print("fetch count : ${_fetchCount}"); // Update state after getting the response + if (body.isEmpty) { + _fetchCount--; + } setState(() { if (body.isNotEmpty) { // If we have results, map them to Events filteredPosts = body .map((e) => Events.fromJson(e as Map)) .toList(); - } else { - // If no results, clear filteredPosts - filteredPosts.clear(); } }); } else { @@ -570,6 +616,7 @@ class _MyHomePageState extends State { controller: inputItem, labelText: 'Search by item', onChanged: (value) { + _fetchCount = 0; if (value.isNotEmpty) { setState(() { itemName = value; @@ -587,6 +634,7 @@ class _MyHomePageState extends State { } }, onClear: () { + _fetchCount = 0; setState(() { inputItem.clear(); itemName = ''; @@ -598,6 +646,7 @@ class _MyHomePageState extends State { }, suggestions: suggestionsItem, onSuggestionTap: (suggestion) async { + _fetchCount = 0; setState(() { itemName = suggestion['name']; inputItem.text = itemName; @@ -613,6 +662,7 @@ class _MyHomePageState extends State { controller: inputTags, labelText: 'Search by tags', onChanged: (value) { + _fetchCount = 0; if (value.isNotEmpty) { setState(() { itemTags = value; @@ -630,6 +680,7 @@ class _MyHomePageState extends State { } }, onClear: () { + _fetchCount = 0; setState(() { inputTags.clear(); }); @@ -637,6 +688,8 @@ class _MyHomePageState extends State { }, suggestions: suggestionsTags, onSuggestionTap: (suggestion) async { + _fetchCount = 0; + setState(() { itemTags = suggestion['name']; inputTags.text = itemTags; @@ -660,6 +713,8 @@ class _MyHomePageState extends State { controller: inputGeo, labelText: 'Search by geographical zone', onChanged: (value) async { + _fetchCount = 0; + if (value.isNotEmpty) { setState(() { geographicalZone = value; @@ -681,6 +736,7 @@ class _MyHomePageState extends State { } }, onClear: () async { + _fetchCount = 0; final prefs = await SharedPreferences.getInstance(); prefs.remove("city_lat"); prefs.remove("city_long"); @@ -696,6 +752,7 @@ class _MyHomePageState extends State { }, suggestions: suggestionsGeo, onSuggestionTap: (suggestion) async { + _fetchCount = 0; final latitude = suggestion['geometry']['coordinates'][1]; final longitude = suggestion['geometry']['coordinates'][0]; setState(() { @@ -770,12 +827,22 @@ class _MyHomePageState extends State { } return ListView.separated( - itemCount: displayedPosts.length, + controller: _scrollController, + itemCount: displayedPosts.isNotEmpty + ? displayedPosts.length + + (_isLoading ? 1 : 0) // Add 1 only if loading + : 0, itemBuilder: (context, index) { + if (index >= displayedPosts.length) { + return _isLoading + ? Center(child: CircularProgressIndicator()) + : SizedBox.shrink(); + } final post = displayedPosts[index]; final startDate = DateTime.parse(post.startDate!); final date = DateFormat.yMd().format(startDate); final time = DateFormat.Hm().format(startDate); + return ListTile( title: Text('${post.name!}'), subtitle: Text('${post.place!}\n${date} ${time}'), diff --git a/covas_mobile/pubspec.lock b/covas_mobile/pubspec.lock index f8e2f6c..770d04a 100644 --- a/covas_mobile/pubspec.lock +++ b/covas_mobile/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: transitive description: name: args - sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 url: "https://pub.dev" source: hosted - version: "2.6.0" + version: "2.7.0" asn1lib: dependency: transitive description: