liquid glass for ai

This commit is contained in:
Mr.Jebelli 2025-11-26 09:47:32 +03:30
parent 461eee1ed7
commit 63ab4c38d2
49 changed files with 1434 additions and 1193 deletions

View File

@ -116,6 +116,9 @@
android:screenOrientation="portrait" android:screenOrientation="portrait"
android:exported="true" android:exported="true"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" /> android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
<meta-data
android:name="io.flutter.embedding.android.EnableImpeller"
android:value="true" />
</application> </application>

View File

@ -1,42 +1,39 @@
// ignore_for_file: deprecated_member_use
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:app_links/app_links.dart';
import 'package:bot_toast/bot_toast.dart'; import 'package:bot_toast/bot_toast.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:get/get.dart';
import 'package:home_widget/home_widget.dart';
import 'package:provider/provider.dart';
import 'package:provider/single_child_widget.dart';
import 'package:sentry_flutter/sentry_flutter.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/requests/news.dart'; import 'package:didvan/models/requests/news.dart';
import 'package:didvan/models/requests/radar.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';
import 'package:didvan/routes/route_generator.dart'; import 'package:didvan/routes/route_generator.dart';
import 'package:didvan/routes/routes.dart'; import 'package:didvan/routes/routes.dart';
import 'package:didvan/services/app_home_widget/home_widget_repository.dart'; import 'package:didvan/services/app_home_widget/home_widget_repository.dart';
import 'package:didvan/services/media/media.dart'; import 'package:didvan/services/media/media.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/notification/firebase_api.dart'; import 'package:didvan/services/notification/firebase_api.dart';
import 'package:didvan/services/notification/notification_service.dart'; import 'package:didvan/services/notification/notification_service.dart';
import 'package:didvan/utils/my_custom_scroll_behavior.dart'; import 'package:didvan/utils/my_custom_scroll_behavior.dart';
import 'package:didvan/providers/media.dart';
import 'package:didvan/providers/theme.dart';
import 'package:didvan/providers/user.dart';
import 'package:didvan/views/ai/ai_chat_state.dart'; import 'package:didvan/views/ai/ai_chat_state.dart';
import 'package:didvan/views/ai/ai_state.dart'; import 'package:didvan/views/ai/ai_state.dart';
import 'package:didvan/views/ai/bot_assistants_state.dart'; import 'package:didvan/views/ai/bot_assistants_state.dart';
import 'package:didvan/views/ai/history_ai_chat_state.dart'; import 'package:didvan/views/ai/history_ai_chat_state.dart';
import 'package:didvan/views/podcasts/podcasts_state.dart'; import 'package:didvan/views/podcasts/podcasts_state.dart';
import 'package:didvan/views/podcasts/studio_details/studio_details_state.dart'; import 'package:didvan/views/podcasts/studio_details/studio_details_state.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:get/get.dart';
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';
import 'package:app_links/app_links.dart';
import 'services/network/request.dart';
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
Uri? initialURI; Uri? initialURI;
@ -52,29 +49,34 @@ void main() async {
runZonedGuarded( runZonedGuarded(
() async { () async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
try { try {
if (!kIsWeb) { if (!kIsWeb) {
HomeWidget.registerBackgroundCallback(_backgroundCallbackHomeWidget); HomeWidget.registerBackgroundCallback(_backgroundCallbackHomeWidget);
HomeWidget.registerInteractivityCallback(_backgroundCallbackHomeWidget); HomeWidget.registerInteractivityCallback(
_backgroundCallbackHomeWidget);
await NotificationService.initializeNotification(); await NotificationService.initializeNotification();
try {
if (Platform.isAndroid) { if (Platform.isAndroid) {
try {
await FlutterDownloader.initialize(debug: true, ignoreSsl: true); await FlutterDownloader.initialize(debug: true, ignoreSsl: true);
} catch (e) {
e.printError();
} }
} catch (e) {
e.printError();
} }
} }
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform);
await FirebaseApi().initNotification(); await FirebaseApi().initNotification();
} catch (e) { } catch (e) {
debugPrint(e.toString()); debugPrint("Initialization Error: $e");
} }
await SentryFlutter.init( await SentryFlutter.init(
(options) { (options) {
options.dsn = 'https://a4cfcaa7d67471240d295c25c968d91d@o4508585857384448.ingest.de.sentry.io/4508585886548048'; options.dsn =
'https://a4cfcaa7d67471240d295c25c968d91d@o4508585857384448.ingest.de.sentry.io/4508585886548048';
options.tracesSampleRate = 1.0; options.tracesSampleRate = 1.0;
options.profilesSampleRate = 1.0; options.profilesSampleRate = 1.0;
}, },
@ -95,9 +97,24 @@ class Didvan extends StatefulWidget {
} }
class _DidvanState extends State<Didvan> with WidgetsBindingObserver { class _DidvanState extends State<Didvan> with WidgetsBindingObserver {
late AppLinks _appLinks; late final AppLinks _appLinks;
StreamSubscription<Uri>? _linkSubscription; StreamSubscription<Uri>? _linkSubscription;
final List<SingleChildWidget> _applicationProviders = [
ChangeNotifierProvider<PodcastsState>(create: (_) => PodcastsState()),
ChangeNotifierProvider<MediaProvider>(create: (_) => MediaProvider()),
ChangeNotifierProvider<UserProvider>(create: (_) => UserProvider()),
ChangeNotifierProvider<ThemeProvider>(create: (_) => ThemeProvider()),
ChangeNotifierProvider<StudioDetailsState>(
create: (_) => StudioDetailsState()),
ChangeNotifierProvider<HistoryAiChatState>(
create: (_) => HistoryAiChatState()),
ChangeNotifierProvider<AiState>(create: (_) => AiState()),
ChangeNotifierProvider<AiChatState>(create: (_) => AiChatState()),
ChangeNotifierProvider<BotAssistantsState>(
create: (_) => BotAssistantsState()),
];
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -124,49 +141,47 @@ class _DidvanState extends State<Didvan> with WidgetsBindingObserver {
} }
void _navigateTo(Uri uri) { void _navigateTo(Uri uri) {
if (mounted) { if (!mounted) return;
String path = uri.path;
final Map<String, String> params = uri.queryParameters;
final String? token = params['token']; final Map<String, String> params = uri.queryParameters;
final String? token = params['token'];
if (token != null) { if (token != null) {
//todo: this didnt work debugPrint("DeepLink Token Received: $token");
print("DEBUG: received token in url, token: $token, path: $path"); RequestService.token = token;
RequestService.token = token; }
final String path = uri.path;
if (path.startsWith('/news/')) {
final String idStr = path.split('/news/').last;
final int? id = int.tryParse(idStr);
if (id != null) {
navigatorKey.currentState?.pushNamed(
Routes.newsDetails,
arguments: {'id': id, 'args': const NewsRequestArgs(page: 0)},
);
} }
} else if (path.startsWith('/radar/')) {
if (path.startsWith('/news/')) { final String idStr = path.split('/radar/').last;
final id = path.split('/news/').last; final int? id = int.tryParse(idStr);
if (id.isNotEmpty) { if (id != null) {
navigatorKey.currentState?.pushNamed( navigatorKey.currentState?.pushNamed(
Routes.newsDetails, Routes.radarDetails,
arguments: {'id': int.parse(id), 'args': const NewsRequestArgs(page: 0)}, arguments: {'id': id, 'args': const RadarRequestArgs(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)},
);
}
} }
} }
} }
@override @override
void didChangeAppLifecycleState(AppLifecycleState state) async { void didChangeAppLifecycleState(AppLifecycleState state) async {
if (!kIsWeb) { if (!kIsWeb && state == AppLifecycleState.resumed) {
if (state == AppLifecycleState.resumed) { final route = await HomeWidget.getWidgetData("cRoute", defaultValue: '');
var r = await HomeWidget.getWidgetData("cRoute", defaultValue: ''); if (route.toString() != Routes.splash) {
if (r.toString() != Routes.splash) { await HomeWidgetRepository.decideWhereToGo();
await HomeWidgetRepository.decideWhereToGo(); if (HomeWidgetRepository.data != null) {
NotificationMessage? data = HomeWidgetRepository.data; await HomeWidgetRepository.decideWhereToGoNotif();
if (data != null) {
await HomeWidgetRepository.decideWhereToGoNotif();
}
} }
} }
} }
@ -175,60 +190,60 @@ class _DidvanState extends State<Didvan> with WidgetsBindingObserver {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MultiProvider( return MultiProvider(
providers: [ providers: _applicationProviders,
ChangeNotifierProvider<PodcastsState>(create: (context) => PodcastsState()),
ChangeNotifierProvider<MediaProvider>(create: (context) => MediaProvider()),
ChangeNotifierProvider<UserProvider>(create: (context) => UserProvider()),
ChangeNotifierProvider<ThemeProvider>(create: (context) => ThemeProvider()),
ChangeNotifierProvider<StudioDetailsState>(create: (context) => StudioDetailsState()),
ChangeNotifierProvider<HistoryAiChatState>(create: (context) => HistoryAiChatState()),
ChangeNotifierProvider<AiState>(create: (context) => AiState()),
ChangeNotifierProvider<AiChatState>(create: (context) => AiChatState()),
ChangeNotifierProvider<BotAssistantsState>(create: (context) => BotAssistantsState()),
],
child: Consumer<ThemeProvider>( child: Consumer<ThemeProvider>(
builder: (context, themeProvider, child) => Container( builder: (context, themeProvider, child) {
color: Theme.of(context).colorScheme.surface, final lightTheme = LightThemeConfig.themeData.copyWith(
child: SafeArea( bottomSheetTheme: const BottomSheetThemeData(
child: MaterialApp( surfaceTintColor: Colors.transparent,
scrollBehavior: MyCustomScrollBehavior(), backgroundColor: Colors.transparent,
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"),
), ),
), textTheme: LightThemeConfig.themeData.textTheme.apply(
), fontFamily: themeProvider.fontFamily,
),
);
final darkTheme = DarkThemeConfig.themeData.copyWith(
bottomSheetTheme: const BottomSheetThemeData(
surfaceTintColor: Colors.transparent,
backgroundColor: Colors.transparent,
),
textTheme: DarkThemeConfig.themeData.textTheme.apply(
fontFamily: themeProvider.fontFamily,
),
);
return Container(
color: Theme.of(context).colorScheme.surface,
child: SafeArea(
child: MaterialApp(
scrollBehavior: MyCustomScrollBehavior(),
navigatorKey: navigatorKey,
debugShowCheckedModeBanner: false,
title: 'Didvan',
theme: lightTheme,
darkTheme: darkTheme,
color: lightTheme.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"),
),
),
);
},
), ),
); );
} }
} }

View File

@ -8,6 +8,7 @@ class MainPageContentType {
final List<String> subtitles; final List<String> subtitles;
final int? duration; final int? duration;
final String? description; final String? description;
final String? riskScore;
MainPageContentType({ MainPageContentType({
required this.id, required this.id,
@ -19,6 +20,7 @@ class MainPageContentType {
required this.subtitles, required this.subtitles,
this.duration, this.duration,
this.description, this.description,
this.riskScore,
}); });
factory MainPageContentType.fromJson(Map<String, dynamic> json) => factory MainPageContentType.fromJson(Map<String, dynamic> json) =>
@ -32,5 +34,6 @@ class MainPageContentType {
subtitles: List<String>.from(json['subtitles']), subtitles: List<String>.from(json['subtitles']),
duration: json['duration'], duration: json['duration'],
description: json['description'], description: json['description'],
riskScore: json['score'],
); );
} }

View File

@ -34,9 +34,6 @@ class UserProvider extends CoreProvier {
int get unreadMessageCount => _unreadMessageCount; int get unreadMessageCount => _unreadMessageCount;
// static final List<MapEntry> _radarMarkQueue = [];
// static final List<MapEntry> _newsMarkQueue = [];
// static final List<MapEntry> _studioMarkQueue = [];
static final List<MapEntry> _statisticMarkQueue = []; static final List<MapEntry> _statisticMarkQueue = [];
static final List<Map> _itemMarkQueue = []; static final List<Map> _itemMarkQueue = [];
@ -76,7 +73,7 @@ class UserProvider extends CoreProvier {
} }
} else { } else {
print( print(
"UserProvider: Welcome message API failed. Status: ${service.statusCode}, Error: ${service.errorMessage}"); // Log failure "UserProvider: Welcome message API failed. Status: ${service.statusCode}, Error: ${service.errorMessage}");
_welcomeMessage = null; _welcomeMessage = null;
} }
} catch (e) { } catch (e) {
@ -109,6 +106,7 @@ class UserProvider extends CoreProvier {
final RequestService service = RequestService(RequestHelper.userInfo); final RequestService service = RequestService(RequestHelper.userInfo);
await service.httpGet(); await service.httpGet();
// اگر توکن نامعتبر است (401)، فالس برمیگردانیم تا توکن پاک شود
if (service.statusCode == 401) { if (service.statusCode == 401) {
print("UserProvider: getUserInfo failed - Unauthorized (401)."); print("UserProvider: getUserInfo failed - Unauthorized (401).");
isAuthenticated = false; isAuthenticated = false;
@ -150,6 +148,13 @@ class UserProvider extends CoreProvier {
print( print(
"UserProvider: getUserInfo failed. Status: ${service.statusCode}, Error: ${service.errorMessage}"); "UserProvider: getUserInfo failed. Status: ${service.statusCode}, Error: ${service.errorMessage}");
isAuthenticated = false; isAuthenticated = false;
// اصلاح مهم: اگر خطا 401 نیست (مثلاً مشکل سرور یا اینترنت)، Exception پرتاب میکنیم
// تا در Splash وارد بخش catch شود و توکن پاک نشود.
if (service.statusCode != 401) {
throw Exception("Server Error or Connection Issue: ${service.statusCode}");
}
return false; return false;
} }
@ -295,56 +300,6 @@ class UserProvider extends CoreProvier {
} }
} }
// static Future<void> changeRadarMark(int id, bool value) async {
//   _radarMarkQueue.add(MapEntry(id, value));
//   Future.delayed(const Duration(milliseconds: 500), () async {
//     final MapEntry? lastChange =
//         _radarMarkQueue.lastWhereOrNull((item) => item.key == id);
//     if (lastChange == null) return;
//     final service = RequestService(RequestHelper.mark(id, 'radar'));
//     if (lastChange.value) {
//       await service.post();
//     } else {
//       await service.delete();
//     }
//     _radarMarkQueue.removeWhere((element) => element.key == id);
//   });
// }
// static Future<void> changeStudioMark(int id, bool value) async {
//   _studioMarkQueue.add(MapEntry(id, value));
//   Future.delayed(const Duration(milliseconds: 500), () async {
//     final MapEntry? lastChange =
//         _studioMarkQueue.lastWhereOrNull((item) => item.key == id);
//     if (lastChange == null) return;
//     final service = RequestService(RequestHelper.mark(id, 'studio'));
//     if (lastChange.value) {
//       await service.post();
//     } else {
//       await service.delete();
//     }
//     _studioMarkQueue.removeWhere((element) => element.key == id);
//   });
// }
// static Future<void> changeNewsMark(int id, bool value) async {
//   _newsMarkQueue.add(MapEntry(id, value));
//   Future.delayed(const Duration(milliseconds: 500), () async {
//     final MapEntry? lastChange =
//         _newsMarkQueue.lastWhereOrNull((item) => item.key == id);
//     if (lastChange == null) return;
//     final service = RequestService(RequestHelper.mark(id, 'news'));
//     if (lastChange.value) {
//       await service.post();
//     } else {
//       await service.delete();
//   ----------------------------------------------------------------
//   }
//     _newsMarkQueue.removeWhere((element) => element.key == id);
//   });
// }
static Future<void> changeStatisticMark(int id, bool value) async { static Future<void> changeStatisticMark(int id, bool value) async {
_statisticMarkQueue.add(MapEntry(id, value)); _statisticMarkQueue.add(MapEntry(id, value));
Future.delayed(const Duration(milliseconds: 500), () async { Future.delayed(const Duration(milliseconds: 500), () async {
@ -387,12 +342,4 @@ class UserProvider extends CoreProvier {
return false; return false;
} }
} }
}
// Future<void> getUnreadMessageCount() async {
// final RequestService service = RequestService(RequestHelper.directs);
// await service.httpGet();
// if (service.isSuccess) {
// _unreadMessageCount = service.result['unread'] ?? 0;
// }
// }
}

View File

@ -0,0 +1,82 @@
#include <flutter/runtime_effect.glsl>
uniform vec2 uResolution;
uniform float uDistortionStrength;
uniform float uBlurIntensity;
uniform float uDispersion;
uniform sampler2D uTexture;
uniform vec2 uWidgetOffset;
uniform vec2 uTextureSize;
out vec4 fragColor;
void main() {
vec2 fragCoord = FlutterFragCoord();
// 1. محاسبه مختصات UV جهانی
vec2 globalPos = fragCoord + uWidgetOffset;
vec2 uv = globalPos / uTextureSize;
// 2. ساخت ماسک لبه (فقط 8 درصد دور کادر برای لیکویید شدن)
vec2 localUV = fragCoord / uResolution;
float distToEdgeX = min(localUV.x, 1.0 - localUV.x);
float distToEdgeY = min(localUV.y, 1.0 - localUV.y);
float distToEdge = min(distToEdgeX, distToEdgeY);
// ضخامت لبه شیشه‌ای
float borderThickness = 0.1;
// نرم کردن ماسک (از 0 تا 1)
float borderMask = 1.0 - smoothstep(borderThickness - 0.09, borderThickness, distToEdge);
// اگر کاملاً در مرکز هستیم، فقط تکسچر را نشان بده
if (borderMask <= 0.01) {
fragColor = texture(uTexture, uv);
return;
}
// 3. اعوجاج (Distortion) در لبه‌ها
vec2 distortionDir = vec2(0.5) - localUV;
// اعمال اعوجاج فقط در محدوده ماسک
vec2 distortedUV = uv + (distortionDir * uDistortionStrength * 0.01 * borderMask);
// 4. بلور (Blur) و تجزیه رنگ (Dispersion)
vec4 finalColor = vec4(0.0);
if (uBlurIntensity > 0.0) {
// --- تغییر اصلی: افزایش ضریب برای منشور بیشتر ---
// مقدار قبلی 0.008 بود، به 0.035 افزایش یافت تا رنگ‌ها بیشتر پخش شوند
float dispersion = uDispersion * 0.010 * borderMask;
// نمونه‌برداری با فاصله بیشتر برای کانال‌های رنگی
vec4 colR = texture(uTexture, distortedUV + vec2(dispersion, 0.0));
vec4 colG = texture(uTexture, distortedUV);
vec4 colB = texture(uTexture, distortedUV - vec2(dispersion, 0.0));
vec4 baseColor = vec4(colR.r, colG.g, colB.b, 1.0);
// بلور گاوسی ساده شده
float blurStep = uBlurIntensity * 0.001 * borderMask;
vec4 blurCol = (
texture(uTexture, distortedUV + vec2(blurStep, blurStep)) +
texture(uTexture, distortedUV + vec2(-blurStep, -blurStep)) +
texture(uTexture, distortedUV + vec2(blurStep, -blurStep)) +
texture(uTexture, distortedUV + vec2(-blurStep, blurStep))
) * 0.25; // اصلاح: ضریب 0.02 خیلی تیره بود، 0.25 میانگین دقیق‌تری است
// ترکیب رنگ اصلی با بلور
// کاهش شدت میکس بلور (از 0.7 به 0.5) تا رنگ‌های منشور شفاف‌تر دیده شوند
finalColor = mix(baseColor, blurCol, 0.5);
} else {
finalColor = texture(uTexture, distortedUV);
}
// 5. میکس نهایی (مرکز شفاف + لبه‌های شیشه‌ای)
vec4 clearCenter = texture(uTexture, uv);
fragColor = mix(clearCenter, finalColor, borderMask);
// هایلایت سفید لبه
float shine = smoothstep(0.0, borderThickness * 0.5, distToEdge) * (1.0 - smoothstep(borderThickness * 0.5, borderThickness, distToEdge));
fragColor += vec4(shine * 0.15 * borderMask);
}

View File

@ -964,7 +964,8 @@ class _AiChatPageState extends State<AiChatPage> with TickerProviderStateMixin {
.withOpacity(0.9), .withOpacity(0.9),
border: Border.all( border: Border.all(
color: message.role.toString().contains('user') color: message.role.toString().contains('user')
? const Color.fromARGB(0, 0, 0, 0) :Theme.of(context).colorScheme.border, ? const Color.fromARGB(0, 0, 0, 0)
: Theme.of(context).colorScheme.border,
width: 0.5, width: 0.5,
), ),
), ),
@ -1332,7 +1333,7 @@ class _AiChatPageState extends State<AiChatPage> with TickerProviderStateMixin {
margin: const EdgeInsets.fromLTRB(8, 8, 8, 0), margin: const EdgeInsets.fromLTRB(8, 8, 8, 0),
child: Row( child: Row(
children: [ children: [
const Icon(Icons.file_copy), SvgPicture.asset('lib/assets/icons/copy.svg',),
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
child: Column( child: Column(

View File

@ -454,7 +454,9 @@ class _HistoryAiChatPageState extends State<HistoryAiChatPage> {
DateTime.parse(chat DateTime.parse(chat
.updatedAt .updatedAt
.toString()) .toString())
.toPersianDateStr(monthString: ''), .toPersianDateStr(
monthString:
''),
style: style:
const TextStyle( const TextStyle(
fontSize: fontSize:

View File

@ -51,7 +51,10 @@ class _PasswordInputState extends State<PasswordInput> {
onTap: () => state.currentPageIndex++, onTap: () => state.currentPageIndex++,
child: DidvanText( child: DidvanText(
'فراموشی رمز عبور', 'فراموشی رمز عبور',
style: Theme.of(context).textTheme.titleSmall?.copyWith(fontWeight: FontWeight.normal), style: Theme.of(context)
.textTheme
.titleSmall
?.copyWith(fontWeight: FontWeight.normal),
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
), ),
), ),

View File

@ -23,6 +23,8 @@ class _ResetPasswordState extends State<ResetPassword> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final authState = context.watch<AuthenticationState>(); final authState = context.watch<AuthenticationState>();
final bottomInset = MediaQuery.of(context).viewInsets.bottom;
return Form( return Form(
key: _formKey, key: _formKey,
child: AuthenticationLayout( child: AuthenticationLayout(
@ -50,7 +52,7 @@ class _ResetPasswordState extends State<ResetPassword> {
obsecureText: true, obsecureText: true,
prefixSvgPath: 'lib/assets/icons/key.svg', prefixSvgPath: 'lib/assets/icons/key.svg',
), ),
const Spacer(), const SizedBox(height: 32),
DidvanButton( DidvanButton(
onPressed: () async { onPressed: () async {
if (!_formKey.currentState!.validate()) { if (!_formKey.currentState!.validate()) {
@ -79,8 +81,8 @@ class _ResetPasswordState extends State<ResetPassword> {
}, },
title: authState.hasPassword ? 'تغییر رمز عبور' : 'تایید رمز عبور', title: authState.hasPassword ? 'تغییر رمز عبور' : 'تایید رمز عبور',
), ),
const SizedBox( SizedBox(
height: 48, height: 48 + bottomInset,
), ),
], ],
), ),

View File

@ -2,7 +2,6 @@
import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart'; import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/assets.dart';
import 'package:didvan/models/view/app_bar_data.dart'; import 'package:didvan/models/view/app_bar_data.dart';
import 'package:didvan/views/comments/comments_state.dart'; import 'package:didvan/views/comments/comments_state.dart';
import 'package:didvan/views/comments/widgets/comment.dart'; import 'package:didvan/views/comments/widgets/comment.dart';
@ -75,17 +74,22 @@ class _CommentsState extends State<Comments> {
builder: (context, state, child) => SliverStateHandler<CommentsState>( builder: (context, state, child) => SliverStateHandler<CommentsState>(
onRetry: state.getComments, onRetry: state.getComments,
state: state, state: state,
itemPadding: const EdgeInsets.only( bottom: 20), itemPadding: (widget.pageData['type'] == 'news' ||
widget.pageData['type'] == 'radar')
? const EdgeInsets.only(bottom: 20, left: 20, right: 20)
: const EdgeInsets.only(bottom: 20),
childCount: state.comments.length, childCount: state.comments.length,
placeholder: const _CommentPlaceholder(), placeholder: const _CommentPlaceholder(),
centerEmptyState: false, centerEmptyState: false,
enableEmptyState: state.comments.isEmpty, enableEmptyState: state.comments.isEmpty,
paddingEmptyState: 0, paddingEmptyState: 0,
emptyState: EmptyState( emptyState: const EmptyState(
asset: Assets.emptyChat, asset: 'lib/assets/images/empty_states/Empty_List.png',
title: 'لیست خالی است', title: 'لیست خالی است',
titleColor: const Color.fromARGB(255, 0, 126, 167), height: 500,
subtitle: 'در حال حاضر آیتمی در این بخش ثبت نشده است. هر زمان مورد جدیدی اضافه شود، در اینجا نمایش داده می‌شود.', titleColor: Color.fromARGB(255, 0, 126, 167),
subtitle:
'در حال حاضر آیتمی در این بخش ثبت نشده است. هر زمان مورد جدیدی اضافه شود، در اینجا نمایش داده می‌شود.',
), ),
builder: (context, state, index) => Comment( builder: (context, state, index) => Comment(
key: ValueKey( key: ValueKey(
@ -337,4 +341,4 @@ class _CommentPlaceholder extends StatelessWidget {
], ],
); );
} }
} }

View File

@ -262,8 +262,8 @@ class _MessageState extends State<Message> with SingleTickerProviderStateMixin {
), ),
), ),
DidvanText( DidvanText(
DateTimeUtils.timeWithAmPm( DateTimeUtils.timeWithAmPm(widget.message.createdAt)
widget.message.createdAt).toPersianDigit(), .toPersianDigit(),
style: Theme.of(context).textTheme.labelSmall, style: Theme.of(context).textTheme.labelSmall,
color: Theme.of(context).colorScheme.caption, color: Theme.of(context).colorScheme.caption,
), ),

View File

@ -641,7 +641,6 @@ class _FadeInSlide extends StatefulWidget {
Key? key, Key? key,
required this.child, required this.child,
this.delay = Duration.zero, this.delay = Duration.zero,
// ignore: unused_element_parameter
this.duration = const Duration(milliseconds: 400), this.duration = const Duration(milliseconds: 400),
this.slideOffset = 50.0, this.slideOffset = 50.0,
}) : super(key: key); }) : super(key: key);

View File

@ -161,7 +161,7 @@ class _FilteredBookmarksState extends State<FilteredBookmarks> {
onMarkChanged: (id, value) => onMarkChanged: (id, value) =>
_onBookmarkChanged(id, value, true, item.type), _onBookmarkChanged(id, value, true, item.type),
enableBookmark: true, enableBookmark: true,
useCardStyle: true, // **اعمال استایل کارت در لیست بوکمارک** useCardStyle: true,
); );
}, },
childCount: state.bookmarks.length + childCount: state.bookmarks.length +
@ -174,4 +174,4 @@ class _FilteredBookmarksState extends State<FilteredBookmarks> {
), ),
); );
} }
} }

View File

@ -1,3 +1,5 @@
// ignore_for_file: deprecated_member_use
import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart'; import 'package:didvan/config/theme_data.dart';
import 'package:didvan/main.dart'; import 'package:didvan/main.dart';
@ -258,7 +260,8 @@ class SwotSection extends StatelessWidget {
final double? headerSize; final double? headerSize;
final double? moreSize; final double? moreSize;
const SwotSection({super.key, required this.swotItems, this.headerSize, this.moreSize}); const SwotSection(
{super.key, required this.swotItems, this.headerSize, this.moreSize});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -314,7 +317,9 @@ class SwotSection extends StatelessWidget {
? Theme.of(context).textTheme.titleSmall ? Theme.of(context).textTheme.titleSmall
: TextStyle(fontSize: moreSize), : TextStyle(fontSize: moreSize),
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
fontWeight: moreSize == null ? FontWeight.bold : FontWeight.normal, fontWeight: moreSize == null
? FontWeight.bold
: FontWeight.normal,
), ),
], ],
), ),
@ -1074,6 +1079,8 @@ class MainPageSection extends StatelessWidget {
Padding( Padding(
padding: const EdgeInsets.all(9.5), padding: const EdgeInsets.all(9.5),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( Row(
children: [ children: [
@ -1093,15 +1100,42 @@ class MainPageSection extends StatelessWidget {
], ],
), ),
const SizedBox( const SizedBox(
height: 13, height: 9,
), ),
const Row( Builder(
mainAxisAlignment: MainAxisAlignment.start, builder: (context) {
children: [ final riskValue = double.tryParse(item.riskScore ?? '0') ?? 0;
SizedBox( final isNegative = riskValue < 0;
height: 19, return Container(
) padding: const EdgeInsets.symmetric(
], horizontal: 4, vertical: 3),
decoration: BoxDecoration(
color: (isNegative ? Colors.red : Colors.green)
.withOpacity(0.1),
borderRadius: BorderRadius.circular(6),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
SvgPicture.asset(
isNegative
? "lib/assets/images/features/Badge.svg"
: "lib/assets/images/features/Badge-Green.svg",
height: 9,
),
const SizedBox(width: 4),
Text(
"عدد ریسک : ${riskValue.abs().toInt().toString().toPersianDigit()}",
style: TextStyle(
color: isNegative ? Colors.red : Colors.green,
fontWeight: FontWeight.normal,
fontSize: 12,
),
)
],
),
);
},
), ),
], ],
), ),

View File

@ -1,8 +1,13 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/design_config.dart';
import 'package:didvan/models/notification_message.dart';
import 'package:didvan/models/view/action_sheet_data.dart'; import 'package:didvan/models/view/action_sheet_data.dart';
import 'package:didvan/providers/theme.dart'; import 'package:didvan/providers/theme.dart';
import 'package:didvan/services/app_initalizer.dart'; import 'package:didvan/services/app_initalizer.dart';
import 'package:didvan/services/app_home_widget/home_widget_repository.dart';
import 'package:didvan/utils/action_sheet.dart'; import 'package:didvan/utils/action_sheet.dart';
import 'package:didvan/views/home/explore/explore.dart'; import 'package:didvan/views/home/explore/explore.dart';
import 'package:didvan/views/home/main/main_page.dart'; import 'package:didvan/views/home/main/main_page.dart';
@ -13,12 +18,6 @@ import 'package:didvan/views/home/search/search.dart';
import 'package:didvan/views/ai_section/ai_section_page.dart'; import 'package:didvan/views/ai_section/ai_section_page.dart';
import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:didvan/views/widgets/didvan/bnb.dart'; import 'package:didvan/views/widgets/didvan/bnb.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:flutter_animate/flutter_animate.dart';
import '../../services/app_home_widget/home_widget_repository.dart';
final GlobalKey<ScaffoldState> homeScaffKey = GlobalKey<ScaffoldState>(); final GlobalKey<ScaffoldState> homeScaffKey = GlobalKey<ScaffoldState>();
@ -36,74 +35,71 @@ class _HomeState extends State<Home>
@override @override
void initState() { void initState() {
super.initState();
final state = context.read<HomeState>(); final state = context.read<HomeState>();
DesignConfig.updateSystemUiOverlayStyle(); DesignConfig.updateSystemUiOverlayStyle();
_tabController = TabController(length: 5, vsync: this, initialIndex: 0); _tabController = TabController(length: 5, vsync: this, initialIndex: 0);
state.tabController = _tabController; state.tabController = _tabController;
_tabController.addListener(() { _tabController.addListener(() {
if (_tabController.indexIsChanging) { state.currentPageIndex = _tabController.index;
state.currentPageIndex = _tabController.index;
} else {
state.currentPageIndex = _tabController.index;
}
}); });
if (!kIsWeb) { if (!kIsWeb) {
Future.delayed(Duration.zero, () { Future.microtask(() async {
HomeWidgetRepository.fetchWidget(); await HomeWidgetRepository.fetchWidget();
HomeWidgetRepository.decideWhereToGo(); await HomeWidgetRepository.decideWhereToGo();
NotificationMessage? data = HomeWidgetRepository.data; if (HomeWidgetRepository.data != null) {
if (data != null) { await HomeWidgetRepository.decideWhereToGoNotif();
HomeWidgetRepository.decideWhereToGoNotif(); }
if (mounted) {
AppInitializer.handleCLick(state, _tabController);
} }
AppInitializer.handleCLick(state, _tabController);
}); });
} }
state.refresh(); state.refresh();
context.read<ThemeProvider>().addListener(() { context.read<ThemeProvider>().addListener(() {
state.refresh(); if (mounted) state.refresh();
}); });
super.initState();
} }
PreferredSizeWidget? getAppBar() { Future<void> _handleBackPress() async {
return null; if (context.read<HomeState>().tabController.index == 0) {
if (kIsWeb) {
return;
}
ActionSheetUtils(context).openDialog(
data: ActionSheetData(
content: const DidvanText('آیا قصد خروج از برنامه را دارید؟'),
onConfirmed: () {
SystemChannels.platform.invokeMethod('SystemNavigator.pop');
},
isBackgroundDropBlur: true,
confrimTitle: 'بله',
dismissTitle: 'خیر',
),
);
} else {
_tabController.animateTo(0);
}
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return PopScope(
key: homeScaffKey, canPop: false,
// ignore: deprecated_member_use // ignore: deprecated_member_use
backgroundColor: Theme.of(context).colorScheme.background, onPopInvoked: (didPop) async {
resizeToAvoidBottomInset: false, if (didPop) return;
drawer: null, await _handleBackPress();
// ignore: deprecated_member_use },
body: WillPopScope( child: Scaffold(
onWillPop: () async { key: homeScaffKey,
if (context.read<HomeState>().tabController.index == 0) { backgroundColor: Theme.of(context).colorScheme.surface,
if (kIsWeb) { resizeToAvoidBottomInset: false,
return true; body: Consumer<HomeState>(
}
ActionSheetUtils(context).openDialog(
data: ActionSheetData(
content: const DidvanText(
'آیا قصد خروج از برنامه را دارید؟',
),
onConfirmed: () {
SystemChannels.platform.invokeMethod('SystemNavigator.pop');
},
isBackgroundDropBlur: true,
confrimTitle: 'بله',
dismissTitle: 'خیر',
));
} else {
_tabController.animateTo(0);
}
return false;
},
child: Consumer<HomeState>(
builder: (context, state, child) => AnimatedCrossFade( builder: (context, state, child) => AnimatedCrossFade(
duration: DesignConfig.lowAnimationDuration, duration: DesignConfig.lowAnimationDuration,
crossFadeState: state.filtering crossFadeState: state.filtering
@ -127,26 +123,24 @@ class _HomeState extends State<Home>
secondChild: const SearchPage(), secondChild: const SearchPage(),
), ),
), ),
), bottomNavigationBar: Consumer<HomeState>(
bottomNavigationBar: Consumer<HomeState>( builder: (context, state, child) => DidvanBNB(
builder: (context, state, child) => DidvanBNB( currentTabIndex: state.currentPageIndex,
currentTabIndex: state.currentPageIndex, onTabChanged: (index) {
onTabChanged: (index) { if (index < _tabController.length) {
if (index < _tabController.length) { state.currentPageIndex = index;
state.currentPageIndex = index; FocusScope.of(context).unfocus();
FocusScope.of(context).unfocus(); state.resetFilters(false);
state.resetFilters(false); _tabController.animateTo(index);
_tabController.animateTo(index); }
} },
},
),
)
.animate()
.slideY(
duration: 500.ms,
begin: 1,
curve: Curves.easeOut,
), ),
).animate().slideY(
duration: 500.ms,
begin: 1,
curve: Curves.easeOut,
),
),
); );
} }
} }

View File

@ -1,36 +1,34 @@
// ignore_for_file: unnecessary_import // lib/views/home/main/main_page.dart
// ignore_for_file: unnecessary_import, deprecated_member_use
import 'package:didvan/views/home/explore/explore.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart';
import 'package:universal_html/html.dart' as html;
import 'package:url_launcher/url_launcher_string.dart';
import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart'; import 'package:didvan/services/app_initalizer.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/models/home_page_content/content.dart'; import 'package:didvan/models/home_page_content/content.dart';
import 'package:didvan/models/home_page_content/home_page_list.dart'; import 'package:didvan/models/home_page_content/home_page_list.dart';
import 'package:didvan/models/home_page_content/swot.dart'; import 'package:didvan/models/home_page_content/swot.dart';
import 'package:didvan/services/app_initalizer.dart'; import 'package:didvan/views/home/home_state.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/views/home/explore/explore.dart';
import 'package:didvan/views/home/main/main_page_state.dart'; import 'package:didvan/views/home/main/main_page_state.dart';
import 'package:didvan/views/home/new_statistic/new_statistics_state.dart';
import 'package:didvan/views/home/main/widgets/main_content.dart'; import 'package:didvan/views/home/main/widgets/main_content.dart';
import 'package:didvan/views/home/main/widgets/story_section.dart'; import 'package:didvan/views/home/main/widgets/story_section.dart';
import 'package:didvan/views/home/main/widgets/simple_explore_card.dart'; import 'package:didvan/views/home/main/widgets/simple_explore_card.dart';
import 'package:didvan/views/home/main/widgets/didvan_plus_section.dart'; import 'package:didvan/views/home/main/widgets/didvan_plus_section.dart';
import 'package:didvan/views/home/main/widgets/didvan_voice_section.dart'; import 'package:didvan/views/home/main/widgets/didvan_voice_section.dart';
import 'package:didvan/views/home/new_statistic/new_statistics_state.dart';
import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:didvan/views/widgets/state_handlers/state_handler.dart'; import 'package:didvan/views/widgets/state_handlers/state_handler.dart';
import 'package:didvan/views/widgets/carousel_3d.dart'; import 'package:didvan/views/widgets/carousel_3d.dart';
import 'package:didvan/views/home/home_state.dart';
import 'package:didvan/views/widgets/text_divider.dart'; import 'package:didvan/views/widgets/text_divider.dart';
import 'package:didvan/views/widgets/home_app_bar.dart'; import 'package:didvan/views/widgets/home_app_bar.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:flutter/foundation.dart' show kIsWeb, defaultTargetPlatform;
import 'package:universal_html/html.dart' as html;
bool isAnyMobile() { bool isAnyMobile() {
if (kIsWeb) { if (kIsWeb) {
@ -45,9 +43,7 @@ bool isAnyMobile() {
} }
class MainPage extends StatefulWidget { class MainPage extends StatefulWidget {
const MainPage({ const MainPage({super.key});
super.key,
});
@override @override
State<MainPage> createState() => _MainPageState(); State<MainPage> createState() => _MainPageState();
@ -73,6 +69,7 @@ class _MainPageState extends State<MainPage> {
'🏠 MainPage build - didvanPlus: ${state.didvanPlus != null}'); '🏠 MainPage build - didvanPlus: ${state.didvanPlus != null}');
debugPrint( debugPrint(
'🏠 MainPage build - didvanVoice: ${state.didvanVoice != null}'); '🏠 MainPage build - didvanVoice: ${state.didvanVoice != null}');
return Column( return Column(
children: [ children: [
const HomeAppBar( const HomeAppBar(
@ -83,6 +80,7 @@ class _MainPageState extends State<MainPage> {
child: ListView( child: ListView(
padding: const EdgeInsets.only(top: 0, bottom: 16), padding: const EdgeInsets.only(top: 0, bottom: 16),
children: [ children: [
// --- Stories Section ---
if (state.stories.isNotEmpty) ...[ if (state.stories.isNotEmpty) ...[
const TextDivider(text: 'دیده‌بان') const TextDivider(text: 'دیده‌بان')
.animate() .animate()
@ -95,12 +93,16 @@ class _MainPageState extends State<MainPage> {
child: StorySection(stories: state.stories), child: StorySection(stories: state.stories),
).animate().fadeIn(delay: 600.ms, duration: 500.ms), ).animate().fadeIn(delay: 600.ms, duration: 500.ms),
], ],
// --- Didvan Plus ---
if (state.didvanPlus != null) ...[ if (state.didvanPlus != null) ...[
const SizedBox(height: 16), const SizedBox(height: 16),
DidvanPlusSection(didvanPlus: state.didvanPlus!) DidvanPlusSection(didvanPlus: state.didvanPlus!)
.animate() .animate()
.fadeIn(delay: 650.ms, duration: 500.ms), .fadeIn(delay: 650.ms, duration: 500.ms),
], ],
// --- Strategic Dashboard ---
const SizedBox(height: 12), const SizedBox(height: 12),
const TextDivider(text: 'پیشخوان استراتژیک') const TextDivider(text: 'پیشخوان استراتژیک')
.animate() .animate()
@ -109,6 +111,8 @@ class _MainPageState extends State<MainPage> {
padding: EdgeInsets.symmetric(horizontal: 16), padding: EdgeInsets.symmetric(horizontal: 16),
child: MainPageMainContent(), child: MainPageMainContent(),
).animate().fadeIn(delay: 800.ms, duration: 500.ms), ).animate().fadeIn(delay: 800.ms, duration: 500.ms),
// --- Explore Latest ---
if (state.content != null && if (state.content != null &&
state.content!.lists.isNotEmpty) ...[ state.content!.lists.isNotEmpty) ...[
const _ExploreLatestTitle() const _ExploreLatestTitle()
@ -119,10 +123,16 @@ class _MainPageState extends State<MainPage> {
swotItems: state.swotItems, swotItems: state.swotItems,
).animate().fadeIn(delay: 1000.ms, duration: 500.ms), ).animate().fadeIn(delay: 1000.ms, duration: 500.ms),
], ],
// --- SWOT Items ---
if (state.swotItems.isNotEmpty) if (state.swotItems.isNotEmpty)
SwotSection(swotItems: state.swotItems,headerSize: 13,moreSize: 12,) SwotSection(
.animate() swotItems: state.swotItems,
.fadeIn(delay: 1100.ms, duration: 500.ms), headerSize: 13,
moreSize: 12,
).animate().fadeIn(delay: 1100.ms, duration: 500.ms),
// --- Didvan Voice ---
if (state.didvanVoice != null) ...[ if (state.didvanVoice != null) ...[
const SizedBox(height: 16), const SizedBox(height: 16),
const _DidvanVoiceTitle() const _DidvanVoiceTitle()
@ -133,6 +143,8 @@ class _MainPageState extends State<MainPage> {
.animate() .animate()
.fadeIn(delay: 1200.ms, duration: 500.ms), .fadeIn(delay: 1200.ms, duration: 500.ms),
], ],
// --- Commented Out Section (Industry Pulse) ---
// const _IndustryPulseTitle() // const _IndustryPulseTitle()
// .animate() // .animate()
// .fadeIn(delay: 1100.ms, duration: 500.ms), // .fadeIn(delay: 1100.ms, duration: 500.ms),
@ -149,18 +161,15 @@ class _MainPageState extends State<MainPage> {
} }
} }
// --- Sub Widgets ---
class _DidvanSignalsTitle extends StatelessWidget { class _DidvanSignalsTitle extends StatelessWidget {
const _DidvanSignalsTitle(); const _DidvanSignalsTitle();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
left: 16,
right: 16,
bottom: 16,
top: 0,
),
child: Row( child: Row(
children: [ children: [
SvgPicture.asset( SvgPicture.asset(
@ -221,12 +230,7 @@ class _ExploreLatestTitle extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
left: 16,
right: 16,
bottom: 16,
top: 0,
),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
@ -284,10 +288,7 @@ class _ExploreLatestSlider extends StatelessWidget {
itemsData = []; itemsData = [];
for (var list in lists) { for (var list in lists) {
if (list.type == 'video' || if (['video', 'podcast', 'news', 'radar'].contains(list.type)) {
list.type == 'podcast' ||
list.type == 'news' ||
list.type == 'radar') {
continue; continue;
} }
if (list.contents.isNotEmpty) { if (list.contents.isNotEmpty) {
@ -318,18 +319,14 @@ class _ExploreLatestSlider extends StatelessWidget {
itemsData.add((type: 'swot', content: null, swotItem: swotItems.first)); itemsData.add((type: 'swot', content: null, swotItem: swotItems.first));
} }
if (items.isEmpty) { if (items.isEmpty) return const SizedBox.shrink();
return const SizedBox.shrink();
}
return Carousel3D( return Carousel3D(
items: items, items: items,
height: 220, height: 220,
autoPlayDuration: const Duration(seconds: 5), autoPlayDuration: const Duration(seconds: 5),
showControls: true, showControls: true,
onItemChanged: (index) { onItemChanged: (index) {},
// Optional: Handle item change if needed
},
onItemTap: (index) { onItemTap: (index) {
final data = itemsData[index]; final data = itemsData[index];
if (data.swotItem != null) { if (data.swotItem != null) {
@ -351,6 +348,10 @@ class _ExploreLatestSlider extends StatelessWidget {
} }
} }
// ---------------------------------------------------------------------------
// ------------------------- COMMENTED OUT SECTIONS --------------------------
// ---------------------------------------------------------------------------
// class _IndustryPulseTitle extends StatelessWidget { // class _IndustryPulseTitle extends StatelessWidget {
// const _IndustryPulseTitle(); // const _IndustryPulseTitle();

View File

@ -1,5 +1,4 @@
import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/models/didvan_plus_model.dart'; import 'package:didvan/models/didvan_plus_model.dart';
import 'package:didvan/services/network/request_helper.dart'; import 'package:didvan/services/network/request_helper.dart';
import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/network/request.dart';

View File

@ -11,7 +11,6 @@ import 'package:didvan/views/widgets/didvan/card.dart';
import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:didvan/views/widgets/infography_tag.dart'; import 'package:didvan/views/widgets/infography_tag.dart';
import 'package:didvan/views/widgets/ink_wrapper.dart'; import 'package:didvan/views/widgets/ink_wrapper.dart';
import 'package:didvan/views/widgets/liked_button.dart';
import 'package:didvan/views/widgets/skeleton_image.dart'; import 'package:didvan/views/widgets/skeleton_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';

View File

@ -39,13 +39,13 @@ class SimpleExploreCard extends StatelessWidget {
return 'lib/assets/icons/Startup.svg'; return 'lib/assets/icons/Startup.svg';
case 'radar': case 'radar':
return 'lib/assets/icons/Pouyesh_Ofogh_New.svg'; return 'lib/assets/icons/Pouyesh_Ofogh_New.svg';
case 'trend': // رادار روند case 'trend':
case 'trends': case 'trends':
return 'lib/assets/icons/Ravand.svg'; return 'lib/assets/icons/Ravand.svg';
case 'technology': // رادار تکنولوزی/فناوری case 'technology':
case 'tech': case 'tech':
return 'lib/assets/icons/Technology.svg'; return 'lib/assets/icons/Technology.svg';
case 'risk': // رادار ریسک case 'risk':
return 'lib/assets/icons/RiskRadar.svg'; return 'lib/assets/icons/RiskRadar.svg';
case 'survey': case 'survey':
case 'delphi': case 'delphi':

View File

@ -292,14 +292,13 @@ class _PodcastTabPageState extends State<PodcastTabPage> {
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final podcast = state.studios[index]; final podcast = state.studios[index];
// NEW: بررسی اینکه آیا این آیتم، آخرین آیتم در لیست است
final bool isLast = index == state.studios.length - 1; final bool isLast = index == state.studios.length - 1;
return PodcastListCard( return PodcastListCard(
podcast: podcast, podcast: podcast,
onTap: () => _navigateToDetails(index), onTap: () => _navigateToDetails(index),
isLastItem: isLast, // NEW: ارسال پارامتر به ویجت کارت isLastItem: isLast,
); );
}, },
), ),
@ -310,4 +309,4 @@ class _PodcastTabPageState extends State<PodcastTabPage> {
onRetry: () => context.read<PodcastsState>().getStudios(page: 1), onRetry: () => context.read<PodcastsState>().getStudios(page: 1),
); );
} }
} }

View File

@ -3,7 +3,6 @@
import 'package:chewie/chewie.dart'; import 'package:chewie/chewie.dart';
import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart'; import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/constants/assets.dart'; import 'package:didvan/constants/assets.dart';
import 'package:didvan/models/enums.dart'; import 'package:didvan/models/enums.dart';
import 'package:didvan/models/studio_details_data.dart'; import 'package:didvan/models/studio_details_data.dart';

View File

@ -12,13 +12,13 @@ import 'package:persian_number_utility/persian_number_utility.dart';
class PodcastListCard extends StatelessWidget { class PodcastListCard extends StatelessWidget {
final OverviewData podcast; final OverviewData podcast;
final VoidCallback onTap; final VoidCallback onTap;
final bool isLastItem; // NEW: پارامتر جدید برای تشخیص آیتم آخر final bool isLastItem;
const PodcastListCard({ const PodcastListCard({
super.key, super.key,
required this.podcast, required this.podcast,
required this.onTap, required this.onTap,
this.isLastItem = false, // NEW: مقداردهی اولیه پارامتر this.isLastItem = false,
}); });
String _formatDuration(int? duration) { String _formatDuration(int? duration) {
@ -143,7 +143,6 @@ class PodcastListCard extends StatelessWidget {
), ),
), ),
), ),
// NEW: چک میکنیم که آیا آیتم آخر است یا نه
if (!isLastItem) if (!isLastItem)
Divider( Divider(
color: Colors.grey[300], color: Colors.grey[300],
@ -155,4 +154,4 @@ class PodcastListCard extends StatelessWidget {
), ),
); );
} }
} }

View File

@ -46,13 +46,13 @@ class _NewStatisticState extends State<NewStatistic> {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
// ignore: prefer_const_constructors
HomeAppBar( HomeAppBar(
showSearchField: true, showSearchField: true,
), ),
Center( Center(
child: Container( child: Container(
padding: padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
color: Theme.of(context).colorScheme.background, color: Theme.of(context).colorScheme.background,
child: DidvanText( child: DidvanText(
'دسته‌بندی‌های کلان', 'دسته‌بندی‌های کلان',
@ -102,8 +102,7 @@ class _NewStatisticState extends State<NewStatistic> {
itemsInStatics(context, state, 1, DidvanIcons.currency_solid), itemsInStatics(context, state, 1, DidvanIcons.currency_solid),
itemsInStatics(context, state, 2, DidvanIcons.bitcoin_solid), itemsInStatics(context, state, 2, DidvanIcons.bitcoin_solid),
itemsInStatics(context, state, 3, DidvanIcons.metal_solid), itemsInStatics(context, state, 3, DidvanIcons.metal_solid),
itemsInStatics( itemsInStatics(context, state, 4, DidvanIcons.commodity_solid),
context, state, 4, DidvanIcons.commodity_solid),
itemsInStatics(context, state, 5, DidvanIcons.industry_solid, itemsInStatics(context, state, 5, DidvanIcons.industry_solid,
hasDivider: false), hasDivider: false),
const SizedBox( const SizedBox(
@ -369,4 +368,4 @@ class StatHeader extends StatelessWidget {
) )
]); ]);
} }
} }

View File

@ -112,11 +112,11 @@ class _MentionsState extends State<Mentions> {
enableEmptyState: state.comments.isEmpty, enableEmptyState: state.comments.isEmpty,
paddingEmptyState: 0, paddingEmptyState: 0,
emptyState: const EmptyState( emptyState: const EmptyState(
asset: 'lib/assets/images/empty_states/Empty_List.png', asset: 'lib/assets/images/empty_states/Empty_List.png',
title: 'دوستان خود را فراخوانی کنید', title: 'دوستان خود را فراخوانی کنید',
titleColor: Color.fromARGB(255, 0, 126, 167), titleColor: Color.fromARGB(255, 0, 126, 167),
height: 550, height: 550,
), ),
builder: (context, state, index) => Mention( builder: (context, state, index) => Mention(
key: ValueKey( key: ValueKey(
state.comments[index].id.toString() + state.comments[index].id.toString() +

View File

@ -71,7 +71,9 @@ class _NewsState extends State<News> {
title: Text( title: Text(
'دنیای فولاد', 'دنیای فولاد',
style: theme.textTheme.headlineSmall?.copyWith( style: theme.textTheme.headlineSmall?.copyWith(
color: DesignConfig.isDark? const Color.fromARGB(255, 0, 90, 119) : const Color.fromARGB(255, 0, 53, 70), color: DesignConfig.isDark
? const Color.fromARGB(255, 0, 90, 119)
: const Color.fromARGB(255, 0, 53, 70),
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 19), fontSize: 19),
), ),

View File

@ -1,3 +1,5 @@
// ignore_for_file: deprecated_member_use
import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart'; import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/assets.dart'; import 'package:didvan/constants/assets.dart';
@ -438,7 +440,6 @@ class _CategoryExpansionGroupState extends State<_CategoryExpansionGroup> {
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: CupertinoSwitch( child: CupertinoSwitch(
value: item.selected ?? false, value: item.selected ?? false,
// ignore: deprecated_member_use
activeColor: activeColor:
Theme.of(context).colorScheme.primary, Theme.of(context).colorScheme.primary,
onChanged: (value) { onChanged: (value) {

View File

@ -27,7 +27,8 @@ final List<OnboardingEntity> onboardingPages = [
OnboardingEntity( OnboardingEntity(
imagePath: 'lib/assets/images/onboarding/1.png', imagePath: 'lib/assets/images/onboarding/1.png',
title: 'هوشان', title: 'هوشان',
description: 'ارائه ابزارهای هوش مصنوعی مورد نیاز مدیران اعم از خلاصه‌ساز، ساخت عکس، تحلیل و ترسیم نمودار، تبدیل متن به صوت، ساخت ویدیو و ترجمه، امکان پرسش و پاسخ از مدل‌های زبانی مختلف و یا جستجوی هوشمند در محتوای داخلی از طریق دستیار اختصاصی هوش مصنوعی دیدوان.', description:
'ارائه ابزارهای هوش مصنوعی مورد نیاز مدیران اعم از خلاصه‌ساز، ساخت عکس، تحلیل و ترسیم نمودار، تبدیل متن به صوت، ساخت ویدیو و ترجمه، امکان پرسش و پاسخ از مدل‌های زبانی مختلف و یا جستجوی هوشمند در محتوای داخلی از طریق دستیار اختصاصی هوش مصنوعی دیدوان.',
), ),
]; ];
@ -168,7 +169,9 @@ class _OnboardingPageState extends State<OnboardingPage> {
], ],
), ),
), ),
const SizedBox(height: 10,), const SizedBox(
height: 10,
),
Expanded( Expanded(
flex: 2, flex: 2,
child: Padding( child: Padding(
@ -184,7 +187,8 @@ class _OnboardingPageState extends State<OnboardingPage> {
theme.textTheme.headlineMedium?.copyWith( theme.textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 22, fontSize: 22,
color: const Color.fromARGB(255, 0, 126, 167), color:
const Color.fromARGB(255, 0, 126, 167),
), ),
), ),
const SizedBox(height: 13), const SizedBox(height: 13),
@ -193,10 +197,10 @@ class _OnboardingPageState extends State<OnboardingPage> {
textDirection: TextDirection.rtl, textDirection: TextDirection.rtl,
textAlign: TextAlign.start, textAlign: TextAlign.start,
style: theme.textTheme.bodyMedium?.copyWith( style: theme.textTheme.bodyMedium?.copyWith(
color: const Color.fromARGB(255, 41, 41, 41), color:
height: 1.6, const Color.fromARGB(255, 41, 41, 41),
fontSize: 14 height: 1.6,
), fontSize: 14),
), ),
], ],
), ),
@ -208,7 +212,6 @@ class _OnboardingPageState extends State<OnboardingPage> {
}, },
), ),
), ),
if (_currentPage > 0) if (_currentPage > 0)
Positioned( Positioned(
top: 16, top: 16,
@ -234,7 +237,6 @@ class _OnboardingPageState extends State<OnboardingPage> {
), ),
), ),
), ),
Positioned( Positioned(
bottom: 30, bottom: 30,
left: 24, left: 24,

View File

@ -4,7 +4,6 @@ import 'package:chewie/chewie.dart';
import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart'; import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/constants/assets.dart'; import 'package:didvan/constants/assets.dart';
import 'package:didvan/models/enums.dart'; import 'package:didvan/models/enums.dart';
import 'package:didvan/models/studio_details_data.dart'; import 'package:didvan/models/studio_details_data.dart';

View File

@ -1,3 +1,5 @@
// ignore_for_file: deprecated_member_use
import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart'; import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/constants/app_icons.dart';
@ -44,7 +46,10 @@ class ChatRoomItem extends StatelessWidget {
Row( Row(
children: [ children: [
SvgPicture.asset( SvgPicture.asset(
chatRoom.lastMessage.writedByAdmin? chatRoom.lastMessage.readed ?'lib/assets/icons/sms.svg' :'lib/assets/icons/sms-notification.svg' chatRoom.lastMessage.writedByAdmin
? chatRoom.lastMessage.readed
? 'lib/assets/icons/sms.svg'
: 'lib/assets/icons/sms-notification.svg'
: 'lib/assets/icons/sms-tracking.svg', : 'lib/assets/icons/sms-tracking.svg',
height: 24, height: 24,
width: 24, width: 24,
@ -87,8 +92,12 @@ class ChatRoomItem extends StatelessWidget {
child: Padding( child: Padding(
padding: const EdgeInsets.fromLTRB(10, 8, 10, 8), padding: const EdgeInsets.fromLTRB(10, 8, 10, 8),
child: Text( child: Text(
chatRoom.lastMessage.writedByAdmin? chatRoom.lastMessage.readed chatRoom.lastMessage.writedByAdmin
? 'پاسخ داده شده' : 'خوانده نشده' : 'در انتظار پاسخ',), ? chatRoom.lastMessage.readed
? 'پاسخ داده شده'
: 'خوانده نشده'
: 'در انتظار پاسخ',
),
), ),
), ),
// Icon( // Icon(

View File

@ -1,3 +1,5 @@
// ignore_for_file: avoid_print, deprecated_member_use
import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart'; import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/constants/app_icons.dart';
@ -159,11 +161,12 @@ class _ProfilePageState extends State<ProfilePage>
image: image:
(user.photo != null && user.photo!.isNotEmpty) (user.photo != null && user.photo!.isNotEmpty)
? DecorationImage( ? DecorationImage(
image: NetworkImage('https://api.didvan.app${user.photo!}', image: NetworkImage(
headers: { 'https://api.didvan.app${user.photo!}',
'Authorization': headers: {
'Bearer ${RequestService.token}', 'Authorization':
}), 'Bearer ${RequestService.token}',
}),
fit: BoxFit.cover, fit: BoxFit.cover,
) )
: null, : null,

View File

@ -1,3 +1,5 @@
// ignore_for_file: deprecated_member_use
import 'dart:async'; import 'dart:async';
import 'dart:math'; import 'dart:math';

View File

@ -1,7 +1,6 @@
// ignore_for_file: deprecated_member_use, avoid_print // ignore_for_file: deprecated_member_use, avoid_print
import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/assets.dart'; import 'package:didvan/constants/assets.dart';
import 'package:didvan/main.dart'; import 'package:didvan/main.dart';
import 'package:didvan/providers/media.dart'; import 'package:didvan/providers/media.dart';
@ -40,7 +39,7 @@ class _SplashState extends State<Splash> with TickerProviderStateMixin {
_pulseController = AnimationController( _pulseController = AnimationController(
duration: const Duration(milliseconds: 1500), duration: const Duration(milliseconds: 1500),
vsync: this, vsync: this,
); );
_pulseAnimation = Tween<double>( _pulseAnimation = Tween<double>(
@ -195,18 +194,6 @@ class _SplashState extends State<Splash> with TickerProviderStateMixin {
], ],
const Spacer(flex: 3), const Spacer(flex: 3),
Padding(
padding: const EdgeInsets.only(bottom: 24),
child: Text(
'توسعه یافته توسط فرتاک',
style: TextStyle(
fontSize: 12,
color: colorScheme.onBackground.withOpacity(0.4),
fontWeight: FontWeight.w400,
),
),
),
], ],
), ),
), ),
@ -251,15 +238,16 @@ class _SplashState extends State<Splash> with TickerProviderStateMixin {
await mediaProvider.getDownloadsList(); await mediaProvider.getDownloadsList();
} }
// اگر اینجا خطای سرور رخ دهد (به خاطر تغییر در user.dart)، کد به بلوک catch میرود
final result = await userProvider.getUserInfo(); final result = await userProvider.getUserInfo();
if (!result) { if (!result) {
// این بخش فقط زمانی اجرا میشود که سرور پاسخ ۴۰۱ (نامعتبر بودن توکن) داده باشد
print("no results were returned for user info"); 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"); print("error in case of no user info result: $e");
// catch
} }
navigatorKey.currentState!.pushNamedAndRemoveUntil( navigatorKey.currentState!.pushNamedAndRemoveUntil(
@ -281,7 +269,6 @@ class _SplashState extends State<Splash> with TickerProviderStateMixin {
if (destinationRoute == Routes.home) { if (destinationRoute == Routes.home) {
print("destination route was home and init uri is $initialURI"); print("destination route was home and init uri is $initialURI");
// (routeArguments as Map)['deepLinkUri'] = initialURI;
initialURI = null; initialURI = null;
} }
@ -296,6 +283,7 @@ class _SplashState extends State<Splash> with TickerProviderStateMixin {
arguments: routeArguments, arguments: routeArguments,
); );
} catch (e) { } catch (e) {
// اگر خطای سرور رخ دهد، اینجا گرفته میشود و دکمه تلاش مجدد نشان داده میشود
setState(() { setState(() {
_errorOccured = true; _errorOccured = true;
}); });

File diff suppressed because it is too large Load Diff

View File

@ -149,8 +149,7 @@ class _AiVoiceChatDialogState extends State<AiVoiceChatDialog>
_isAiSpeaking = false; _isAiSpeaking = false;
}); });
} }
// <<< راهحل مشکل ۲: ریست کردن پاسخ قبلی
if (_currentAiResponse != null) { if (_currentAiResponse != null) {
setState(() { setState(() {
_currentAiResponse = null; _currentAiResponse = null;
@ -178,7 +177,6 @@ class _AiVoiceChatDialogState extends State<AiVoiceChatDialog>
await Future.delayed(const Duration(seconds: 2)); await Future.delayed(const Duration(seconds: 2));
// <<< راهحل مشکل ۲: بررسی وضعیت قبل از شروع ضبط
if (mounted && _isPreparing) { if (mounted && _isPreparing) {
setState(() { setState(() {
_isPreparing = false; _isPreparing = false;
@ -188,11 +186,9 @@ class _AiVoiceChatDialogState extends State<AiVoiceChatDialog>
_preparingController.stop(); _preparingController.stop();
_waveController.repeat(); _waveController.repeat();
} else if (mounted) { } else if (mounted) {
// اگر کاربر انگشت خود را برداشته بود (_isPreparing false شده) await _audioRecorder.stop();
await _audioRecorder.stop(); // ضبط را متوقف کن
_waveController.stop(); _waveController.stop();
} }
// >>>
} }
} catch (e) { } catch (e) {
setState(() { setState(() {
@ -200,7 +196,6 @@ class _AiVoiceChatDialogState extends State<AiVoiceChatDialog>
_isRecording = false; _isRecording = false;
_statusText = 'خطا در شروع ضبط صدا'; _statusText = 'خطا در شروع ضبط صدا';
}); });
// <<< راهحل مشکل ۲: توقف انیمیشنها در صورت خطا
_preparingController.stop(); _preparingController.stop();
_waveController.stop(); _waveController.stop();
// >>> // >>>
@ -475,8 +470,8 @@ class _AiVoiceChatDialogState extends State<AiVoiceChatDialog>
top: 0, top: 0,
child: _buildVisualization(), child: _buildVisualization(),
), ),
// وقتی پاسخ هست، کارت پاسخ را نمایش میدهیم if (_currentAiResponse != null &&
if (_currentAiResponse != null && _currentAiResponse!.isNotEmpty) _currentAiResponse!.isNotEmpty)
Positioned( Positioned(
bottom: 120, bottom: 120,
left: 24, left: 24,
@ -484,7 +479,6 @@ class _AiVoiceChatDialogState extends State<AiVoiceChatDialog>
child: _buildResponsePreview(), child: _buildResponsePreview(),
) )
else else
// در غیر این صورت، متن status را نمایش میدهیم
Positioned( Positioned(
bottom: 130, bottom: 130,
left: 24, left: 24,
@ -709,7 +703,8 @@ class _AiVoiceChatDialogState extends State<AiVoiceChatDialog>
shape: BoxShape.circle, shape: BoxShape.circle,
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: const Color(0xFF00AAFF).withOpacity(0.4), color: const Color(0xFF00AAFF)
.withOpacity(0.4),
blurRadius: 12, blurRadius: 12,
spreadRadius: 2, spreadRadius: 2,
), ),
@ -872,7 +867,8 @@ class _AiVoiceChatDialogState extends State<AiVoiceChatDialog>
shape: BoxShape.circle, shape: BoxShape.circle,
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: const Color(0xFF00AAFF).withOpacity(0.4), color:
const Color(0xFF00AAFF).withOpacity(0.4),
blurRadius: 15, blurRadius: 15,
spreadRadius: 2, spreadRadius: 2,
), ),
@ -953,7 +949,8 @@ class _AiVoiceChatDialogState extends State<AiVoiceChatDialog>
Expanded( Expanded(
child: ElevatedButton.icon( child: ElevatedButton.icon(
onPressed: () { onPressed: () {
Clipboard.setData(ClipboardData(text: _currentAiResponse ?? '')); Clipboard.setData(ClipboardData(
text: _currentAiResponse ?? ''));
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: const DidvanText( content: const DidvanText(
@ -1033,15 +1030,13 @@ class _AiVoiceChatDialogState extends State<AiVoiceChatDialog>
_startRecording(); _startRecording();
} }
}, },
// <<< راهحل مشکل ۲: (بدون تغییر، همچنان پابرجاست)
onLongPressEnd: (_) { onLongPressEnd: (_) {
if (_isRecording) { if (_isRecording) {
_stopRecording(); _stopRecording();
} else if (_isPreparing) { } else if (_isPreparing) {
// کاربر انگشت خود را در حین آمادهسازی برداشت
setState(() { setState(() {
_isPreparing = false; _isPreparing = false;
_statusText = 'برای شروع مکالمه، دکمه میکروفون را نگه دارید'; _statusText = 'برای شروع مکالمه، دکمه میکروفون را نگه دارید';
}); });
_preparingController.stop(); _preparingController.stop();
_waveController.stop(); _waveController.stop();
@ -1236,4 +1231,4 @@ class PreparingSpinnerPainter extends CustomPainter {
bool shouldRepaint(covariant PreparingSpinnerPainter oldDelegate) { bool shouldRepaint(covariant PreparingSpinnerPainter oldDelegate) {
return true; return true;
} }
} }

View File

@ -1,3 +1,5 @@
// ignore_for_file: deprecated_member_use
import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart'; import 'package:didvan/config/theme_data.dart';
import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/didvan/text.dart';
@ -312,7 +314,10 @@ class _NavBarItemState extends State<_NavBarItem>
duration: const Duration(milliseconds: 150), duration: const Duration(milliseconds: 150),
child: DidvanText( child: DidvanText(
widget.title, widget.title,
style: Theme.of(context).textTheme.bodySmall?.copyWith(fontSize: 11), style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(fontSize: 11),
color: Theme.of(context).colorScheme.caption, color: Theme.of(context).colorScheme.caption,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),

View File

@ -491,9 +491,53 @@ class _DidvanPageViewState extends State<DidvanPageView> {
const EdgeInsets.symmetric( const EdgeInsets.symmetric(
horizontal: 16, horizontal: 16,
vertical: 4), vertical: 4),
child: MultitypeOverview( child: GestureDetector(
item: item.relatedContents[i], onTap: () {
onMarkChanged: (id, value) {}, final relatedItem =
item.relatedContents[i];
if (relatedItem.type ==
'news') {
Navigator.of(context)
.pushNamed(
Routes.newsDetails,
arguments: {
'id': relatedItem.id,
'args':
const NewsRequestArgs(
page: 0),
'onMarkChanged':
(id, value) {},
'hasUnmarkConfirmation':
false,
},
);
} else if (relatedItem
.type ==
'radar') {
Navigator.of(context)
.pushNamed(
Routes.radarDetails,
arguments: {
'id': relatedItem.id,
'args':
const RadarRequestArgs(
page: 0),
'onMarkChanged':
(id, value) {},
'onCommentsChanged':
(id, count) {},
'hasUnmarkConfirmation':
false,
},
);
}
},
child: MultitypeOverview(
item:
item.relatedContents[i],
onMarkChanged:
(id, value) {},
),
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),

View File

@ -0,0 +1,46 @@
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
class GlassShader {
ui.FragmentProgram? _program;
ui.FragmentShader? _shader;
Future<void> initialize() async {
_program = await ui.FragmentProgram.fromAsset(
'lib/shaders/liquid_glass_lens.frag');
_shader = _program!.fragmentShader();
}
ui.FragmentShader? get shader => _shader;
void updateUniforms({
required Size widgetSize,
required Size textureSize,
required Offset widgetOffset,
required double distortion,
required double blur,
required double dispersion,
required ui.Image texture,
}) {
if (_shader == null) return;
// uniform vec2 uResolution;
// uniform float uDistortionStrength;
// uniform float uBlurIntensity;
// uniform float uDispersion;
// uniform sampler2D uTexture;
// uniform vec2 uWidgetOffset;
// uniform vec2 uTextureSize;
_shader!.setFloat(0, widgetSize.width);
_shader!.setFloat(1, widgetSize.height);
_shader!.setFloat(2, distortion);
_shader!.setFloat(3, blur);
_shader!.setFloat(4, dispersion);
_shader!.setImageSampler(0, texture);
_shader!.setFloat(5, widgetOffset.dx);
_shader!.setFloat(6, widgetOffset.dy);
_shader!.setFloat(7, textureSize.width);
_shader!.setFloat(8, textureSize.height);
}
}

View File

@ -0,0 +1,131 @@
import 'dart:async';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
import 'glass_logic.dart';
class ShaderPainter extends CustomPainter {
ShaderPainter(this.shader);
final ui.FragmentShader shader;
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()..shader = shader;
canvas.drawRect(Offset.zero & size, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
class LiquidGlassContainer extends StatefulWidget {
final Widget child;
final GlobalKey backgroundKey;
final GlassShader shader;
final double distortion;
final double blur;
final double dispersion;
final BorderRadius? borderRadius;
const LiquidGlassContainer({
super.key,
required this.child,
required this.backgroundKey,
required this.shader,
this.distortion = 1.0,
this.blur = 5.0,
this.dispersion = 1.0,
this.borderRadius,
});
@override
State<LiquidGlassContainer> createState() => _LiquidGlassContainerState();
}
class _LiquidGlassContainerState extends State<LiquidGlassContainer>
with SingleTickerProviderStateMixin {
ui.Image? _capturedImage;
late Ticker _ticker;
@override
void initState() {
super.initState();
_ticker = createTicker((_) => _captureBackground());
_ticker.start();
}
@override
void dispose() {
_ticker.dispose();
super.dispose();
}
Future<void> _captureBackground() async {
final boundaryContext = widget.backgroundKey.currentContext;
if (boundaryContext == null) return;
final renderObject = boundaryContext.findRenderObject();
if (renderObject is! RenderRepaintBoundary) return;
try {
final image = await renderObject.toImage(pixelRatio: 1.0);
if (mounted) {
setState(() {
_capturedImage = image;
});
}
} catch (e) {
// Ignored
}
}
@override
Widget build(BuildContext context) {
Offset offsetFromBackground = Offset.zero;
Size bgSize = Size.zero;
final RenderBox? box = context.findRenderObject() as RenderBox?;
final RenderBox? backgroundBox =
widget.backgroundKey.currentContext?.findRenderObject() as RenderBox?;
if (box != null && backgroundBox != null) {
final absolutePos = box.localToGlobal(Offset.zero);
final backgroundPos = backgroundBox.localToGlobal(Offset.zero);
offsetFromBackground = absolutePos - backgroundPos;
bgSize = backgroundBox.size;
}
return ClipRRect(
borderRadius: widget.borderRadius ?? BorderRadius.zero,
child: Stack(
children: [
if (_capturedImage != null &&
widget.shader.shader != null &&
backgroundBox != null)
Positioned.fill(
child: CustomPaint(
painter: ShaderPainter(widget.shader.shader!),
),
),
Builder(builder: (ctx) {
if (_capturedImage != null &&
widget.shader.shader != null &&
backgroundBox != null) {
widget.shader.updateUniforms(
widgetSize: (box?.size ?? Size.zero),
textureSize: bgSize,
widgetOffset: offsetFromBackground,
distortion: widget.distortion,
blur: widget.blur,
dispersion: widget.dispersion,
texture: _capturedImage!,
);
}
return const SizedBox.shrink();
}),
widget.child,
],
),
);
}
}

View File

@ -45,7 +45,9 @@ class HomeAppBar extends StatelessWidget {
return Column( return Column(
children: [ children: [
const SizedBox(height: 10,), const SizedBox(
height: 10,
),
Padding( Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
child: Row( child: Row(
@ -105,8 +107,8 @@ class HomeAppBar extends StatelessWidget {
), ),
Container( Container(
color: const Color.fromARGB(255, 224, 224, 224), color: const Color.fromARGB(255, 224, 224, 224),
height: 20, height: 20,
width: 1.5, width: 1.5,
), ),
GestureDetector( GestureDetector(
onTap: () { onTap: () {

View File

@ -534,7 +534,6 @@ class _HistoryDrawerContentState extends State<HistoryDrawerContent> {
...last7DaysChats.map((chat) => ...last7DaysChats.map((chat) =>
_buildChatItem(chat, historyState)), _buildChatItem(chat, historyState)),
], ],
// '۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'
if (last30DaysChats.isNotEmpty) ...[ if (last30DaysChats.isNotEmpty) ...[
_buildSectionHeader('۳۰ روز اخیر'), _buildSectionHeader('۳۰ روز اخیر'),
...last30DaysChats.map((chat) => ...last30DaysChats.map((chat) =>
@ -616,7 +615,8 @@ class _HistoryDrawerContentState extends State<HistoryDrawerContent> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
DidvanText( DidvanText(
_formatDateFromString(chat.updatedAt).toPersianDigit(), _formatDateFromString(chat.updatedAt)
.toPersianDigit(),
fontSize: 12, fontSize: 12,
color: Colors.grey[600], color: Colors.grey[600],
), ),

View File

@ -127,7 +127,6 @@ class LogoAppBar extends StatelessWidget implements PreferredSizeWidget {
void _onChanged(String value, BuildContext context) { void _onChanged(String value, BuildContext context) {
final state = context.read<HomeState>(); final state = context.read<HomeState>();
// Use the improved search functionality from HomeState
if (value.length >= 2) { if (value.length >= 2) {
state.onSearchChanged(value); state.onSearchChanged(value);
} else if (value.isEmpty) { } else if (value.isEmpty) {

View File

@ -22,7 +22,7 @@ class MultitypeOverview extends StatelessWidget {
final bool enableCaption; final bool enableCaption;
final bool enableBookmark; final bool enableBookmark;
final bool showDivider; final bool showDivider;
final bool useCardStyle; // **جدید: برای کنترل استایل کارت** final bool useCardStyle;
const MultitypeOverview({ const MultitypeOverview({
Key? key, Key? key,
@ -32,11 +32,10 @@ class MultitypeOverview extends StatelessWidget {
this.enableCaption = false, this.enableCaption = false,
this.enableBookmark = false, this.enableBookmark = false,
this.showDivider = true, this.showDivider = true,
this.useCardStyle = false, // **مقدار پیشفرض false** this.useCardStyle = false,
}) : super(key: key); }) : super(key: key);
get _targetPageArgs { get _targetPageArgs {
// ...
if (item.type == 'radar') { if (item.type == 'radar') {
return const RadarRequestArgs(page: 0); return const RadarRequestArgs(page: 0);
} }
@ -47,7 +46,6 @@ class MultitypeOverview extends StatelessWidget {
} }
String? get _targetPageRouteName { String? get _targetPageRouteName {
// ...
if (item.type == 'radar') { if (item.type == 'radar') {
return Routes.radarDetails; return Routes.radarDetails;
} }
@ -64,7 +62,6 @@ class MultitypeOverview extends StatelessWidget {
} }
String get _icon { String get _icon {
// ...
switch (item.type) { switch (item.type) {
case 'radar': case 'radar':
return 'lib/assets/icons/Pouyesh_Ofogh_New.svg'; return 'lib/assets/icons/Pouyesh_Ofogh_New.svg';
@ -88,7 +85,6 @@ class MultitypeOverview extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// محتوای داخلی کارت
final innerContent = Column( final innerContent = Column(
children: [ children: [
Row( Row(
@ -249,7 +245,6 @@ class MultitypeOverview extends StatelessWidget {
], ],
], ],
), ),
// **اگر useCardStyle فعال نباشد، فاصله و خط جداکننده قبلی را اضافه میکنیم**
if (!useCardStyle) ...[ if (!useCardStyle) ...[
const SizedBox( const SizedBox(
height: 10, height: 10,
@ -262,7 +257,6 @@ class MultitypeOverview extends StatelessWidget {
], ],
); );
// **اعمال استایل کارت (Container با Border و Padding) به صورت شرطی**
if (useCardStyle) { if (useCardStyle) {
return Padding( return Padding(
padding: const EdgeInsets.all(20.0), padding: const EdgeInsets.all(20.0),
@ -281,7 +275,7 @@ class MultitypeOverview extends StatelessWidget {
); );
} }
return innerContent; // در غیر این صورت، فقط محتوای داخلی را برمیگرداند. return innerContent;
} }
static Widget get placeholder => const DidvanCard( static Widget get placeholder => const DidvanCard(
@ -306,4 +300,4 @@ class MultitypeOverview extends StatelessWidget {
], ],
), ),
); );
} }

View File

@ -6,7 +6,6 @@ import 'package:didvan/views/widgets/bookmark_button.dart';
import 'package:didvan/views/widgets/didvan/card.dart'; import 'package:didvan/views/widgets/didvan/card.dart';
import 'package:didvan/views/widgets/didvan/divider.dart'; import 'package:didvan/views/widgets/didvan/divider.dart';
import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:didvan/views/widgets/liked_button.dart';
import 'package:didvan/views/widgets/shimmer_placeholder.dart'; import 'package:didvan/views/widgets/shimmer_placeholder.dart';
import 'package:didvan/views/widgets/skeleton_image.dart'; import 'package:didvan/views/widgets/skeleton_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -197,4 +196,4 @@ class NewsOverview extends StatelessWidget {
], ],
), ),
); );
} }

View File

@ -6,7 +6,6 @@ import 'package:didvan/views/widgets/bookmark_button.dart';
import 'package:didvan/views/widgets/didvan/card.dart'; import 'package:didvan/views/widgets/didvan/card.dart';
import 'package:didvan/views/widgets/didvan/divider.dart'; import 'package:didvan/views/widgets/didvan/divider.dart';
import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:didvan/views/widgets/liked_button.dart';
import 'package:didvan/views/widgets/shimmer_placeholder.dart'; import 'package:didvan/views/widgets/shimmer_placeholder.dart';
import 'package:didvan/views/widgets/skeleton_image.dart'; import 'package:didvan/views/widgets/skeleton_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -176,8 +175,7 @@ class RadarOverview extends StatelessWidget {
svgIconOn: 'lib/assets/icons/bookmark_fill.svg', svgIconOn: 'lib/assets/icons/bookmark_fill.svg',
svgIconOff: 'lib/assets/icons/archive-tick.svg', svgIconOff: 'lib/assets/icons/archive-tick.svg',
color: const Color.fromARGB(255, 0, 126, 167), color: const Color.fromARGB(255, 0, 126, 167),
unbookmarkedColor: unbookmarkedColor: Theme.of(context).colorScheme.caption,
Theme.of(context).colorScheme.caption,
), ),
], ],
), ),

View File

@ -13,7 +13,7 @@ class EmptyConnection extends StatelessWidget {
title: 'اتصال اینترنت برقرار نیست', title: 'اتصال اینترنت برقرار نیست',
titleColor: const Color.fromARGB(255, 0, 126, 167), titleColor: const Color.fromARGB(255, 0, 126, 167),
action: onRetry, action: onRetry,
buttonTitle: 'تلاش مجدد' , buttonTitle: 'تلاش مجدد',
); );
} }
} }

View File

@ -29,8 +29,11 @@ class EmptyState extends StatelessWidget {
final isSvg = asset.toLowerCase().endsWith('.svg'); final isSvg = asset.toLowerCase().endsWith('.svg');
return Column( return Column(
mainAxisAlignment: subtitle != null ? MainAxisAlignment.start : MainAxisAlignment.center, mainAxisAlignment:
crossAxisAlignment: subtitle != null ? CrossAxisAlignment.start : CrossAxisAlignment.center, subtitle != null ? MainAxisAlignment.start : MainAxisAlignment.center,
crossAxisAlignment: subtitle != null
? CrossAxisAlignment.start
: CrossAxisAlignment.center,
children: [ children: [
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,

View File

@ -1,3 +1,5 @@
// ignore_for_file: prefer_const_constructors
import 'package:didvan/models/enums.dart'; import 'package:didvan/models/enums.dart';
import 'package:didvan/providers/core.dart'; import 'package:didvan/providers/core.dart';
import 'package:didvan/views/widgets/state_handlers/empty_connection.dart'; import 'package:didvan/views/widgets/state_handlers/empty_connection.dart';

View File

@ -933,6 +933,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.1.1"
liquid_glass_renderer:
dependency: "direct main"
description:
name: liquid_glass_renderer
sha256: "789be157494b38cdef7607bb9b63e7fe3fab76fae16149e52fa313c2d858cb3f"
url: "https://pub.dev"
source: hosted
version: "0.2.0-dev.4"
list_counter: list_counter:
dependency: transitive dependency: transitive
description: description:
@ -949,6 +957,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.0" version: "2.5.0"
logging:
dependency: transitive
description:
name: logging
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
url: "https://pub.dev"
source: hosted
version: "1.3.0"
markdown: markdown:
dependency: transitive dependency: transitive
description: description:
@ -1005,6 +1021,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.6" version: "1.0.6"
motor:
dependency: transitive
description:
name: motor
sha256: d6fd00496a2b934f8f54e660acc0d039d0e4bf8e667b6977b0480be97385779e
url: "https://pub.dev"
source: hosted
version: "1.0.1"
nested: nested:
dependency: transitive dependency: transitive
description: description:

View File

@ -117,6 +117,7 @@ dependencies:
syncfusion_flutter_charts: ^31.1.19 syncfusion_flutter_charts: ^31.1.19
flutter_animate: ^4.5.2 flutter_animate: ^4.5.2
syncfusion_flutter_pdfviewer: ^31.1.21 syncfusion_flutter_pdfviewer: ^31.1.21
liquid_glass_renderer: ^0.2.0-dev.4
# image_gallery_saver: ^2.0.3 # image_gallery_saver: ^2.0.3
# fading_edge_scrollview: ^4.1.1 # fading_edge_scrollview: ^4.1.1
@ -135,6 +136,10 @@ dev_dependencies:
# The following section is specific to Flutter. # The following section is specific to Flutter.
flutter: flutter:
shaders:
- lib/shaders/liquid_glass_lens.frag
# - lib/shaders/static_liquid.frag
# - lib/shaders/water_ripple.frag
# The following line ensures that the Material Icons font is # The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in # included with your application, so that you can use the icons in
# the material Icons class. # the material Icons class.