diff --git a/lib/pages/home/news/news_details/news_details.dart b/lib/pages/home/news/news_details/news_details.dart index dc39cf2..c1b756a 100644 --- a/lib/pages/home/news/news_details/news_details.dart +++ b/lib/pages/home/news/news_details/news_details.dart @@ -1,76 +1,70 @@ +import 'package:didvan/pages/home/news/news_details/news_details_state.dart'; import 'package:didvan/widgets/didvan/page_view.dart'; -import 'package:didvan/widgets/didvan/text.dart'; import 'package:didvan/widgets/floating_navigation_bar.dart'; -import 'package:didvan/widgets/skeletun_image.dart'; +import 'package:didvan/widgets/state_handlers/state_handler.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; -class NewsDetails extends StatelessWidget { - const NewsDetails({Key? key}) : super(key: key); +class NewsDetails extends StatefulWidget { + final Map pageData; + const NewsDetails({Key? key, required this.pageData}) : super(key: key); + + @override + State createState() => _NewsDetailsState(); +} + +class _NewsDetailsState extends State { + final ScrollController _scrollController = ScrollController(); + + @override + void initState() { + final state = context.read(); + state.args = widget.pageData['args']; + Future.delayed(Duration.zero, () { + state.getNewsDetails(widget.pageData['id']); + }); + super.initState(); + } @override Widget build(BuildContext context) { return Scaffold( - body: Stack( - children: [ - DidvanPageView( - pages: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SkeletonImage( - imageUrl: 'https://wallpapercave.com/wp/wp9373116.jpg', - width: double.infinity, - height: 200, + body: Consumer( + builder: (context, state, child) => StateHandler( + onRetry: () => state.getNewsDetails(state.currentNews.id), + state: state, + builder: (context, state) => Stack( + children: [ + if (state.news.isNotEmpty) + DidvanPageView( + isRadar: false, + initialIndex: state.initialIndex, + onPageChanged: _onPageChnaged, + scrollController: _scrollController, + items: state.news, + ), + if (state.news.isNotEmpty) + Positioned( + bottom: 0, + left: 0, + right: 0, + child: FloatingNavigationBar( + news: state.currentNews, + scrollController: _scrollController, ), - const SizedBox(height: 20), - for (var i = 0; i < 10; i++) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Builder( - builder: (context) { - switch (i) { - case 0: - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - DidvanText( - 'تحلیل شکاف فناوری صنعت فولاد ایران', - style: - Theme.of(context).textTheme.bodyText1, - ), - const SizedBox(height: 8), - const DidvanText( - 'پایگاه خبری معدن نیوز - 2 ساعت پیش', - ), - const SizedBox(height: 8), - ], - ); - default: - return const Padding( - padding: EdgeInsets.only(bottom: 8), - child: DidvanText( - 'این سایت امروز (شنبه) تیم منتخب قاره آسیا در سال ۲۰۲۱ میلادی را معرفی کرد که از ایران، سردار آزمون و محمد حسین کنعانی‌زادگان در ترکیب اصلی و مهدی طارمی و سیدمجید حسینی در جمع بازیکنان ذخیره دیده می‌شوند.', - ), - ); - } - }, - ), - ), - const SizedBox(height: 20), - ], - ), + ), ], ), - const Positioned( - left: 0, - right: 0, - bottom: 0, - child: FloatingNavigationBar( - isRadar: false, - ), - ), - ], + ), ), ); } + + void _onPageChnaged(int index) { + final state = context.read(); + state.getNewsDetails( + state.news[index]!.id, + isForward: state.currentIndex < index, + ); + } } diff --git a/lib/pages/home/news/news_details/news_details_state.dart b/lib/pages/home/news/news_details/news_details_state.dart index 116b980..72173a7 100644 --- a/lib/pages/home/news/news_details/news_details_state.dart +++ b/lib/pages/home/news/news_details/news_details_state.dart @@ -1,3 +1,73 @@ -import 'package:didvan/providers/core_provider.dart'; +import 'dart:math'; -class NewsDetailsState extends CoreProvier {} +import 'package:didvan/models/enums.dart'; +import 'package:didvan/models/news_details_data.dart'; +import 'package:didvan/models/requests/news.dart'; +import 'package:didvan/providers/core_provider.dart'; +import 'package:didvan/services/network/request.dart'; +import 'package:didvan/services/network/request_helper.dart'; + +class NewsDetailsState extends CoreProvier { + final List news = []; + late final int initialIndex; + late final NewsRequestArgs args; + + int _currentIndex = 0; + int get currentIndex => _currentIndex; + + NewsDetailsData get currentNews => news[_currentIndex]!; + + Future getNewsDetails(int id, {bool? isForward}) async { + if (isForward == null) { + appState = AppState.busy; + } + final service = RequestService(RequestHelper.newsDetails(id, args)); + await service.httpGet(); + if (service.isSuccess) { + final result = service.result; + NewsDetailsData? prevNews; + if (result['prevNews'].isNotEmpty) { + prevNews = NewsDetailsData.fromJson(result['prevNews']); + } + final newsItem = NewsDetailsData.fromJson(result['news']); + NewsDetailsData? nextNews; + if (result['nextNews'].isNotEmpty) { + nextNews = NewsDetailsData.fromJson(result['nextNews']); + } + if (isForward == null) { + news.addAll(List.generate(max(newsItem.order - 2, 0), (index) => null)); + if (prevNews != null) { + news.add(prevNews); + } + news.add(newsItem); + if (nextNews != null) { + news.add(nextNews); + } + _currentIndex = initialIndex = newsItem.order - 1; + } else if (isForward) { + if (!exists(nextNews) && nextNews != null) { + news.add(nextNews); + } + _currentIndex++; + } else if (!isForward) { + if (!exists(prevNews) && prevNews != null) { + news[_currentIndex - 2] = prevNews; + } + _currentIndex--; + } + appState = AppState.idle; + return; + } + if (isForward == null) { + appState = AppState.failed; + } + } + + bool exists(NewsDetailsData? newsItem) => + news.any((n) => newsItem != null && n != null && n.id == newsItem.id); + + void onCommentAdded(int id) { + news.firstWhere((item) => item!.id == id)!.comments++; + notifyListeners(); + } +} diff --git a/lib/pages/home/news/news_state.dart b/lib/pages/home/news/news_state.dart index 12fe709..2851146 100644 --- a/lib/pages/home/news/news_state.dart +++ b/lib/pages/home/news/news_state.dart @@ -1,20 +1,31 @@ import 'package:collection/collection.dart'; import 'package:didvan/models/enums.dart'; import 'package:didvan/models/news_overview.dart'; +import 'package:didvan/models/requests/news.dart'; import 'package:didvan/providers/core_provider.dart'; import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/network/request_helper.dart'; class NewsState extends CoreProvier { - bool isFiltering = false; String search = ''; String lastSearch = ''; String? startDate; String? endDate; + int page = 1; final List _markQueue = []; final List news = []; + void init() { + search = ''; + lastSearch = ''; + startDate = null; + endDate = null; + Future.delayed(Duration.zero, () { + getNews(page: 1); + }); + } + void resetFilters() { startDate = null; endDate = null; @@ -24,21 +35,23 @@ class NewsState extends CoreProvier { Future getNews({ required int page, }) async { - if (search != '' || filterApplied) { + if (this.page == page) { + news.clear(); + } + this.page = page; + if (search != '') { lastSearch = search; - isFiltering = true; - } else { - isFiltering = false; } lastSearch = search; appState = AppState.busy; - news.clear(); final service = RequestService( RequestHelper.newsOverviews( - page: 1, - startDate: startDate?.split(' ').first, - endDate: endDate?.split(' ').first, - search: search == '' ? null : search, + args: NewsRequestArgs( + page: 1, + startDate: startDate?.split(' ').first, + endDate: endDate?.split(' ').first, + search: search == '' ? null : search, + ), ), ); await service.httpGet(); @@ -53,7 +66,7 @@ class NewsState extends CoreProvier { appState = AppState.failed; } - Future markNews(int id) async { + Future mark(int id) async { news.firstWhere((element) => element.id == id).marked = true; notifyListeners(); _markQueue.add(MapEntry(id, true)); @@ -69,7 +82,7 @@ class NewsState extends CoreProvier { }); } - Future unMarkNews(int id) async { + Future unMark(int id) async { news.firstWhere((element) => element.id == id).marked = false; notifyListeners(); _markQueue.add(MapEntry(id, false)); @@ -85,5 +98,5 @@ class NewsState extends CoreProvier { }); } - bool get filterApplied => startDate != null || endDate != null; + bool get isFiltering => startDate != null || endDate != null; } diff --git a/lib/pages/home/news/widgets/news_item.dart b/lib/pages/home/news/widgets/news_item.dart index 7b479bf..993e875 100644 --- a/lib/pages/home/news/widgets/news_item.dart +++ b/lib/pages/home/news/widgets/news_item.dart @@ -1,13 +1,14 @@ import 'package:didvan/models/news_overview.dart'; +import 'package:didvan/models/requests/news.dart'; import 'package:didvan/pages/home/news/news_state.dart'; import 'package:didvan/routes/routes.dart'; +import 'package:didvan/utils/date_time.dart'; import 'package:didvan/widgets/bookmark_button.dart'; import 'package:didvan/widgets/didvan/card.dart'; import 'package:didvan/widgets/didvan/divider.dart'; import 'package:didvan/widgets/didvan/text.dart'; import 'package:didvan/widgets/skeleton_image.dart'; import 'package:flutter/material.dart'; -import 'package:persian_number_utility/persian_number_utility.dart'; import 'package:provider/provider.dart'; class NewsItem extends StatelessWidget { @@ -23,6 +24,12 @@ class NewsItem extends StatelessWidget { arguments: { 'state': state, 'id': news.id, + 'args': NewsRequestArgs( + page: state.page, + endDate: state.endDate, + search: state.search, + startDate: state.startDate, + ) }, ), child: Column( @@ -56,14 +63,22 @@ class NewsItem extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - DidvanText( - '${news.reference} | ${DateTime.parse(news.createdAt).toPersianDateStr()}', - style: Theme.of(context).textTheme.overline, + Row( + children: [ + DidvanText( + news.reference, + style: Theme.of(context).textTheme.caption, + ), + DidvanText( + ' - ' + DateTimeUtils.momentGenerator(news.createdAt), + style: Theme.of(context).textTheme.caption, + ), + ], ), BookmarkButton( value: news.marked, - onMark: () => state.markNews(news.id), - onUnmark: () => state.unMarkNews(news.id), + onMark: () => state.mark(news.id), + onUnmark: () => state.unMark(news.id), ), ], ),