Merge pull request 'dev/fix-news' (#2) from dev/fix-news into master

Reviewed-on: #2
This commit is contained in:
Mr.Jebelli 2025-07-16 11:58:58 +00:00
commit 0aec835d8a
21 changed files with 465 additions and 298 deletions

15
.vscode/launch.json vendored Normal file
View File

@ -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}"
}
]
}

BIN
build.zip Normal file

Binary file not shown.

View File

@ -1,3 +1,5 @@
// lib/main.dart
// ignore_for_file: deprecated_member_use // ignore_for_file: deprecated_member_use
import 'dart:async'; import 'dart:async';
@ -7,6 +9,8 @@ import 'package:bot_toast/bot_toast.dart';
import 'package:didvan/config/theme_data.dart'; import 'package:didvan/config/theme_data.dart';
import 'package:didvan/firebase_options.dart'; import 'package:didvan/firebase_options.dart';
import 'package:didvan/models/notification_message.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/media.dart';
import 'package:didvan/providers/theme.dart'; import 'package:didvan/providers/theme.dart';
import 'package:didvan/providers/user.dart'; import 'package:didvan/providers/user.dart';
@ -32,13 +36,11 @@ import 'package:home_widget/home_widget.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:flutter_downloader/flutter_downloader.dart'; import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:sentry_flutter/sentry_flutter.dart';
// پکیج جدید برای Deep Linking
import 'package:app_links/app_links.dart'; import 'package:app_links/app_links.dart';
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); import 'services/network/request.dart';
// متغیر استاتیک برای نگهداری لینک اولیه final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
Uri? initialURI; Uri? initialURI;
@pragma('vm:entry-point') @pragma('vm:entry-point')
@ -49,6 +51,8 @@ Future<void> _backgroundCallbackHomeWidget(Uri? uri) async {
} }
void main() async { void main() async {
runZonedGuarded(
() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
try { try {
if (!kIsWeb) { if (!kIsWeb) {
@ -57,18 +61,14 @@ void main() async {
await NotificationService.initializeNotification(); await NotificationService.initializeNotification();
try { try {
if (Platform.isAndroid) { if (Platform.isAndroid) {
await FlutterDownloader.initialize( await FlutterDownloader.initialize(debug: true, ignoreSsl: true);
debug: true,
ignoreSsl: true
);
} }
} catch (e) { } catch (e) {
e.printError(); e.printError();
} }
} }
await Firebase.initializeApp( await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
options: DefaultFirebaseOptions.currentPlatform);
await FirebaseApi().initNotification(); await FirebaseApi().initNotification();
} catch (e) { } catch (e) {
debugPrint(e.toString()); debugPrint(e.toString());
@ -76,13 +76,17 @@ void main() async {
await SentryFlutter.init( await SentryFlutter.init(
(options) { (options) {
options.dsn = options.dsn = 'https://a4cfcaa7d67471240d295c25c968d91d@o4508585857384448.ingest.de.sentry.io/4508585886548048';
'https://a4cfcaa7d67471240d295c25c968d91d@o4508585857384448.ingest.de.sentry.io/4508585886548048';
options.tracesSampleRate = 1.0; options.tracesSampleRate = 1.0;
options.profilesSampleRate = 1.0; options.profilesSampleRate = 1.0;
}, },
appRunner: () => runApp(const Didvan()), appRunner: () => runApp(const Didvan()),
); );
},
(error, stack) {
Sentry.captureException(error, stackTrace: stack);
},
);
} }
class Didvan extends StatefulWidget { class Didvan extends StatefulWidget {
@ -100,41 +104,57 @@ class _DidvanState extends State<Didvan> with WidgetsBindingObserver {
void initState() { void initState() {
super.initState(); super.initState();
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
// مقداردهی اولیه و گوش دادن به لینکها در اینجا انجام میشود
_initDeepLinks(); _initDeepLinks();
} }
@override @override
void dispose() { void dispose() {
WidgetsBinding.instance.removeObserver(this); WidgetsBinding.instance.removeObserver(this);
_linkSubscription?.cancel(); // لغو کردن subscription _linkSubscription?.cancel();
if (MediaService.currentPodcast != null) { if (MediaService.currentPodcast != null) {
MediaService.audioPlayer.dispose(); MediaService.audioPlayer.dispose();
} }
super.dispose(); super.dispose();
} }
/// منطق جدید برای مدیریت لینکها با app_links
Future<void> _initDeepLinks() async { Future<void> _initDeepLinks() async {
_appLinks = AppLinks(); _appLinks = AppLinks();
// لینک اولیه را فقط در متغیر ذخیره میکنیم
initialURI = await _appLinks.getInitialLink(); initialURI = await _appLinks.getInitialLink();
// به لینکهای جدید زمانی که اپلیکیشن باز است گوش میدهیم
_linkSubscription = _appLinks.uriLinkStream.listen((uri) { _linkSubscription = _appLinks.uriLinkStream.listen((uri) {
_navigateTo(uri); _navigateTo(uri);
}); });
} }
/// تابع کمکی برای ناوبری
void _navigateTo(Uri uri) { void _navigateTo(Uri uri) {
if (mounted) { if (mounted) {
String path = uri.path; String path = uri.path;
if (uri.fragment.isNotEmpty) { final Map<String, String> params = uri.queryParameters;
path = "/${uri.fragment}";
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<Didvan> with WidgetsBindingObserver {
if (!kIsWeb) { if (!kIsWeb) {
if (state == AppLifecycleState.resumed) { if (state == AppLifecycleState.resumed) {
var r = await HomeWidget.getWidgetData("cRoute", defaultValue: ''); var r = await HomeWidget.getWidgetData("cRoute", defaultValue: '');
if (r!.toString() != Routes.splash) { if (r.toString() != Routes.splash) {
await HomeWidgetRepository.decideWhereToGo(); await HomeWidgetRepository.decideWhereToGo();
NotificationMessage? data = HomeWidgetRepository.data; NotificationMessage? data = HomeWidgetRepository.data;
if (data != null) { if (data != null) {
await HomeWidgetRepository.decideWhereToGoNotif(); await HomeWidgetRepository.decideWhereToGoNotif();
} }

View File

@ -1,3 +1,5 @@
// lib/services/network/request.dart
// ignore_for_file: empty_catches // ignore_for_file: empty_catches
import 'dart:convert'; import 'dart:convert';
@ -15,7 +17,7 @@ import 'package:mime/mime.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
class RequestService { class RequestService {
static late String token; static String? token; // Made token nullable
int? statusCode; int? statusCode;
dynamic data(String s) { dynamic data(String s) {
@ -48,12 +50,9 @@ class RequestService {
}) { }) {
if (body != null) _requestBody = body; if (body != null) _requestBody = body;
if (requestHeaders != null) _headers.addAll(requestHeaders); if (requestHeaders != null) _headers.addAll(requestHeaders);
if (useAutherization) _headers.addAll({'Authorization': 'Bearer $token'}); if (useAutherization && token != null) { // Check if token is not null
// if (kDebugMode) { _headers.addAll({'Authorization': 'Bearer $token'});
// try { }
// print('Authorization : Bearer $token');
// } catch (e) {}
// }
if (body != null) _requestBody = body; if (body != null) _requestBody = body;
} }

View File

@ -42,7 +42,6 @@ class _AuthenticationState extends State<Authentication> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
resizeToAvoidBottomInset: false,
body: Consumer<AuthenticationState>( body: Consumer<AuthenticationState>(
builder: (context, state, child) => WillPopScope( builder: (context, state, child) => WillPopScope(
onWillPop: () async { onWillPop: () async {

View File

@ -49,7 +49,8 @@ class AuthenticationLayout extends StatelessWidget {
SliverPadding( SliverPadding(
padding: const EdgeInsets.symmetric(horizontal: 20), padding: const EdgeInsets.symmetric(horizontal: 20),
sliver: SliverFillRemaining( sliver: SliverFillRemaining(
hasScrollBody: false, hasScrollBody: true,
fillOverscroll: true,
child: Column( child: Column(
children: [ children: [
for (var i = 0; i < children.length; i++) children[i], for (var i = 0; i < children.length; i++) children[i],

View File

@ -24,6 +24,7 @@ class CustomizeCategoryState extends CoreProvier {
Future<void> getFavourites() async { Future<void> getFavourites() async {
appState = AppState.busy; appState = AppState.busy;
update();
final service = RequestService( final service = RequestService(
RequestHelper.favourites(), RequestHelper.favourites(),
@ -31,13 +32,19 @@ class CustomizeCategoryState extends CoreProvier {
await service.httpGet(); await service.httpGet();
if (service.isSuccess) { if (service.isSuccess) {
faves.clear(); faves.clear();
final favourites = service.data('types'); final favouritesData = service.data('types');
for (var i = 0; i < favourites.length; i++) { if (favouritesData is List) {
faves.add(FavoritesResponse.fromJson(favourites[i])); for (var i = 0; i < favouritesData.length; i++) {
faves.add(FavoritesResponse.fromJson(favouritesData[i]));
} }
}
// اضافه کردن مستقیم بخش فرصت و تهدید
faves.add(FavoritesResponse(id: 999, name: 'فرصت و تهدید', selected: false));
selectedFavIds.clear(); selectedFavIds.clear();
for (var element in faves) { for (var element in faves) {
if (element.selected!) { if (element.selected == true) {
selectedFavIds.add(element.id!); selectedFavIds.add(element.id!);
} }
} }

View File

@ -78,7 +78,9 @@ class _FavoritesStepState extends State<FavoritesStep> {
asset: Assets.emptyChat, asset: Assets.emptyChat,
title: 'اولین نظر را بنویسید...', title: 'اولین نظر را بنویسید...',
), ),
builder: (context, state, index) => Center( builder: (context, state, index) => Column(
children: [
Center(
child: Container( child: Container(
height: 48, height: 48,
decoration: BoxDecoration( decoration: BoxDecoration(
@ -86,13 +88,13 @@ class _FavoritesStepState extends State<FavoritesStep> {
border: Border.all( border: Border.all(
width: 1, width: 1,
color: state.faves[index].selected! color: state.faves[index].selected!
? Theme.of(context) ? Theme.of(context).colorScheme.focusedBorder
.colorScheme : Theme.of(context).colorScheme.cardBorder
.focusedBorder ),
: Theme.of(context).colorScheme.cardBorder),
color: state.faves[index].selected! color: state.faves[index].selected!
? Theme.of(context).colorScheme.focused ? Theme.of(context).colorScheme.focused
: Theme.of(context).colorScheme.cardBorder), : Theme.of(context).colorScheme.cardBorder
),
child: CustomizeCategoryCheckbox( child: CustomizeCategoryCheckbox(
title: state.faves[index].name!, title: state.faves[index].name!,
value: state.faves[index].selected, value: state.faves[index].selected,
@ -100,7 +102,14 @@ class _FavoritesStepState extends State<FavoritesStep> {
onChanged: (val) { onChanged: (val) {
state.changeSelected( 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),
],
), ),
), ),
), ),

View File

@ -142,9 +142,10 @@ class _HomeState extends State<Home>
@override @override
void initState() { void initState() {
if (widget.showDialogs ?? false) { // Remove dialog showing logic to prevent welcome popups
_showDialog(context); // if (widget.showDialogs ?? false) {
} // _showDialog(context);
// }
final state = context.read<HomeState>(); final state = context.read<HomeState>();
DesignConfig.updateSystemUiOverlayStyle(); DesignConfig.updateSystemUiOverlayStyle();

View File

@ -82,6 +82,7 @@ class HomeState extends CoreProvier {
); );
await service.httpGet(); await service.httpGet();
if (service.isSuccess) { if (service.isSuccess) {
print("DEBUG : Homestate is succes");
lastPage = service.result['lastPage']; lastPage = service.result['lastPage'];
results.addAll( results.addAll(
List<OverviewData>.from( List<OverviewData>.from(
@ -94,12 +95,14 @@ class HomeState extends CoreProvier {
appState = AppState.idle; appState = AppState.idle;
return; return;
} }
print("DEBUG : Homestate is NOT succes");
appState = AppState.failed; appState = AppState.failed;
} }
Future<void> searchAll({required int page}) async { Future<void> searchAll({required int page}) async {
this.page = page; this.page = page;
if (page == 1) { if (page == 1) {
print("DEBUG : serach is busy");
results.clear(); results.clear();
appState = AppState.busy; appState = AppState.busy;
} }
@ -115,6 +118,7 @@ class HomeState extends CoreProvier {
); );
await service.httpGet(); await service.httpGet();
if (service.isSuccess) { if (service.isSuccess) {
print("DEBUG : HTTPget Home is succes");
lastPage = service.result['lastPage']; lastPage = service.result['lastPage'];
unreadCount = service.result['unread'] ?? unreadCount; unreadCount = service.result['unread'] ?? unreadCount;
results.addAll( results.addAll(
@ -128,6 +132,7 @@ class HomeState extends CoreProvier {
appState = AppState.idle; appState = AppState.idle;
return; return;
} }
print("DEBUG : Homestate is faild");
appState = AppState.failed; appState = AppState.failed;
} }

View File

@ -33,22 +33,36 @@ class MainPage extends StatefulWidget {
class _MainPageState extends State<MainPage> { class _MainPageState extends State<MainPage> {
@override @override
void initState() { void initState() {
super.initState();
print("DEBUG: _MainPageState initstate called");
WidgetsBinding.instance.addPostFrameCallback((_) {
print("DEBUG: addPostFrameCallback called");
context.read<MainPageState>().init(); context.read<MainPageState>().init();
});
super.initState(); super.initState();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
print("DEBUG: _MainPageState build called");
return StateHandler<MainPageState>( return StateHandler<MainPageState>(
onRetry: context.read<MainPageState>().init, onRetry: () => {
print("DEBUG: _MainPageState onRetry called"),
context.read<MainPageState>().init
},
state: context.watch<MainPageState>(), state: context.watch<MainPageState>(),
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), padding: const EdgeInsets.symmetric(vertical: 16),
children: [ children: [
if (state.stories.isNotEmpty) StorySection(stories: state.stories), if (state.stories.isNotEmpty) StorySection(stories: state.stories),
const SizedBox(height: 12), const SizedBox(height: 12),
const MainPageMainContent(), const MainPageMainContent(),
Builder(builder: (context) { Builder(builder: (context) {
final List<Widget> pageContent = []; final List<Widget> pageContent = [];
if (state.content != null && state.content!.lists.isNotEmpty) { if (state.content != null && state.content!.lists.isNotEmpty) {
@ -118,10 +132,12 @@ class _MainPageState extends State<MainPage> {
} }
} }
} }
print("DEBUG: FutureBuilder error");
return Column(children: pageContent); return Column(children: pageContent);
}), }),
], ],
), );
},
); );
} }
} }
@ -289,7 +305,6 @@ class _MainPageSection extends StatelessWidget {
return const SizedBox(); return const SizedBox();
} }
// This condition handles the "Soha" module, which should not display the Opportunity/Threat module.
if (list.type == 'delphi') { if (list.type == 'delphi') {
return Column( return Column(
children: [ children: [
@ -299,7 +314,6 @@ class _MainPageSection extends StatelessWidget {
); );
} }
// For all other modules, display as before.
return Column( return Column(
children: [ children: [
_buildSectionHeader(context, icon), _buildSectionHeader(context, icon),

View File

@ -24,12 +24,19 @@ class MainPageState extends CoreProvier {
List<SwotItem> swotItems = []; List<SwotItem> swotItems = [];
Future<void> _getMainPageContent() async { Future<void> _getMainPageContent() async {
print("DEBUG: _getMainPageContent started");
final service = RequestService(RequestHelper.mainPageContent); final service = RequestService(RequestHelper.mainPageContent);
await service.httpGet(); await service.httpGet();
if (service.isSuccess) { if (service.isSuccess) {
print("DEBUG: _getMainPageContent success");
content = MainPageContent.fromJson(service.result); content = MainPageContent.fromJson(service.result);
print("DEBUG: _getMainPageContent service.result: ${service.result}");
unread = service.result['unread']; unread = service.result['unread'];
print("DEBUG: __getMainPageContent unread: $unread, content: $content");
notifyListeners();
} else { } else {
print("DEBUG: _getMainPageContent failed state");
notifyListeners();
throw Exception('Failed to load main page content'); throw Exception('Failed to load main page content');
} }
} }
@ -43,14 +50,14 @@ class MainPageState extends CoreProvier {
try { try {
swotItems = await SwotService.fetchSwotItems(); swotItems = await SwotService.fetchSwotItems();
} catch (e) { } catch (e) {
print(e);
} }
} }
Future<void> _fetchStories() async { Future<void> _fetchStories() async {
try { try {
stories = await StoryService.getStories(); stories = await StoryService.getStories();
print("Fetched ${stories.length} stories."); // print("Fetched ${stories.length} stories.");
} catch (e) { } catch (e) {
stories = []; stories = [];
debugPrint("Could not fetch stories: $e"); debugPrint("Could not fetch stories: $e");
@ -58,6 +65,7 @@ class MainPageState extends CoreProvier {
} }
void init() { void init() {
print("DEBUG: MainPageState init called");
Future.delayed(Duration.zero, () async { Future.delayed(Duration.zero, () async {
appState = AppState.busy; appState = AppState.busy;
try { try {
@ -71,6 +79,7 @@ class MainPageState extends CoreProvier {
appState = AppState.failed; appState = AppState.failed;
} }
}); });
_getMainPageContent();
} }
void markChangeHandler(String type, int id, bool value) { void markChangeHandler(String type, int id, bool value) {

View File

@ -31,7 +31,7 @@ class _NewsDetailsState extends State<NewsDetails> {
Future.delayed(Duration.zero, () { Future.delayed(Duration.zero, () {
state.getNewsDetails(widget.pageData['id']); state.getNewsDetails(widget.pageData['id']);
}); });
if(widget.pageData['goToComment'] != null){ if (widget.pageData['goToComment'] != null) {
state.openComments = widget.pageData['goToComment']; state.openComments = widget.pageData['goToComment'];
} }
super.initState(); super.initState();
@ -42,7 +42,8 @@ class _NewsDetailsState extends State<NewsDetails> {
return Scaffold( return Scaffold(
body: Consumer<NewsDetailsState>( body: Consumer<NewsDetailsState>(
builder: (context, state, child) => StateHandler<NewsDetailsState>( builder: (context, state, child) => StateHandler<NewsDetailsState>(
onRetry: () => state.getNewsDetails(state.currentNews.id), onRetry: () =>
state.getNewsDetails(widget.pageData['id']),
state: state, state: state,
builder: (context, state) => Stack( builder: (context, state) => Stack(
children: [ children: [

View File

@ -17,6 +17,7 @@ class NewsDetailsState extends CoreProvier {
int _trackingTimerCounter = 0; int _trackingTimerCounter = 0;
bool isFetchingNewItem = false; bool isFetchingNewItem = false;
final List<int> relatedQueue = []; final List<int> relatedQueue = [];
bool _isRequestInProgress = false;
int _currentIndex = 0; int _currentIndex = 0;
int get currentIndex => _currentIndex; int get currentIndex => _currentIndex;
@ -25,15 +26,33 @@ class NewsDetailsState extends CoreProvier {
NewsDetailsData get currentNews => news[_currentIndex]!; NewsDetailsData get currentNews => news[_currentIndex]!;
Future<void> getNewsDetails(int id, {bool? isForward}) async { Future<void> getNewsDetails(int id, {bool? isForward}) async {
// Prevent duplicate requests
if (_isRequestInProgress) {
return;
}
_isRequestInProgress = true;
// Set loading state
if (isForward == null) { if (isForward == null) {
appState = AppState.busy; appState = AppState.busy;
} else { } else {
isFetchingNewItem = true; isFetchingNewItem = true;
notifyListeners(); notifyListeners();
} }
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)); final service = RequestService(RequestHelper.newsDetails(id, args));
await service.httpGet(); await service.httpGet();
_handleTracking(sendRequest: isForward != null); _handleTracking(sendRequest: isForward != null);
if (service.isSuccess) { if (service.isSuccess) {
final result = service.result; final result = service.result;
final newsItem = NewsDetailsData.fromJson(result['news']); final newsItem = NewsDetailsData.fromJson(result['news']);
@ -41,6 +60,8 @@ class NewsDetailsState extends CoreProvier {
news.add(newsItem); news.add(newsItem);
initialIndex = 0; initialIndex = 0;
appState = AppState.idle; appState = AppState.idle;
isFetchingNewItem = false;
_isRequestInProgress = false;
return; return;
} }
NewsDetailsData? prevNews; NewsDetailsData? prevNews;
@ -77,10 +98,23 @@ class NewsDetailsState extends CoreProvier {
getRelatedContents(); getRelatedContents();
} }
appState = AppState.idle; appState = AppState.idle;
return; } else {
} isFetchingNewItem = false;
if (isForward == null) { if (isForward == null) {
appState = AppState.failed; appState = AppState.failed;
} else {
notifyListeners();
}
}
} catch (e) {
print('Error fetching news details: $e');
isFetchingNewItem = false;
if (isForward == null) {
appState = AppState.failed;
}
notifyListeners();
} finally {
_isRequestInProgress = false;
} }
} }

View File

@ -56,7 +56,8 @@ class _GeneralSettingsState extends State<GeneralSettings> {
appBarData: AppBarData(hasBack: true, title: 'تنظیمات'), appBarData: AppBarData(hasBack: true, title: 'تنظیمات'),
children: [ children: [
DidvanCard( DidvanCard(
child: MenuOption( child:
MenuOption(
title: 'زمان دریافت اعلان', title: 'زمان دریافت اعلان',
onTap: () => Navigator.of(context).pushNamed( onTap: () => Navigator.of(context).pushNamed(
Routes.notificationTime, Routes.notificationTime,

View File

@ -94,47 +94,47 @@ class _ProfilePageState extends State<ProfilePage> {
padding: const EdgeInsets.only(right: 8.0, top: 8), padding: const EdgeInsets.only(right: 8.0, top: 8),
child: Column( child: Column(
children: [ children: [
Padding( // Padding(
padding: const EdgeInsets.symmetric( // padding: const EdgeInsets.symmetric(
vertical: 12.0), // vertical: 12.0),
child: MenuOption( // child: MenuOption(
title: 'زمان دریافت اعلان', // title: 'زمان دریافت اعلان',
onTap: () => // onTap: () =>
Navigator.of(context).pushNamed( // Navigator.of(context).pushNamed(
Routes.notificationTime, // Routes.notificationTime,
arguments: { // arguments: {
"fromFav": false, // "fromFav": false,
'onTimeChanged': () => Future.delayed( // 'onTimeChanged': () => Future.delayed(
Duration.zero, // Duration.zero,
() => state.getTime(), // () => state.getTime(),
) // )
}, // },
), // ),
icon: DidvanIcons.notification_regular, // icon: DidvanIcons.notification_regular,
suffix: state.time, // suffix: state.time,
// suffix: 'از${DateTimeUtils.normalizeTimeDuration( // // suffix: 'از${DateTimeUtils.normalizeTimeDuration(
// Duration(minutes: state.notificationTimeRange[0]), // // Duration(minutes: state.notificationTimeRange[0]),
// )} تا ${DateTimeUtils.normalizeTimeDuration( // // )} تا ${DateTimeUtils.normalizeTimeDuration(
// Duration(minutes: state.notificationTimeRange[1]), // // Duration(minutes: state.notificationTimeRange[1]),
// )}', // // )}',
), // ),
), // ),
Padding( // Padding(
padding: const EdgeInsets.symmetric( // padding: const EdgeInsets.symmetric(
vertical: 12.0), // vertical: 12.0),
child: MenuOption( // child: MenuOption(
title: 'شخصی سازی محتوا', // title: 'شخصی سازی محتوا',
onTap: () => Navigator.of(context) // onTap: () => Navigator.of(context)
.pushNamed(Routes.favouritesStep, // .pushNamed(Routes.favouritesStep,
arguments: {"toTimer": false}), // arguments: {"toTimer": false}),
icon: DidvanIcons.note_regular, // icon: DidvanIcons.note_regular,
// suffix: 'از${DateTimeUtils.normalizeTimeDuration( // // suffix: 'از${DateTimeUtils.normalizeTimeDuration(
// Duration(minutes: state.notificationTimeRange[0]), // // Duration(minutes: state.notificationTimeRange[0]),
// )} تا ${DateTimeUtils.normalizeTimeDuration( // // )} تا ${DateTimeUtils.normalizeTimeDuration(
// Duration(minutes: state.notificationTimeRange[1]), // // Duration(minutes: state.notificationTimeRange[1]),
// )}', // // )}',
), // ),
), // ),
Padding( Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
vertical: 12.0), vertical: 12.0),

View File

@ -18,6 +18,7 @@ class RadarDetailsState extends CoreProvier {
bool isFetchingNewItem = false; bool isFetchingNewItem = false;
final List<int> relatedQueue = []; final List<int> relatedQueue = [];
bool openComments = false; bool openComments = false;
bool _isRequestInProgress = false;
int _currentIndex = 0; int _currentIndex = 0;
int get currentIndex => _currentIndex; int get currentIndex => _currentIndex;
@ -31,15 +32,33 @@ class RadarDetailsState extends CoreProvier {
} }
Future<void> getRadarDetails(int id, {bool? isForward}) async { Future<void> getRadarDetails(int id, {bool? isForward}) async {
// Prevent duplicate requests
if (_isRequestInProgress) {
return;
}
_isRequestInProgress = true;
// Set loading state
if (isForward == null) { if (isForward == null) {
appState = AppState.busy; appState = AppState.busy;
} else { } else {
isFetchingNewItem = true; isFetchingNewItem = true;
notifyListeners(); notifyListeners();
} }
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)); final service = RequestService(RequestHelper.radarDetails(id, args));
await service.httpGet(); await service.httpGet();
_handleTracking(sendRequest: isForward != null); _handleTracking(sendRequest: isForward != null);
if (service.isSuccess) { if (service.isSuccess) {
final result = service.result; final result = service.result;
final radar = RadarDetailsData.fromJson(result['radar']); final radar = RadarDetailsData.fromJson(result['radar']);
@ -47,6 +66,8 @@ class RadarDetailsState extends CoreProvier {
radars.add(radar); radars.add(radar);
initialIndex = 0; initialIndex = 0;
appState = AppState.idle; appState = AppState.idle;
isFetchingNewItem = false;
_isRequestInProgress = false;
return; return;
} }
@ -86,11 +107,23 @@ class RadarDetailsState extends CoreProvier {
getRelatedContents(); getRelatedContents();
} }
appState = AppState.idle; appState = AppState.idle;
return; } else {
} isFetchingNewItem = false;
//why? total page state shouldn't die!
if (isForward == null) { if (isForward == null) {
appState = AppState.failed; appState = AppState.failed;
} else {
notifyListeners();
}
}
} catch (e) {
print('Error fetching radar details: $e');
isFetchingNewItem = false;
if (isForward == null) {
appState = AppState.failed;
}
notifyListeners();
} finally {
_isRequestInProgress = false;
} }
} }

View File

@ -87,7 +87,9 @@ class _SplashState extends State<Splash> {
Future<void> _initialize(ThemeProvider themeProvider, Future<void> _initialize(ThemeProvider themeProvider,
UserProvider userProvider, MediaProvider mediaProvider) async { UserProvider userProvider, MediaProvider mediaProvider) async {
try { try {
print("checking if is in browser");
if (kIsWeb) { if (kIsWeb) {
print("detected browser");
html.window.onBeforeUnload.listen((event) { html.window.onBeforeUnload.listen((event) {
StorageService.webStorage StorageService.webStorage
.removeWhere((key, value) => key == 'image-cache'); .removeWhere((key, value) => key == 'image-cache');
@ -103,20 +105,22 @@ class _SplashState extends State<Splash> {
await AppInitializer.setupServices(navigatorKey.currentContext!); await AppInitializer.setupServices(navigatorKey.currentContext!);
final String? token = await userProvider.setAndGetToken(); final String? token = await userProvider.setAndGetToken();
print("detected token as $token");
if (token != null) { if (token != null) {
RequestService.token = token;
if (!kIsWeb) { if (!kIsWeb) {
await mediaProvider.getDownloadsList(); await mediaProvider.getDownloadsList();
} }
RequestService.token = token;
final result = await userProvider.getUserInfo(); final result = await userProvider.getUserInfo();
if (!result) { if (!result) {
print("no results were returned for user info");
try { try {
StorageService.delete(key: 'token'); StorageService.delete(key: 'token');
} catch (e) { } catch (e) {
print("error in case of no user info result: $e");
// catch // catch
} }
@ -127,28 +131,33 @@ class _SplashState extends State<Splash> {
return; return;
} }
print("got results for user info: $result");
await ServerDataProvider.getData(); await ServerDataProvider.getData();
} }
// --- بخش اصلاح شده --- print("token route is $token");
// ابتدا بررسی میکنیم که کاربر باید به کدام صفحه اصلی برود String extractedPath = initialURI?.path.toString() == '/' ? Routes.home : initialURI?.path.toString() ?? Routes.home;
final String destinationRoute = token == null ? Routes.authenticaion : Routes.home; final String destinationRoute = token == null ? Routes.authenticaion : extractedPath;
final dynamic routeArguments = token == null ? false : {'showDialogs': true}; // Set showDialogs to false to prevent welcome popups
dynamic routeArguments = token == null ? {'isResetPassword': false} : {'showDialogs': false};
// اگر لینک ورودی وجود داشت، آن را به عنوان آرگومان به صفحه Home میفرستیم
if (destinationRoute == Routes.home && initialURI != null) { if (destinationRoute == Routes.home) {
(routeArguments as Map)['deepLinkUri'] = initialURI; print("destination route was home and init uri is $initialURI");
initialURI = null; // لینک را مصرف میکنیم تا دوباره استفاده نشود // (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( await navigatorKey.currentState!.pushReplacementNamed(
destinationRoute, destinationRoute,
arguments: routeArguments, arguments: routeArguments,
); );
// --- پایان بخش اصلاح شده ---
} catch (e) { } catch (e) {
setState(() { setState(() {
_errorOccured = true; _errorOccured = true;

View File

@ -26,12 +26,14 @@ class _WebViewState extends State<WebView> {
}; };
void _onProgress(int progress) { void _onProgress(int progress) {
print('🌐 WebView loading progress: $progress%'); // Add logging
setState(() { setState(() {
this.progress = progress; this.progress = progress;
}); });
} }
void _onPageFinished(String url) async { void _onPageFinished(String url) async {
print('✅ WebView finished loading: $url'); // Add logging
await Future.delayed(const Duration(seconds: 2)); await Future.delayed(const Duration(seconds: 2));
setState(() { setState(() {
if (progress == 100) loading = false; if (progress == 100) loading = false;
@ -40,21 +42,24 @@ class _WebViewState extends State<WebView> {
@override @override
void initState() { void initState() {
print('🔄 Initializing WebView with URL: ${widget.src}'); // Add logging
controller = WebViewController() controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted) ..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate( ..setNavigationDelegate(
NavigationDelegate( NavigationDelegate(
onProgress: _onProgress, onProgress: _onProgress,
onPageStarted: (String url) {}, onPageStarted: (String url) {
print('▶️ WebView started loading: $url'); // Add logging
},
onPageFinished: _onPageFinished, onPageFinished: _onPageFinished,
onHttpError: (HttpResponseError error) {}, onHttpError: (HttpResponseError error) {
// print('❌ WebView HTTP error: ${error.statusCode} on ${error.url}'); // Add logging
},
onWebResourceError: (WebResourceError error) { onWebResourceError: (WebResourceError error) {
// navigatorKey.currentState!.pop(); // print('❌ WebView resource error: ${error.description} on ${error.failingUrl}'); // Add logging
}, },
onNavigationRequest: (NavigationRequest request) { onNavigationRequest: (NavigationRequest request) {
// if (request.url.startsWith('https://www.youtube.com/')) { print('🔀 WebView navigation request to: ${request.url}'); // Add logging
// return NavigationDecision.prevent;
// }
return NavigationDecision.navigate; return NavigationDecision.navigate;
}, },
), ),
@ -77,7 +82,7 @@ class _WebViewState extends State<WebView> {
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
title: const DidvanText( title: const DidvanText(
'بازگشت به دیدوان', 'بازگشت',
), ),
leading: const BackButton(), leading: const BackButton(),
), ),

View File

@ -109,7 +109,7 @@ class _DidvanTextFieldState extends State<DidvanTextField> {
? TextDirection.ltr ? TextDirection.ltr
: TextDirection.rtl, : TextDirection.rtl,
child: Padding( 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: Center(
child: TextFormField( child: TextFormField(
inputFormatters: <TextInputFormatter>[ inputFormatters: <TextInputFormatter>[

View File

@ -1,2 +1,9 @@
update_js: update_js:
cp ./lib/assets/js/main.js ./build/flutter_assets/lib/assets/js/main.js 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