diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..2ba986f --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "chrome", + "request": "launch", + "name": "Launch Chrome against localhost", + "url": "http://localhost:8080", + "webRoot": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/build.zip b/build.zip new file mode 100644 index 0000000..24a5a5a Binary files /dev/null and b/build.zip differ diff --git a/lib/main.dart b/lib/main.dart index 9aec6c2..b7c514e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,5 @@ +// lib/main.dart + // ignore_for_file: deprecated_member_use import 'dart:async'; @@ -7,6 +9,8 @@ import 'package:bot_toast/bot_toast.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/firebase_options.dart'; import 'package:didvan/models/notification_message.dart'; +import 'package:didvan/models/requests/news.dart'; +import 'package:didvan/models/requests/radar.dart'; import 'package:didvan/providers/media.dart'; import 'package:didvan/providers/theme.dart'; import 'package:didvan/providers/user.dart'; @@ -32,56 +36,56 @@ import 'package:home_widget/home_widget.dart'; import 'package:provider/provider.dart'; import 'package:flutter_downloader/flutter_downloader.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; - -// پکیج جدید برای Deep Linking import 'package:app_links/app_links.dart'; -final GlobalKey navigatorKey = GlobalKey(); +import 'services/network/request.dart'; -// متغیر استاتیک برای نگهداری لینک اولیه +final GlobalKey navigatorKey = GlobalKey(); Uri? initialURI; @pragma('vm:entry-point') Future _backgroundCallbackHomeWidget(Uri? uri) async { if (uri != null) { - await HomeWidget.saveWidgetData("uri", uri.host); + await HomeWidget.saveWidgetData("uri", uri.host); } } void main() async { - WidgetsFlutterBinding.ensureInitialized(); - try { - if (!kIsWeb) { - HomeWidget.registerBackgroundCallback(_backgroundCallbackHomeWidget); - HomeWidget.registerInteractivityCallback(_backgroundCallbackHomeWidget); - await NotificationService.initializeNotification(); + runZonedGuarded( + () async { + WidgetsFlutterBinding.ensureInitialized(); try { - if (Platform.isAndroid) { - await FlutterDownloader.initialize( - debug: true, - ignoreSsl: true - ); + if (!kIsWeb) { + HomeWidget.registerBackgroundCallback(_backgroundCallbackHomeWidget); + HomeWidget.registerInteractivityCallback(_backgroundCallbackHomeWidget); + await NotificationService.initializeNotification(); + try { + if (Platform.isAndroid) { + await FlutterDownloader.initialize(debug: true, ignoreSsl: true); + } + } catch (e) { + e.printError(); + } } + + await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); + await FirebaseApi().initNotification(); } catch (e) { - e.printError(); + debugPrint(e.toString()); } - } - await Firebase.initializeApp( - options: DefaultFirebaseOptions.currentPlatform); - await FirebaseApi().initNotification(); - } catch (e) { - debugPrint(e.toString()); - } - - await SentryFlutter.init( - (options) { - options.dsn = - 'https://a4cfcaa7d67471240d295c25c968d91d@o4508585857384448.ingest.de.sentry.io/4508585886548048'; - options.tracesSampleRate = 1.0; - options.profilesSampleRate = 1.0; + await SentryFlutter.init( + (options) { + options.dsn = 'https://a4cfcaa7d67471240d295c25c968d91d@o4508585857384448.ingest.de.sentry.io/4508585886548048'; + options.tracesSampleRate = 1.0; + options.profilesSampleRate = 1.0; + }, + appRunner: () => runApp(const Didvan()), + ); + }, + (error, stack) { + Sentry.captureException(error, stackTrace: stack); }, - appRunner: () => runApp(const Didvan()), ); } @@ -100,41 +104,57 @@ class _DidvanState extends State with WidgetsBindingObserver { void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); - // مقداردهی اولیه و گوش دادن به لینک‌ها در اینجا انجام می‌شود _initDeepLinks(); } - + @override void dispose() { WidgetsBinding.instance.removeObserver(this); - _linkSubscription?.cancel(); // لغو کردن subscription + _linkSubscription?.cancel(); if (MediaService.currentPodcast != null) { MediaService.audioPlayer.dispose(); } super.dispose(); } - - /// منطق جدید برای مدیریت لینک‌ها با app_links + Future _initDeepLinks() async { _appLinks = AppLinks(); - - // لینک اولیه را فقط در متغیر ذخیره می‌کنیم initialURI = await _appLinks.getInitialLink(); - - // به لینک‌های جدید زمانی که اپلیکیشن باز است گوش می‌دهیم _linkSubscription = _appLinks.uriLinkStream.listen((uri) { _navigateTo(uri); }); } - - /// تابع کمکی برای ناوبری + void _navigateTo(Uri uri) { if (mounted) { String path = uri.path; - if (uri.fragment.isNotEmpty) { - path = "/${uri.fragment}"; + final Map params = uri.queryParameters; + + final String? token = params['token']; + + if (token != null) { + //todo: this didnt work + print("DEBUG: received token in url, token: $token, path: $path"); + RequestService.token = token; + } + + if (path.startsWith('/news/')) { + final id = path.split('/news/').last; + if (id.isNotEmpty) { + navigatorKey.currentState?.pushNamed( + Routes.newsDetails, + arguments: {'id': int.parse(id), 'args': const NewsRequestArgs(page: 0)}, + ); + } + } else if (path.startsWith('/radar/')) { + final id = path.split('/radar/').last; + if (id.isNotEmpty) { + navigatorKey.currentState?.pushNamed( + Routes.radarDetails, + arguments: {'id': int.parse(id), 'args': const RadarRequestArgs(page: 0)}, + ); + } } - navigatorKey.currentState?.pushNamed(path); } } @@ -143,11 +163,9 @@ class _DidvanState extends State with WidgetsBindingObserver { if (!kIsWeb) { if (state == AppLifecycleState.resumed) { var r = await HomeWidget.getWidgetData("cRoute", defaultValue: ''); - if (r!.toString() != Routes.splash) { - await HomeWidgetRepository.decideWhereToGo(); - + if (r.toString() != Routes.splash) { + await HomeWidgetRepository.decideWhereToGo(); NotificationMessage? data = HomeWidgetRepository.data; - if (data != null) { await HomeWidgetRepository.decideWhereToGoNotif(); } @@ -175,43 +193,43 @@ class _DidvanState extends State with WidgetsBindingObserver { color: Theme.of(context).colorScheme.surface, child: SafeArea( child: MaterialApp( - scrollBehavior: MyCustomScrollBehavior(), - navigatorKey: navigatorKey, - debugShowCheckedModeBanner: false, - title: 'Didvan', - theme: LightThemeConfig.themeData.copyWith( - bottomSheetTheme: const BottomSheetThemeData( - surfaceTintColor: Colors.transparent, - backgroundColor: Colors.transparent), - textTheme: LightThemeConfig.themeData.textTheme.apply( - fontFamily: themeProvider.fontFamily, - )), - darkTheme: DarkThemeConfig.themeData.copyWith( - bottomSheetTheme: const BottomSheetThemeData( - surfaceTintColor: Colors.transparent, - backgroundColor: Colors.transparent), - textTheme: DarkThemeConfig.themeData.textTheme.apply( - fontFamily: themeProvider.fontFamily, - )), - color: LightThemeConfig.themeData.primaryColor, - themeMode: themeProvider.themeMode, - onGenerateRoute: (settings) => - RouteGenerator.generateRoute(settings), - builder: BotToastInit(), - navigatorObservers: [BotToastNavigatorObserver()], - initialRoute: "/", - localizationsDelegates: const [ - GlobalCupertinoLocalizations.delegate, - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - ], - supportedLocales: const [ - Locale("fa", "IR"), - ], - locale: const Locale("fa", "IR"), - )), + scrollBehavior: MyCustomScrollBehavior(), + navigatorKey: navigatorKey, + debugShowCheckedModeBanner: false, + title: 'Didvan', + theme: LightThemeConfig.themeData.copyWith( + bottomSheetTheme: const BottomSheetThemeData( + surfaceTintColor: Colors.transparent, + backgroundColor: Colors.transparent), + textTheme: LightThemeConfig.themeData.textTheme.apply( + fontFamily: themeProvider.fontFamily, + )), + darkTheme: DarkThemeConfig.themeData.copyWith( + bottomSheetTheme: const BottomSheetThemeData( + surfaceTintColor: Colors.transparent, + backgroundColor: Colors.transparent), + textTheme: DarkThemeConfig.themeData.textTheme.apply( + fontFamily: themeProvider.fontFamily, + )), + color: LightThemeConfig.themeData.primaryColor, + themeMode: themeProvider.themeMode, + onGenerateRoute: (settings) => + RouteGenerator.generateRoute(settings), + builder: BotToastInit(), + navigatorObservers: [BotToastNavigatorObserver()], + initialRoute: "/", + localizationsDelegates: const [ + GlobalCupertinoLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + supportedLocales: const [ + Locale("fa", "IR"), + ], + locale: const Locale("fa", "IR"), + )), ), ), ); } -} +} \ No newline at end of file diff --git a/lib/services/network/request.dart b/lib/services/network/request.dart index bad60ff..96bd092 100644 --- a/lib/services/network/request.dart +++ b/lib/services/network/request.dart @@ -1,3 +1,5 @@ +// lib/services/network/request.dart + // ignore_for_file: empty_catches import 'dart:convert'; @@ -15,7 +17,7 @@ import 'package:mime/mime.dart'; import 'package:permission_handler/permission_handler.dart'; class RequestService { - static late String token; + static String? token; // Made token nullable int? statusCode; dynamic data(String s) { @@ -48,12 +50,9 @@ class RequestService { }) { if (body != null) _requestBody = body; if (requestHeaders != null) _headers.addAll(requestHeaders); - if (useAutherization) _headers.addAll({'Authorization': 'Bearer $token'}); - // if (kDebugMode) { - // try { - // print('Authorization : Bearer $token'); - // } catch (e) {} - // } + if (useAutherization && token != null) { // Check if token is not null + _headers.addAll({'Authorization': 'Bearer $token'}); + } if (body != null) _requestBody = body; } diff --git a/lib/views/authentication/authentication.dart b/lib/views/authentication/authentication.dart index 9f72ffb..1d58f43 100644 --- a/lib/views/authentication/authentication.dart +++ b/lib/views/authentication/authentication.dart @@ -42,7 +42,6 @@ class _AuthenticationState extends State { @override Widget build(BuildContext context) { return Scaffold( - resizeToAvoidBottomInset: false, body: Consumer( builder: (context, state, child) => WillPopScope( onWillPop: () async { diff --git a/lib/views/authentication/widgets/authentication_layout.dart b/lib/views/authentication/widgets/authentication_layout.dart index bd4a0cb..579ea4c 100644 --- a/lib/views/authentication/widgets/authentication_layout.dart +++ b/lib/views/authentication/widgets/authentication_layout.dart @@ -49,7 +49,8 @@ class AuthenticationLayout extends StatelessWidget { SliverPadding( padding: const EdgeInsets.symmetric(horizontal: 20), sliver: SliverFillRemaining( - hasScrollBody: false, + hasScrollBody: true, + fillOverscroll: true, child: Column( children: [ for (var i = 0; i < children.length; i++) children[i], diff --git a/lib/views/customize_category/customize_category_state.dart b/lib/views/customize_category/customize_category_state.dart index b48578a..d02142b 100644 --- a/lib/views/customize_category/customize_category_state.dart +++ b/lib/views/customize_category/customize_category_state.dart @@ -24,6 +24,7 @@ class CustomizeCategoryState extends CoreProvier { Future getFavourites() async { appState = AppState.busy; + update(); final service = RequestService( RequestHelper.favourites(), @@ -31,13 +32,19 @@ class CustomizeCategoryState extends CoreProvier { await service.httpGet(); if (service.isSuccess) { faves.clear(); - final favourites = service.data('types'); - for (var i = 0; i < favourites.length; i++) { - faves.add(FavoritesResponse.fromJson(favourites[i])); + final favouritesData = service.data('types'); + if (favouritesData is List) { + for (var i = 0; i < favouritesData.length; i++) { + faves.add(FavoritesResponse.fromJson(favouritesData[i])); + } } + + // اضافه کردن مستقیم بخش فرصت و تهدید + faves.add(FavoritesResponse(id: 999, name: 'فرصت و تهدید', selected: false)); + selectedFavIds.clear(); for (var element in faves) { - if (element.selected!) { + if (element.selected == true) { selectedFavIds.add(element.id!); } } diff --git a/lib/views/customize_category/favorites_step.dart b/lib/views/customize_category/favorites_step.dart index 9a4a653..ff44291 100644 --- a/lib/views/customize_category/favorites_step.dart +++ b/lib/views/customize_category/favorites_step.dart @@ -78,29 +78,38 @@ class _FavoritesStepState extends State { asset: Assets.emptyChat, title: 'اولین نظر را بنویسید...', ), - builder: (context, state, index) => Center( - child: Container( - height: 48, - decoration: BoxDecoration( + builder: (context, state, index) => Column( + children: [ + Center( + child: Container( + height: 48, + decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), border: Border.all( - width: 1, - color: state.faves[index].selected! - ? Theme.of(context) - .colorScheme - .focusedBorder - : Theme.of(context).colorScheme.cardBorder), + width: 1, + color: state.faves[index].selected! + ? Theme.of(context).colorScheme.focusedBorder + : Theme.of(context).colorScheme.cardBorder + ), color: state.faves[index].selected! ? Theme.of(context).colorScheme.focused - : Theme.of(context).colorScheme.cardBorder), - child: CustomizeCategoryCheckbox( + : Theme.of(context).colorScheme.cardBorder + ), + child: CustomizeCategoryCheckbox( title: state.faves[index].name!, value: state.faves[index].selected, checkColor: Theme.of(context).colorScheme.checkFav, onChanged: (val) { state.changeSelected( - index, state.faves, state.selectedFavIds); - })), + index, state.faves, state.selectedFavIds); + } + ) + ), + ), + // Add extra bottom padding for the last item + if (index == state.faves.length - 1) + const SizedBox(height: 80), + ], ), ), ), @@ -129,4 +138,4 @@ class _FavoritesStepState extends State { ), ); } -} +} \ No newline at end of file diff --git a/lib/views/home/home.dart b/lib/views/home/home.dart index 4442229..3192715 100644 --- a/lib/views/home/home.dart +++ b/lib/views/home/home.dart @@ -142,9 +142,10 @@ class _HomeState extends State @override void initState() { - if (widget.showDialogs ?? false) { - _showDialog(context); - } + // Remove dialog showing logic to prevent welcome popups + // if (widget.showDialogs ?? false) { + // _showDialog(context); + // } final state = context.read(); DesignConfig.updateSystemUiOverlayStyle(); diff --git a/lib/views/home/home_state.dart b/lib/views/home/home_state.dart index 5214bf4..96ff205 100644 --- a/lib/views/home/home_state.dart +++ b/lib/views/home/home_state.dart @@ -82,6 +82,7 @@ class HomeState extends CoreProvier { ); await service.httpGet(); if (service.isSuccess) { + print("DEBUG : Homestate is succes"); lastPage = service.result['lastPage']; results.addAll( List.from( @@ -94,12 +95,14 @@ class HomeState extends CoreProvier { appState = AppState.idle; return; } + print("DEBUG : Homestate is NOT succes"); appState = AppState.failed; } Future searchAll({required int page}) async { this.page = page; if (page == 1) { + print("DEBUG : serach is busy"); results.clear(); appState = AppState.busy; } @@ -115,6 +118,7 @@ class HomeState extends CoreProvier { ); await service.httpGet(); if (service.isSuccess) { + print("DEBUG : HTTPget Home is succes"); lastPage = service.result['lastPage']; unreadCount = service.result['unread'] ?? unreadCount; results.addAll( @@ -128,6 +132,7 @@ class HomeState extends CoreProvier { appState = AppState.idle; return; } + print("DEBUG : Homestate is faild"); appState = AppState.failed; } diff --git a/lib/views/home/main/main_page.dart b/lib/views/home/main/main_page.dart index 720aa24..ae1b67e 100644 --- a/lib/views/home/main/main_page.dart +++ b/lib/views/home/main/main_page.dart @@ -33,30 +33,44 @@ class MainPage extends StatefulWidget { class _MainPageState extends State { @override void initState() { - context.read().init(); + super.initState(); + print("DEBUG: _MainPageState initstate called"); + WidgetsBinding.instance.addPostFrameCallback((_) { + print("DEBUG: addPostFrameCallback called"); + context.read().init(); + }); super.initState(); } @override Widget build(BuildContext context) { + print("DEBUG: _MainPageState build called"); return StateHandler( - onRetry: context.read().init, + onRetry: () => { + print("DEBUG: _MainPageState onRetry called"), + context.read().init + }, state: context.watch(), - builder: (context, state) => ListView( + builder: (context, state) { + print("DEBUG: FutureBuilder waiting"); + print("DEBUG: FutureBuilder state.stories.isNotEmpty: ${state.stories.isNotEmpty}"); + print("DEBUG: FutureBuilder state.content: ${state.content!.lists}"); + print("DEBUG: FutureBuilder state.content != null: ${state.content != null}"); + print("DEBUG: FutureBuilder state.content!.lists.isNotEmpty: ${state.content!.lists.isNotEmpty}"); + return ListView( padding: const EdgeInsets.symmetric(vertical: 16), children: [ if (state.stories.isNotEmpty) StorySection(stories: state.stories), const SizedBox(height: 12), const MainPageMainContent(), - Builder(builder: (context) { final List pageContent = []; if (state.content != null && state.content!.lists.isNotEmpty) { final lists = state.content!.lists; - + for (int i = 0; i < lists.length; i++) { final currentList = lists[i]; - + if (i == 4) { pageContent.add( Padding( @@ -107,21 +121,23 @@ class _MainPageState extends State { ), ); } - + pageContent.add(_MainPageSection( list: currentList, isLast: i == lists.length - 1, )); - + if (currentList.type == 'startup') { pageContent.add(_SwotSection(swotItems: state.swotItems)); } } } + print("DEBUG: FutureBuilder error"); return Column(children: pageContent); }), ], - ), + ); + }, ); } } @@ -135,7 +151,7 @@ class _SwotSection extends StatelessWidget { if (swotItems.isEmpty) { return const SizedBox.shrink(); } - + return Padding( padding: const EdgeInsets.all(0.0), child: Column( @@ -288,8 +304,7 @@ class _MainPageSection extends StatelessWidget { if (list.contents.isEmpty) { return const SizedBox(); } - - // This condition handles the "Soha" module, which should not display the Opportunity/Threat module. + if (list.type == 'delphi') { return Column( children: [ @@ -298,8 +313,7 @@ class _MainPageSection extends StatelessWidget { ], ); } - - // For all other modules, display as before. + return Column( children: [ _buildSectionHeader(context, icon), @@ -386,4 +400,4 @@ class _MainPageSection extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/lib/views/home/main/main_page_state.dart b/lib/views/home/main/main_page_state.dart index 08aefbf..b8eb775 100644 --- a/lib/views/home/main/main_page_state.dart +++ b/lib/views/home/main/main_page_state.dart @@ -24,12 +24,19 @@ class MainPageState extends CoreProvier { List swotItems = []; Future _getMainPageContent() async { + print("DEBUG: _getMainPageContent started"); final service = RequestService(RequestHelper.mainPageContent); await service.httpGet(); if (service.isSuccess) { + print("DEBUG: _getMainPageContent success"); content = MainPageContent.fromJson(service.result); + print("DEBUG: _getMainPageContent service.result: ${service.result}"); unread = service.result['unread']; + print("DEBUG: __getMainPageContent unread: $unread, content: $content"); + notifyListeners(); } else { + print("DEBUG: _getMainPageContent failed state"); + notifyListeners(); throw Exception('Failed to load main page content'); } } @@ -43,14 +50,14 @@ class MainPageState extends CoreProvier { try { swotItems = await SwotService.fetchSwotItems(); } catch (e) { - + print(e); } } Future _fetchStories() async { try { stories = await StoryService.getStories(); - print("Fetched ${stories.length} stories."); + // print("Fetched ${stories.length} stories."); } catch (e) { stories = []; debugPrint("Could not fetch stories: $e"); @@ -58,6 +65,7 @@ class MainPageState extends CoreProvier { } void init() { + print("DEBUG: MainPageState init called"); Future.delayed(Duration.zero, () async { appState = AppState.busy; try { @@ -71,6 +79,7 @@ class MainPageState extends CoreProvier { appState = AppState.failed; } }); + _getMainPageContent(); } void markChangeHandler(String type, int id, bool value) { diff --git a/lib/views/news/news_details/news_details.dart b/lib/views/news/news_details/news_details.dart index fda8eb9..d6a021e 100644 --- a/lib/views/news/news_details/news_details.dart +++ b/lib/views/news/news_details/news_details.dart @@ -31,7 +31,7 @@ class _NewsDetailsState extends State { Future.delayed(Duration.zero, () { state.getNewsDetails(widget.pageData['id']); }); - if(widget.pageData['goToComment'] != null){ + if (widget.pageData['goToComment'] != null) { state.openComments = widget.pageData['goToComment']; } super.initState(); @@ -42,7 +42,8 @@ class _NewsDetailsState extends State { return Scaffold( body: Consumer( builder: (context, state, child) => StateHandler( - onRetry: () => state.getNewsDetails(state.currentNews.id), + onRetry: () => + state.getNewsDetails(widget.pageData['id']), state: state, builder: (context, state) => Stack( children: [ diff --git a/lib/views/news/news_details/news_details_state.dart b/lib/views/news/news_details/news_details_state.dart index de02437..c4c2dea 100644 --- a/lib/views/news/news_details/news_details_state.dart +++ b/lib/views/news/news_details/news_details_state.dart @@ -17,6 +17,7 @@ class NewsDetailsState extends CoreProvier { int _trackingTimerCounter = 0; bool isFetchingNewItem = false; final List relatedQueue = []; + bool _isRequestInProgress = false; int _currentIndex = 0; int get currentIndex => _currentIndex; @@ -25,62 +26,95 @@ class NewsDetailsState extends CoreProvier { NewsDetailsData get currentNews => news[_currentIndex]!; Future getNewsDetails(int id, {bool? isForward}) async { + // Prevent duplicate requests + if (_isRequestInProgress) { + return; + } + _isRequestInProgress = true; + + // Set loading state if (isForward == null) { appState = AppState.busy; } else { isFetchingNewItem = true; notifyListeners(); } - final service = RequestService(RequestHelper.newsDetails(id, args)); - await service.httpGet(); - _handleTracking(sendRequest: isForward != null); - if (service.isSuccess) { - final result = service.result; - final newsItem = NewsDetailsData.fromJson(result['news']); - if (args.page == 0) { - news.add(newsItem); - initialIndex = 0; + + try { + // Wait for token to be ready + if (RequestService.token == null || RequestService.token!.isEmpty) { + await Future.delayed(const Duration(milliseconds: 500)); + if (RequestService.token == null || RequestService.token!.isEmpty) { + throw Exception('Token not available'); + } + } + + final service = RequestService(RequestHelper.newsDetails(id, args)); + await service.httpGet(); + _handleTracking(sendRequest: isForward != null); + + if (service.isSuccess) { + final result = service.result; + final newsItem = NewsDetailsData.fromJson(result['news']); + if (args.page == 0) { + news.add(newsItem); + initialIndex = 0; + appState = AppState.idle; + isFetchingNewItem = false; + _isRequestInProgress = false; + return; + } + NewsDetailsData? prevNews; + if (result['prevNews'].isNotEmpty) { + prevNews = NewsDetailsData.fromJson(result['prevNews']); + } + 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--; + } + isFetchingNewItem = false; + if (currentNews.contents.length == 1) { + getRelatedContents(); + } appState = AppState.idle; - return; - } - NewsDetailsData? prevNews; - if (result['prevNews'].isNotEmpty) { - prevNews = NewsDetailsData.fromJson(result['prevNews']); - } - 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); + } else { + isFetchingNewItem = false; + if (isForward == null) { + appState = AppState.failed; + } else { + notifyListeners(); } - 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--; } + } catch (e) { + print('Error fetching news details: $e'); isFetchingNewItem = false; - if (currentNews.contents.length == 1) { - getRelatedContents(); + if (isForward == null) { + appState = AppState.failed; } - appState = AppState.idle; - return; - } - if (isForward == null) { - appState = AppState.failed; + notifyListeners(); + } finally { + _isRequestInProgress = false; } } @@ -153,4 +187,4 @@ class NewsDetailsState extends CoreProvier { _trackingTimer.cancel(); super.dispose(); } -} +} \ No newline at end of file diff --git a/lib/views/profile/general_settings/settings.dart b/lib/views/profile/general_settings/settings.dart index 4658ac6..c7cad4d 100644 --- a/lib/views/profile/general_settings/settings.dart +++ b/lib/views/profile/general_settings/settings.dart @@ -56,7 +56,8 @@ class _GeneralSettingsState extends State { appBarData: AppBarData(hasBack: true, title: 'تنظیمات'), children: [ DidvanCard( - child: MenuOption( + child: + MenuOption( title: 'زمان دریافت اعلان', onTap: () => Navigator.of(context).pushNamed( Routes.notificationTime, diff --git a/lib/views/profile/profile.dart b/lib/views/profile/profile.dart index 927b1ac..75c4eeb 100644 --- a/lib/views/profile/profile.dart +++ b/lib/views/profile/profile.dart @@ -94,47 +94,47 @@ class _ProfilePageState extends State { padding: const EdgeInsets.only(right: 8.0, top: 8), child: Column( children: [ - Padding( - padding: const EdgeInsets.symmetric( - vertical: 12.0), - child: MenuOption( - title: 'زمان دریافت اعلان', - onTap: () => - Navigator.of(context).pushNamed( - Routes.notificationTime, - arguments: { - "fromFav": false, - 'onTimeChanged': () => Future.delayed( - Duration.zero, - () => state.getTime(), - ) - }, - ), - icon: DidvanIcons.notification_regular, - suffix: state.time, - // suffix: 'از${DateTimeUtils.normalizeTimeDuration( - // Duration(minutes: state.notificationTimeRange[0]), - // )} تا ${DateTimeUtils.normalizeTimeDuration( - // Duration(minutes: state.notificationTimeRange[1]), - // )}', - ), - ), - Padding( - padding: const EdgeInsets.symmetric( - vertical: 12.0), - child: MenuOption( - title: 'شخصی سازی محتوا', - onTap: () => Navigator.of(context) - .pushNamed(Routes.favouritesStep, - arguments: {"toTimer": false}), - icon: DidvanIcons.note_regular, - // suffix: 'از${DateTimeUtils.normalizeTimeDuration( - // Duration(minutes: state.notificationTimeRange[0]), - // )} تا ${DateTimeUtils.normalizeTimeDuration( - // Duration(minutes: state.notificationTimeRange[1]), - // )}', - ), - ), + // Padding( + // padding: const EdgeInsets.symmetric( + // vertical: 12.0), + // child: MenuOption( + // title: 'زمان دریافت اعلان', + // onTap: () => + // Navigator.of(context).pushNamed( + // Routes.notificationTime, + // arguments: { + // "fromFav": false, + // 'onTimeChanged': () => Future.delayed( + // Duration.zero, + // () => state.getTime(), + // ) + // }, + // ), + // icon: DidvanIcons.notification_regular, + // suffix: state.time, + // // suffix: 'از${DateTimeUtils.normalizeTimeDuration( + // // Duration(minutes: state.notificationTimeRange[0]), + // // )} تا ${DateTimeUtils.normalizeTimeDuration( + // // Duration(minutes: state.notificationTimeRange[1]), + // // )}', + // ), + // ), + // Padding( + // padding: const EdgeInsets.symmetric( + // vertical: 12.0), + // child: MenuOption( + // title: 'شخصی سازی محتوا', + // onTap: () => Navigator.of(context) + // .pushNamed(Routes.favouritesStep, + // arguments: {"toTimer": false}), + // icon: DidvanIcons.note_regular, + // // suffix: 'از${DateTimeUtils.normalizeTimeDuration( + // // Duration(minutes: state.notificationTimeRange[0]), + // // )} تا ${DateTimeUtils.normalizeTimeDuration( + // // Duration(minutes: state.notificationTimeRange[1]), + // // )}', + // ), + // ), Padding( padding: const EdgeInsets.symmetric( vertical: 12.0), diff --git a/lib/views/radar/radar_details/radar_details_state.dart b/lib/views/radar/radar_details/radar_details_state.dart index 1c9c6e3..ef3e3f8 100644 --- a/lib/views/radar/radar_details/radar_details_state.dart +++ b/lib/views/radar/radar_details/radar_details_state.dart @@ -18,6 +18,7 @@ class RadarDetailsState extends CoreProvier { bool isFetchingNewItem = false; final List relatedQueue = []; bool openComments = false; + bool _isRequestInProgress = false; int _currentIndex = 0; int get currentIndex => _currentIndex; @@ -31,66 +32,98 @@ class RadarDetailsState extends CoreProvier { } Future getRadarDetails(int id, {bool? isForward}) async { + // Prevent duplicate requests + if (_isRequestInProgress) { + return; + } + _isRequestInProgress = true; + + // Set loading state if (isForward == null) { appState = AppState.busy; } else { isFetchingNewItem = true; notifyListeners(); } - final service = RequestService(RequestHelper.radarDetails(id, args)); - await service.httpGet(); - _handleTracking(sendRequest: isForward != null); - if (service.isSuccess) { - final result = service.result; - final radar = RadarDetailsData.fromJson(result['radar']); - if (args.page == 0) { - radars.add(radar); - initialIndex = 0; + + try { + // Wait for token to be ready + if (RequestService.token == null || RequestService.token!.isEmpty) { + await Future.delayed(const Duration(milliseconds: 500)); + if (RequestService.token == null || RequestService.token!.isEmpty) { + throw Exception('Token not available'); + } + } + + final service = RequestService(RequestHelper.radarDetails(id, args)); + await service.httpGet(); + _handleTracking(sendRequest: isForward != null); + + if (service.isSuccess) { + final result = service.result; + final radar = RadarDetailsData.fromJson(result['radar']); + if (args.page == 0) { + radars.add(radar); + initialIndex = 0; + appState = AppState.idle; + isFetchingNewItem = false; + _isRequestInProgress = false; + return; + } + + RadarDetailsData? prevRadar; + if (result['prevRadar'].isNotEmpty) { + prevRadar = RadarDetailsData.fromJson(result['prevRadar']); + } + + RadarDetailsData? nextRadar; + if (result['nextRadar'].isNotEmpty) { + nextRadar = RadarDetailsData.fromJson(result['nextRadar']); + } + + if (isForward == null) { + radars.addAll(List.generate(max(radar.order - 2, 0), (index) => null)); + if (prevRadar != null) { + radars.add(prevRadar); + } + radars.add(radar); + if (nextRadar != null) { + radars.add(nextRadar); + } + _currentIndex = initialIndex = radar.order - 1; + } else if (isForward) { + if (!exists(nextRadar) && nextRadar != null) { + radars.add(nextRadar); + } + _currentIndex++; + } else if (!isForward) { + if (!exists(prevRadar) && prevRadar != null) { + radars[_currentIndex - 2] = prevRadar; + } + _currentIndex--; + } + isFetchingNewItem = false; + if (currentRadar.contents.length == 1) { + getRelatedContents(); + } appState = AppState.idle; - return; - } - - RadarDetailsData? prevRadar; - if (result['prevRadar'].isNotEmpty) { - prevRadar = RadarDetailsData.fromJson(result['prevRadar']); - } - - RadarDetailsData? nextRadar; - if (result['nextRadar'].isNotEmpty) { - nextRadar = RadarDetailsData.fromJson(result['nextRadar']); - } - - if (isForward == null) { - radars.addAll(List.generate(max(radar.order - 2, 0), (index) => null)); - if (prevRadar != null) { - radars.add(prevRadar); + } else { + isFetchingNewItem = false; + if (isForward == null) { + appState = AppState.failed; + } else { + notifyListeners(); } - radars.add(radar); - if (nextRadar != null) { - radars.add(nextRadar); - } - _currentIndex = initialIndex = radar.order - 1; - } else if (isForward) { - if (!exists(nextRadar) && nextRadar != null) { - radars.add(nextRadar); - } - _currentIndex++; - } else if (!isForward) { - if (!exists(prevRadar) && prevRadar != null) { - radars[_currentIndex - 2] = prevRadar; - } - _currentIndex--; } + } catch (e) { + print('Error fetching radar details: $e'); isFetchingNewItem = false; - if (currentRadar.contents.length == 1) { - getRelatedContents(); + if (isForward == null) { + appState = AppState.failed; } - appState = AppState.idle; - return; - } - //why? total page state shouldn't die! - if (isForward == null) { - appState = AppState.failed; + notifyListeners(); + } finally { + _isRequestInProgress = false; } } @@ -164,4 +197,4 @@ class RadarDetailsState extends CoreProvier { _trackingTimer.cancel(); super.dispose(); } -} +} \ No newline at end of file diff --git a/lib/views/splash/splash.dart b/lib/views/splash/splash.dart index d6e7aa8..6ac155c 100644 --- a/lib/views/splash/splash.dart +++ b/lib/views/splash/splash.dart @@ -87,7 +87,9 @@ class _SplashState extends State { Future _initialize(ThemeProvider themeProvider, UserProvider userProvider, MediaProvider mediaProvider) async { try { + print("checking if is in browser"); if (kIsWeb) { + print("detected browser"); html.window.onBeforeUnload.listen((event) { StorageService.webStorage .removeWhere((key, value) => key == 'image-cache'); @@ -103,20 +105,22 @@ class _SplashState extends State { await AppInitializer.setupServices(navigatorKey.currentContext!); final String? token = await userProvider.setAndGetToken(); + print("detected token as $token"); if (token != null) { + RequestService.token = token; if (!kIsWeb) { await mediaProvider.getDownloadsList(); } - RequestService.token = token; - final result = await userProvider.getUserInfo(); if (!result) { + print("no results were returned for user info"); try { StorageService.delete(key: 'token'); } catch (e) { + print("error in case of no user info result: $e"); // catch } @@ -127,28 +131,33 @@ class _SplashState extends State { return; } - + print("got results for user info: $result"); await ServerDataProvider.getData(); } - - // --- بخش اصلاح شده --- - // ابتدا بررسی می‌کنیم که کاربر باید به کدام صفحه اصلی برود - final String destinationRoute = token == null ? Routes.authenticaion : Routes.home; - final dynamic routeArguments = token == null ? false : {'showDialogs': true}; - // اگر لینک ورودی وجود داشت، آن را به عنوان آرگومان به صفحه Home می‌فرستیم - if (destinationRoute == Routes.home && initialURI != null) { - (routeArguments as Map)['deepLinkUri'] = initialURI; - initialURI = null; // لینک را مصرف می‌کنیم تا دوباره استفاده نشود + print("token route is $token"); + String extractedPath = initialURI?.path.toString() == '/' ? Routes.home : initialURI?.path.toString() ?? Routes.home; + final String destinationRoute = token == null ? Routes.authenticaion : extractedPath; + // Set showDialogs to false to prevent welcome popups + dynamic routeArguments = token == null ? {'isResetPassword': false} : {'showDialogs': false}; + + + if (destinationRoute == Routes.home) { + print("destination route was home and init uri is $initialURI"); + // (routeArguments as Map)['deepLinkUri'] = initialURI; + initialURI = null; } - // در نهایت به مسیر تعیین شده می‌رویم + if(destinationRoute == Routes.authenticaion){ + print("destination route is auth route"); + routeArguments = false; + } + + print("destination route: $destinationRoute, route args: $routeArguments"); await navigatorKey.currentState!.pushReplacementNamed( destinationRoute, arguments: routeArguments, ); - // --- پایان بخش اصلاح شده --- - } catch (e) { setState(() { _errorOccured = true; diff --git a/lib/views/webview/web_view.dart b/lib/views/webview/web_view.dart index 84ac7d8..b8feeaf 100644 --- a/lib/views/webview/web_view.dart +++ b/lib/views/webview/web_view.dart @@ -26,12 +26,14 @@ class _WebViewState extends State { }; void _onProgress(int progress) { + print('🌐 WebView loading progress: $progress%'); // Add logging setState(() { this.progress = progress; }); } void _onPageFinished(String url) async { + print('✅ WebView finished loading: $url'); // Add logging await Future.delayed(const Duration(seconds: 2)); setState(() { if (progress == 100) loading = false; @@ -40,21 +42,24 @@ class _WebViewState extends State { @override void initState() { + print('🔄 Initializing WebView with URL: ${widget.src}'); // Add logging controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate( NavigationDelegate( onProgress: _onProgress, - onPageStarted: (String url) {}, + onPageStarted: (String url) { + print('▶️ WebView started loading: $url'); // Add logging + }, onPageFinished: _onPageFinished, - onHttpError: (HttpResponseError error) {}, + onHttpError: (HttpResponseError error) { + // print('❌ WebView HTTP error: ${error.statusCode} on ${error.url}'); // Add logging + }, onWebResourceError: (WebResourceError error) { - // navigatorKey.currentState!.pop(); + // print('❌ WebView resource error: ${error.description} on ${error.failingUrl}'); // Add logging }, onNavigationRequest: (NavigationRequest request) { - // if (request.url.startsWith('https://www.youtube.com/')) { - // return NavigationDecision.prevent; - // } + print('🔀 WebView navigation request to: ${request.url}'); // Add logging return NavigationDecision.navigate; }, ), @@ -77,7 +82,7 @@ class _WebViewState extends State { child: Scaffold( appBar: AppBar( title: const DidvanText( - 'بازگشت به دیدوان', + 'بازگشت', ), leading: const BackButton(), ), diff --git a/lib/views/widgets/didvan/text_field.dart b/lib/views/widgets/didvan/text_field.dart index b538666..c904b24 100644 --- a/lib/views/widgets/didvan/text_field.dart +++ b/lib/views/widgets/didvan/text_field.dart @@ -109,7 +109,7 @@ class _DidvanTextFieldState extends State { ? TextDirection.ltr : TextDirection.rtl, child: Padding( - padding: const EdgeInsets.fromLTRB(kIsWeb?8:8,kIsWeb?4:8,kIsWeb?8:0,kIsWeb?0:8), + padding: const EdgeInsets.fromLTRB(kIsWeb?8:4,kIsWeb?4:4,kIsWeb?8:0,kIsWeb?0:8), child: Center( child: TextFormField( inputFormatters: [ diff --git a/makefile b/makefile index 87723a8..5708751 100644 --- a/makefile +++ b/makefile @@ -1,2 +1,9 @@ update_js: - cp ./lib/assets/js/main.js ./build/flutter_assets/lib/assets/js/main.js \ No newline at end of file + cp ./lib/assets/js/main.js ./build/flutter_assets/lib/assets/js/main.js + +zip_web: + powershell Compress-Archive -Path build\web\* -DestinationPath build.zip -Force + +build_web: + flutter build web +