diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index bf26010..7cf4c38 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -116,6 +116,9 @@ android:screenOrientation="portrait" android:exported="true" android:theme="@style/Theme.AppCompat.Light.NoActionBar" /> + diff --git a/lib/main.dart b/lib/main.dart index c61c2ad..dbefc9a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,42 +1,39 @@ -// ignore_for_file: deprecated_member_use - import 'dart:async'; import 'dart:io'; +import 'package:app_links/app_links.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/firebase_options.dart'; -import 'package:didvan/models/notification_message.dart'; import 'package:didvan/models/requests/news.dart'; import 'package:didvan/models/requests/radar.dart'; -import 'package:didvan/providers/media.dart'; -import 'package:didvan/providers/theme.dart'; -import 'package:didvan/providers/user.dart'; import 'package:didvan/routes/route_generator.dart'; import 'package:didvan/routes/routes.dart'; import 'package:didvan/services/app_home_widget/home_widget_repository.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/notification_service.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_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/podcasts/podcasts_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 navigatorKey = GlobalKey(); Uri? initialURI; @@ -52,29 +49,34 @@ void main() async { runZonedGuarded( () async { WidgetsFlutterBinding.ensureInitialized(); + try { if (!kIsWeb) { HomeWidget.registerBackgroundCallback(_backgroundCallbackHomeWidget); - HomeWidget.registerInteractivityCallback(_backgroundCallbackHomeWidget); + HomeWidget.registerInteractivityCallback( + _backgroundCallbackHomeWidget); await NotificationService.initializeNotification(); - try { - if (Platform.isAndroid) { + + if (Platform.isAndroid) { + try { 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(); } catch (e) { - debugPrint(e.toString()); + debugPrint("Initialization Error: $e"); } await SentryFlutter.init( (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.profilesSampleRate = 1.0; }, @@ -95,9 +97,24 @@ class Didvan extends StatefulWidget { } class _DidvanState extends State with WidgetsBindingObserver { - late AppLinks _appLinks; + late final AppLinks _appLinks; StreamSubscription? _linkSubscription; + final List _applicationProviders = [ + ChangeNotifierProvider(create: (_) => PodcastsState()), + ChangeNotifierProvider(create: (_) => MediaProvider()), + ChangeNotifierProvider(create: (_) => UserProvider()), + ChangeNotifierProvider(create: (_) => ThemeProvider()), + ChangeNotifierProvider( + create: (_) => StudioDetailsState()), + ChangeNotifierProvider( + create: (_) => HistoryAiChatState()), + ChangeNotifierProvider(create: (_) => AiState()), + ChangeNotifierProvider(create: (_) => AiChatState()), + ChangeNotifierProvider( + create: (_) => BotAssistantsState()), + ]; + @override void initState() { super.initState(); @@ -124,49 +141,47 @@ class _DidvanState extends State with WidgetsBindingObserver { } void _navigateTo(Uri uri) { - if (mounted) { - String path = uri.path; - final Map params = uri.queryParameters; + if (!mounted) return; - final String? token = params['token']; + final Map params = uri.queryParameters; + final String? token = params['token']; - if (token != null) { - //todo: this didnt work - print("DEBUG: received token in url, token: $token, path: $path"); - RequestService.token = token; + if (token != null) { + debugPrint("DeepLink Token Received: $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)}, + ); } - - 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)}, - ); - } + } else if (path.startsWith('/radar/')) { + final String idStr = path.split('/radar/').last; + final int? id = int.tryParse(idStr); + if (id != null) { + navigatorKey.currentState?.pushNamed( + Routes.radarDetails, + arguments: {'id': id, 'args': const RadarRequestArgs(page: 0)}, + ); } } } @override void didChangeAppLifecycleState(AppLifecycleState state) async { - if (!kIsWeb) { - if (state == AppLifecycleState.resumed) { - var r = await HomeWidget.getWidgetData("cRoute", defaultValue: ''); - if (r.toString() != Routes.splash) { - await HomeWidgetRepository.decideWhereToGo(); - NotificationMessage? data = HomeWidgetRepository.data; - if (data != null) { - await HomeWidgetRepository.decideWhereToGoNotif(); - } + if (!kIsWeb && state == AppLifecycleState.resumed) { + final route = await HomeWidget.getWidgetData("cRoute", defaultValue: ''); + if (route.toString() != Routes.splash) { + await HomeWidgetRepository.decideWhereToGo(); + if (HomeWidgetRepository.data != null) { + await HomeWidgetRepository.decideWhereToGoNotif(); } } } @@ -175,60 +190,60 @@ class _DidvanState extends State with WidgetsBindingObserver { @override Widget build(BuildContext context) { return MultiProvider( - providers: [ - ChangeNotifierProvider(create: (context) => PodcastsState()), - ChangeNotifierProvider(create: (context) => MediaProvider()), - ChangeNotifierProvider(create: (context) => UserProvider()), - ChangeNotifierProvider(create: (context) => ThemeProvider()), - ChangeNotifierProvider(create: (context) => StudioDetailsState()), - ChangeNotifierProvider(create: (context) => HistoryAiChatState()), - ChangeNotifierProvider(create: (context) => AiState()), - ChangeNotifierProvider(create: (context) => AiChatState()), - ChangeNotifierProvider(create: (context) => BotAssistantsState()), - ], + providers: _applicationProviders, child: Consumer( - builder: (context, themeProvider, child) => Container( - color: Theme.of(context).colorScheme.surface, - child: SafeArea( - child: MaterialApp( - scrollBehavior: MyCustomScrollBehavior(), - navigatorKey: navigatorKey, - debugShowCheckedModeBanner: false, - title: 'Didvan', - theme: LightThemeConfig.themeData.copyWith( - bottomSheetTheme: const BottomSheetThemeData( - surfaceTintColor: Colors.transparent, - backgroundColor: Colors.transparent), - textTheme: LightThemeConfig.themeData.textTheme.apply( - fontFamily: themeProvider.fontFamily, - )), - darkTheme: DarkThemeConfig.themeData.copyWith( - bottomSheetTheme: const BottomSheetThemeData( - surfaceTintColor: Colors.transparent, - backgroundColor: Colors.transparent), - textTheme: DarkThemeConfig.themeData.textTheme.apply( - fontFamily: themeProvider.fontFamily, - )), - color: LightThemeConfig.themeData.primaryColor, - themeMode: themeProvider.themeMode, - onGenerateRoute: (settings) => - RouteGenerator.generateRoute(settings), - builder: BotToastInit(), - navigatorObservers: [BotToastNavigatorObserver()], - initialRoute: "/", - localizationsDelegates: const [ - GlobalCupertinoLocalizations.delegate, - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - ], - supportedLocales: const [ - Locale("fa", "IR"), - ], - locale: const Locale("fa", "IR"), + builder: (context, themeProvider, child) { + final lightTheme = LightThemeConfig.themeData.copyWith( + bottomSheetTheme: const BottomSheetThemeData( + surfaceTintColor: Colors.transparent, + backgroundColor: Colors.transparent, ), - ), - ), + 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"), + ), + ), + ); + }, ), ); } -} \ No newline at end of file +} diff --git a/lib/models/home_page_content/content.dart b/lib/models/home_page_content/content.dart index dc89c3d..172912a 100644 --- a/lib/models/home_page_content/content.dart +++ b/lib/models/home_page_content/content.dart @@ -8,6 +8,7 @@ class MainPageContentType { final List subtitles; final int? duration; final String? description; + final String? riskScore; MainPageContentType({ required this.id, @@ -19,6 +20,7 @@ class MainPageContentType { required this.subtitles, this.duration, this.description, + this.riskScore, }); factory MainPageContentType.fromJson(Map json) => @@ -32,5 +34,6 @@ class MainPageContentType { subtitles: List.from(json['subtitles']), duration: json['duration'], description: json['description'], + riskScore: json['score'], ); } diff --git a/lib/providers/user.dart b/lib/providers/user.dart index 41f911f..1618f11 100644 --- a/lib/providers/user.dart +++ b/lib/providers/user.dart @@ -34,9 +34,6 @@ class UserProvider extends CoreProvier { int get unreadMessageCount => _unreadMessageCount; - // static final List _radarMarkQueue = []; - // static final List _newsMarkQueue = []; - // static final List _studioMarkQueue = []; static final List _statisticMarkQueue = []; static final List _itemMarkQueue = []; @@ -76,7 +73,7 @@ class UserProvider extends CoreProvier { } } else { 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; } } catch (e) { @@ -109,6 +106,7 @@ class UserProvider extends CoreProvier { final RequestService service = RequestService(RequestHelper.userInfo); await service.httpGet(); + // اگر توکن نامعتبر است (401)، فالس برمی‌گردانیم تا توکن پاک شود if (service.statusCode == 401) { print("UserProvider: getUserInfo failed - Unauthorized (401)."); isAuthenticated = false; @@ -150,6 +148,13 @@ class UserProvider extends CoreProvier { print( "UserProvider: getUserInfo failed. Status: ${service.statusCode}, Error: ${service.errorMessage}"); isAuthenticated = false; + + // اصلاح مهم: اگر خطا 401 نیست (مثلاً مشکل سرور یا اینترنت)، Exception پرتاب می‌کنیم + // تا در Splash وارد بخش catch شود و توکن پاک نشود. + if (service.statusCode != 401) { + throw Exception("Server Error or Connection Issue: ${service.statusCode}"); + } + return false; } @@ -295,56 +300,6 @@ class UserProvider extends CoreProvier { } } - // static Future 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 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 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 changeStatisticMark(int id, bool value) async { _statisticMarkQueue.add(MapEntry(id, value)); Future.delayed(const Duration(milliseconds: 500), () async { @@ -387,12 +342,4 @@ class UserProvider extends CoreProvier { return false; } } - - // Future getUnreadMessageCount() async { - // final RequestService service = RequestService(RequestHelper.directs); - // await service.httpGet(); - // if (service.isSuccess) { - // _unreadMessageCount = service.result['unread'] ?? 0; - // } - // } -} +} \ No newline at end of file diff --git a/lib/shaders/liquid_glass_lens.frag b/lib/shaders/liquid_glass_lens.frag new file mode 100644 index 0000000..d8b5ca3 --- /dev/null +++ b/lib/shaders/liquid_glass_lens.frag @@ -0,0 +1,82 @@ +#include + +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); +} \ No newline at end of file diff --git a/lib/views/ai/ai_chat_page.dart b/lib/views/ai/ai_chat_page.dart index aea5cb7..9d4bae6 100644 --- a/lib/views/ai/ai_chat_page.dart +++ b/lib/views/ai/ai_chat_page.dart @@ -964,7 +964,8 @@ class _AiChatPageState extends State with TickerProviderStateMixin { .withOpacity(0.9), border: Border.all( 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, ), ), @@ -1332,7 +1333,7 @@ class _AiChatPageState extends State with TickerProviderStateMixin { margin: const EdgeInsets.fromLTRB(8, 8, 8, 0), child: Row( children: [ - const Icon(Icons.file_copy), + SvgPicture.asset('lib/assets/icons/copy.svg',), const SizedBox(width: 12), Expanded( child: Column( diff --git a/lib/views/ai/history_ai_chat_page.dart b/lib/views/ai/history_ai_chat_page.dart index 8105c5c..35360dc 100644 --- a/lib/views/ai/history_ai_chat_page.dart +++ b/lib/views/ai/history_ai_chat_page.dart @@ -454,7 +454,9 @@ class _HistoryAiChatPageState extends State { DateTime.parse(chat .updatedAt .toString()) - .toPersianDateStr(monthString: ''), + .toPersianDateStr( + monthString: + ''), style: const TextStyle( fontSize: diff --git a/lib/views/authentication/screens/password.dart b/lib/views/authentication/screens/password.dart index 581df8c..ffaed9e 100644 --- a/lib/views/authentication/screens/password.dart +++ b/lib/views/authentication/screens/password.dart @@ -51,7 +51,10 @@ class _PasswordInputState extends State { onTap: () => state.currentPageIndex++, 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, ), ), diff --git a/lib/views/authentication/screens/reset_password.dart b/lib/views/authentication/screens/reset_password.dart index 81690c7..084c1c6 100644 --- a/lib/views/authentication/screens/reset_password.dart +++ b/lib/views/authentication/screens/reset_password.dart @@ -23,6 +23,8 @@ class _ResetPasswordState extends State { @override Widget build(BuildContext context) { final authState = context.watch(); + final bottomInset = MediaQuery.of(context).viewInsets.bottom; + return Form( key: _formKey, child: AuthenticationLayout( @@ -50,7 +52,7 @@ class _ResetPasswordState extends State { obsecureText: true, prefixSvgPath: 'lib/assets/icons/key.svg', ), - const Spacer(), + const SizedBox(height: 32), DidvanButton( onPressed: () async { if (!_formKey.currentState!.validate()) { @@ -79,8 +81,8 @@ class _ResetPasswordState extends State { }, title: authState.hasPassword ? 'تغییر رمز عبور' : 'تایید رمز عبور', ), - const SizedBox( - height: 48, + SizedBox( + height: 48 + bottomInset, ), ], ), diff --git a/lib/views/comments/comments.dart b/lib/views/comments/comments.dart index a32d9ee..6b864ed 100644 --- a/lib/views/comments/comments.dart +++ b/lib/views/comments/comments.dart @@ -2,7 +2,6 @@ import 'package:didvan/config/design_config.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/views/comments/comments_state.dart'; import 'package:didvan/views/comments/widgets/comment.dart'; @@ -75,17 +74,22 @@ class _CommentsState extends State { builder: (context, state, child) => SliverStateHandler( onRetry: state.getComments, 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, placeholder: const _CommentPlaceholder(), centerEmptyState: false, enableEmptyState: state.comments.isEmpty, paddingEmptyState: 0, - emptyState: EmptyState( - asset: Assets.emptyChat, + emptyState: const EmptyState( + asset: 'lib/assets/images/empty_states/Empty_List.png', title: 'لیست خالی است', - titleColor: const Color.fromARGB(255, 0, 126, 167), - subtitle: 'در حال حاضر آیتمی در این بخش ثبت نشده است. هر زمان مورد جدیدی اضافه شود، در اینجا نمایش داده می‌شود.', + height: 500, + titleColor: Color.fromARGB(255, 0, 126, 167), + subtitle: + 'در حال حاضر آیتمی در این بخش ثبت نشده است. هر زمان مورد جدیدی اضافه شود، در اینجا نمایش داده می‌شود.', ), builder: (context, state, index) => Comment( key: ValueKey( @@ -337,4 +341,4 @@ class _CommentPlaceholder extends StatelessWidget { ], ); } -} +} \ No newline at end of file diff --git a/lib/views/direct/widgets/message.dart b/lib/views/direct/widgets/message.dart index 9fe8a47..6913e75 100644 --- a/lib/views/direct/widgets/message.dart +++ b/lib/views/direct/widgets/message.dart @@ -262,8 +262,8 @@ class _MessageState extends State with SingleTickerProviderStateMixin { ), ), DidvanText( - DateTimeUtils.timeWithAmPm( - widget.message.createdAt).toPersianDigit(), + DateTimeUtils.timeWithAmPm(widget.message.createdAt) + .toPersianDigit(), style: Theme.of(context).textTheme.labelSmall, color: Theme.of(context).colorScheme.caption, ), diff --git a/lib/views/home/bookmarks/bookmarks.dart b/lib/views/home/bookmarks/bookmarks.dart index c287cd0..9209ae4 100644 --- a/lib/views/home/bookmarks/bookmarks.dart +++ b/lib/views/home/bookmarks/bookmarks.dart @@ -641,7 +641,6 @@ class _FadeInSlide extends StatefulWidget { Key? key, required this.child, this.delay = Duration.zero, - // ignore: unused_element_parameter this.duration = const Duration(milliseconds: 400), this.slideOffset = 50.0, }) : super(key: key); diff --git a/lib/views/home/bookmarks/filtered_bookmark/filtered_bookmark.dart b/lib/views/home/bookmarks/filtered_bookmark/filtered_bookmark.dart index 9d03bff..8e3cab5 100644 --- a/lib/views/home/bookmarks/filtered_bookmark/filtered_bookmark.dart +++ b/lib/views/home/bookmarks/filtered_bookmark/filtered_bookmark.dart @@ -161,7 +161,7 @@ class _FilteredBookmarksState extends State { onMarkChanged: (id, value) => _onBookmarkChanged(id, value, true, item.type), enableBookmark: true, - useCardStyle: true, // **اعمال استایل کارت در لیست بوکمارک** + useCardStyle: true, ); }, childCount: state.bookmarks.length + @@ -174,4 +174,4 @@ class _FilteredBookmarksState extends State { ), ); } -} \ No newline at end of file +} diff --git a/lib/views/home/explore/explore.dart b/lib/views/home/explore/explore.dart index 85b4351..e2ec075 100644 --- a/lib/views/home/explore/explore.dart +++ b/lib/views/home/explore/explore.dart @@ -1,3 +1,5 @@ +// ignore_for_file: deprecated_member_use + import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/main.dart'; @@ -258,7 +260,8 @@ class SwotSection extends StatelessWidget { final double? headerSize; 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 Widget build(BuildContext context) { @@ -314,7 +317,9 @@ class SwotSection extends StatelessWidget { ? Theme.of(context).textTheme.titleSmall : TextStyle(fontSize: moreSize), 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: const EdgeInsets.all(9.5), child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ @@ -1093,15 +1100,42 @@ class MainPageSection extends StatelessWidget { ], ), const SizedBox( - height: 13, + height: 9, ), - const Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - SizedBox( - height: 19, - ) - ], + Builder( + builder: (context) { + final riskValue = double.tryParse(item.riskScore ?? '0') ?? 0; + final isNegative = riskValue < 0; + 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, + ), + ) + ], + ), + ); + }, ), ], ), diff --git a/lib/views/home/home.dart b/lib/views/home/home.dart index d968502..be591ec 100644 --- a/lib/views/home/home.dart +++ b/lib/views/home/home.dart @@ -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/models/notification_message.dart'; import 'package:didvan/models/view/action_sheet_data.dart'; import 'package:didvan/providers/theme.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/views/home/explore/explore.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/widgets/didvan/text.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 homeScaffKey = GlobalKey(); @@ -36,74 +35,71 @@ class _HomeState extends State @override void initState() { + super.initState(); final state = context.read(); DesignConfig.updateSystemUiOverlayStyle(); + _tabController = TabController(length: 5, vsync: this, initialIndex: 0); state.tabController = _tabController; _tabController.addListener(() { - if (_tabController.indexIsChanging) { - state.currentPageIndex = _tabController.index; - } else { - state.currentPageIndex = _tabController.index; - } + state.currentPageIndex = _tabController.index; }); + if (!kIsWeb) { - Future.delayed(Duration.zero, () { - HomeWidgetRepository.fetchWidget(); - HomeWidgetRepository.decideWhereToGo(); - NotificationMessage? data = HomeWidgetRepository.data; - if (data != null) { - HomeWidgetRepository.decideWhereToGoNotif(); + Future.microtask(() async { + await HomeWidgetRepository.fetchWidget(); + await HomeWidgetRepository.decideWhereToGo(); + if (HomeWidgetRepository.data != null) { + await HomeWidgetRepository.decideWhereToGoNotif(); + } + if (mounted) { + AppInitializer.handleCLick(state, _tabController); } - AppInitializer.handleCLick(state, _tabController); }); } + state.refresh(); context.read().addListener(() { - state.refresh(); + if (mounted) state.refresh(); }); - - super.initState(); } - PreferredSizeWidget? getAppBar() { - return null; + Future _handleBackPress() async { + if (context.read().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 Widget build(BuildContext context) { - return Scaffold( - key: homeScaffKey, + return PopScope( + canPop: false, // ignore: deprecated_member_use - backgroundColor: Theme.of(context).colorScheme.background, - resizeToAvoidBottomInset: false, - drawer: null, - // ignore: deprecated_member_use - body: WillPopScope( - onWillPop: () async { - if (context.read().tabController.index == 0) { - if (kIsWeb) { - return true; - } - 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( + onPopInvoked: (didPop) async { + if (didPop) return; + await _handleBackPress(); + }, + child: Scaffold( + key: homeScaffKey, + backgroundColor: Theme.of(context).colorScheme.surface, + resizeToAvoidBottomInset: false, + body: Consumer( builder: (context, state, child) => AnimatedCrossFade( duration: DesignConfig.lowAnimationDuration, crossFadeState: state.filtering @@ -127,26 +123,24 @@ class _HomeState extends State secondChild: const SearchPage(), ), ), - ), - bottomNavigationBar: Consumer( - builder: (context, state, child) => DidvanBNB( - currentTabIndex: state.currentPageIndex, - onTabChanged: (index) { - if (index < _tabController.length) { - state.currentPageIndex = index; - FocusScope.of(context).unfocus(); - state.resetFilters(false); - _tabController.animateTo(index); - } - }, - ), - ) - .animate() - .slideY( - duration: 500.ms, - begin: 1, - curve: Curves.easeOut, + bottomNavigationBar: Consumer( + builder: (context, state, child) => DidvanBNB( + currentTabIndex: state.currentPageIndex, + onTabChanged: (index) { + if (index < _tabController.length) { + state.currentPageIndex = index; + FocusScope.of(context).unfocus(); + state.resetFilters(false); + _tabController.animateTo(index); + } + }, ), + ).animate().slideY( + duration: 500.ms, + begin: 1, + curve: Curves.easeOut, + ), + ), ); } -} \ No newline at end of file +} diff --git a/lib/views/home/main/main_page.dart b/lib/views/home/main/main_page.dart index 7551859..7a24067 100644 --- a/lib/views/home/main/main_page.dart +++ b/lib/views/home/main/main_page.dart @@ -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/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/home_page_list.dart'; import 'package:didvan/models/home_page_content/swot.dart'; -import 'package:didvan/services/app_initalizer.dart'; -import 'package:didvan/services/network/request.dart'; -import 'package:didvan/views/home/explore/explore.dart'; +import 'package:didvan/views/home/home_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/story_section.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_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/state_handlers/state_handler.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/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() { if (kIsWeb) { @@ -45,9 +43,7 @@ bool isAnyMobile() { } class MainPage extends StatefulWidget { - const MainPage({ - super.key, - }); + const MainPage({super.key}); @override State createState() => _MainPageState(); @@ -73,6 +69,7 @@ class _MainPageState extends State { '🏠 MainPage build - didvanPlus: ${state.didvanPlus != null}'); debugPrint( '🏠 MainPage build - didvanVoice: ${state.didvanVoice != null}'); + return Column( children: [ const HomeAppBar( @@ -83,6 +80,7 @@ class _MainPageState extends State { child: ListView( padding: const EdgeInsets.only(top: 0, bottom: 16), children: [ + // --- Stories Section --- if (state.stories.isNotEmpty) ...[ const TextDivider(text: 'دیده‌بان') .animate() @@ -95,12 +93,16 @@ class _MainPageState extends State { child: StorySection(stories: state.stories), ).animate().fadeIn(delay: 600.ms, duration: 500.ms), ], + + // --- Didvan Plus --- if (state.didvanPlus != null) ...[ const SizedBox(height: 16), DidvanPlusSection(didvanPlus: state.didvanPlus!) .animate() .fadeIn(delay: 650.ms, duration: 500.ms), ], + + // --- Strategic Dashboard --- const SizedBox(height: 12), const TextDivider(text: 'پیشخوان استراتژیک') .animate() @@ -109,6 +111,8 @@ class _MainPageState extends State { padding: EdgeInsets.symmetric(horizontal: 16), child: MainPageMainContent(), ).animate().fadeIn(delay: 800.ms, duration: 500.ms), + + // --- Explore Latest --- if (state.content != null && state.content!.lists.isNotEmpty) ...[ const _ExploreLatestTitle() @@ -119,10 +123,16 @@ class _MainPageState extends State { swotItems: state.swotItems, ).animate().fadeIn(delay: 1000.ms, duration: 500.ms), ], + + // --- SWOT Items --- if (state.swotItems.isNotEmpty) - SwotSection(swotItems: state.swotItems,headerSize: 13,moreSize: 12,) - .animate() - .fadeIn(delay: 1100.ms, duration: 500.ms), + SwotSection( + swotItems: state.swotItems, + headerSize: 13, + moreSize: 12, + ).animate().fadeIn(delay: 1100.ms, duration: 500.ms), + + // --- Didvan Voice --- if (state.didvanVoice != null) ...[ const SizedBox(height: 16), const _DidvanVoiceTitle() @@ -133,6 +143,8 @@ class _MainPageState extends State { .animate() .fadeIn(delay: 1200.ms, duration: 500.ms), ], + + // --- Commented Out Section (Industry Pulse) --- // const _IndustryPulseTitle() // .animate() // .fadeIn(delay: 1100.ms, duration: 500.ms), @@ -149,18 +161,15 @@ class _MainPageState extends State { } } +// --- Sub Widgets --- + class _DidvanSignalsTitle extends StatelessWidget { const _DidvanSignalsTitle(); @override Widget build(BuildContext context) { return Padding( - padding: const EdgeInsets.only( - left: 16, - right: 16, - bottom: 16, - top: 0, - ), + padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), child: Row( children: [ SvgPicture.asset( @@ -221,12 +230,7 @@ class _ExploreLatestTitle extends StatelessWidget { @override Widget build(BuildContext context) { return Padding( - padding: const EdgeInsets.only( - left: 16, - right: 16, - bottom: 16, - top: 0, - ), + padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -284,10 +288,7 @@ class _ExploreLatestSlider extends StatelessWidget { itemsData = []; for (var list in lists) { - if (list.type == 'video' || - list.type == 'podcast' || - list.type == 'news' || - list.type == 'radar') { + if (['video', 'podcast', 'news', 'radar'].contains(list.type)) { continue; } if (list.contents.isNotEmpty) { @@ -318,18 +319,14 @@ class _ExploreLatestSlider extends StatelessWidget { itemsData.add((type: 'swot', content: null, swotItem: swotItems.first)); } - if (items.isEmpty) { - return const SizedBox.shrink(); - } + if (items.isEmpty) return const SizedBox.shrink(); return Carousel3D( items: items, height: 220, autoPlayDuration: const Duration(seconds: 5), showControls: true, - onItemChanged: (index) { - // Optional: Handle item change if needed - }, + onItemChanged: (index) {}, onItemTap: (index) { final data = itemsData[index]; if (data.swotItem != null) { @@ -351,6 +348,10 @@ class _ExploreLatestSlider extends StatelessWidget { } } +// --------------------------------------------------------------------------- +// ------------------------- COMMENTED OUT SECTIONS -------------------------- +// --------------------------------------------------------------------------- + // class _IndustryPulseTitle extends StatelessWidget { // const _IndustryPulseTitle(); diff --git a/lib/views/home/main/widgets/didvan_plus_section.dart b/lib/views/home/main/widgets/didvan_plus_section.dart index 768c7eb..f4fb3e2 100644 --- a/lib/views/home/main/widgets/didvan_plus_section.dart +++ b/lib/views/home/main/widgets/didvan_plus_section.dart @@ -1,5 +1,4 @@ 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/services/network/request_helper.dart'; import 'package:didvan/services/network/request.dart'; diff --git a/lib/views/home/main/widgets/infography_item.dart b/lib/views/home/main/widgets/infography_item.dart index f5990aa..737546b 100644 --- a/lib/views/home/main/widgets/infography_item.dart +++ b/lib/views/home/main/widgets/infography_item.dart @@ -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/infography_tag.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:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; diff --git a/lib/views/home/main/widgets/simple_explore_card.dart b/lib/views/home/main/widgets/simple_explore_card.dart index f098d06..3a0d608 100644 --- a/lib/views/home/main/widgets/simple_explore_card.dart +++ b/lib/views/home/main/widgets/simple_explore_card.dart @@ -39,13 +39,13 @@ class SimpleExploreCard extends StatelessWidget { return 'lib/assets/icons/Startup.svg'; case 'radar': return 'lib/assets/icons/Pouyesh_Ofogh_New.svg'; - case 'trend': // رادار روند + case 'trend': case 'trends': return 'lib/assets/icons/Ravand.svg'; - case 'technology': // رادار تکنولوزی/فناوری + case 'technology': case 'tech': return 'lib/assets/icons/Technology.svg'; - case 'risk': // رادار ریسک + case 'risk': return 'lib/assets/icons/RiskRadar.svg'; case 'survey': case 'delphi': diff --git a/lib/views/home/media/podcast_tab_page.dart b/lib/views/home/media/podcast_tab_page.dart index e45f0ee..5b2ab31 100644 --- a/lib/views/home/media/podcast_tab_page.dart +++ b/lib/views/home/media/podcast_tab_page.dart @@ -292,14 +292,13 @@ class _PodcastTabPageState extends State { physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) { final podcast = state.studios[index]; - - // NEW: بررسی اینکه آیا این آیتم، آخرین آیتم در لیست است + final bool isLast = index == state.studios.length - 1; return PodcastListCard( podcast: podcast, onTap: () => _navigateToDetails(index), - isLastItem: isLast, // NEW: ارسال پارامتر به ویجت کارت + isLastItem: isLast, ); }, ), @@ -310,4 +309,4 @@ class _PodcastTabPageState extends State { onRetry: () => context.read().getStudios(page: 1), ); } -} \ No newline at end of file +} diff --git a/lib/views/home/media/video_details_page.dart b/lib/views/home/media/video_details_page.dart index 11a32c4..d436654 100644 --- a/lib/views/home/media/video_details_page.dart +++ b/lib/views/home/media/video_details_page.dart @@ -3,7 +3,6 @@ import 'package:chewie/chewie.dart'; import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; -import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/constants/assets.dart'; import 'package:didvan/models/enums.dart'; import 'package:didvan/models/studio_details_data.dart'; diff --git a/lib/views/home/media/widgets/podcast_list_card.dart b/lib/views/home/media/widgets/podcast_list_card.dart index 6038e69..2b3ece3 100644 --- a/lib/views/home/media/widgets/podcast_list_card.dart +++ b/lib/views/home/media/widgets/podcast_list_card.dart @@ -12,13 +12,13 @@ import 'package:persian_number_utility/persian_number_utility.dart'; class PodcastListCard extends StatelessWidget { final OverviewData podcast; final VoidCallback onTap; - final bool isLastItem; // NEW: پارامتر جدید برای تشخیص آیتم آخر + final bool isLastItem; const PodcastListCard({ super.key, required this.podcast, required this.onTap, - this.isLastItem = false, // NEW: مقداردهی اولیه پارامتر + this.isLastItem = false, }); String _formatDuration(int? duration) { @@ -143,7 +143,6 @@ class PodcastListCard extends StatelessWidget { ), ), ), - // NEW: چک می‌کنیم که آیا آیتم آخر است یا نه if (!isLastItem) Divider( color: Colors.grey[300], @@ -155,4 +154,4 @@ class PodcastListCard extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/lib/views/home/new_statistic/new_statistic.dart b/lib/views/home/new_statistic/new_statistic.dart index efd69d6..c209f4c 100644 --- a/lib/views/home/new_statistic/new_statistic.dart +++ b/lib/views/home/new_statistic/new_statistic.dart @@ -46,13 +46,13 @@ class _NewStatisticState extends State { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ + // ignore: prefer_const_constructors HomeAppBar( showSearchField: true, ), Center( child: Container( - padding: - const EdgeInsets.symmetric(horizontal: 12, vertical: 16), + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16), color: Theme.of(context).colorScheme.background, child: DidvanText( 'دسته‌بندی‌های کلان', @@ -102,8 +102,7 @@ class _NewStatisticState extends State { itemsInStatics(context, state, 1, DidvanIcons.currency_solid), itemsInStatics(context, state, 2, DidvanIcons.bitcoin_solid), itemsInStatics(context, state, 3, DidvanIcons.metal_solid), - itemsInStatics( - context, state, 4, DidvanIcons.commodity_solid), + itemsInStatics(context, state, 4, DidvanIcons.commodity_solid), itemsInStatics(context, state, 5, DidvanIcons.industry_solid, hasDivider: false), const SizedBox( @@ -369,4 +368,4 @@ class StatHeader extends StatelessWidget { ) ]); } -} \ No newline at end of file +} diff --git a/lib/views/mentions/mentions.dart b/lib/views/mentions/mentions.dart index edb034b..e67e988 100644 --- a/lib/views/mentions/mentions.dart +++ b/lib/views/mentions/mentions.dart @@ -112,11 +112,11 @@ class _MentionsState extends State { enableEmptyState: state.comments.isEmpty, paddingEmptyState: 0, emptyState: const EmptyState( - asset: 'lib/assets/images/empty_states/Empty_List.png', - title: 'دوستان خود را فراخوانی کنید', - titleColor: Color.fromARGB(255, 0, 126, 167), - height: 550, - ), + asset: 'lib/assets/images/empty_states/Empty_List.png', + title: 'دوستان خود را فراخوانی کنید', + titleColor: Color.fromARGB(255, 0, 126, 167), + height: 550, + ), builder: (context, state, index) => Mention( key: ValueKey( state.comments[index].id.toString() + diff --git a/lib/views/news/news.dart b/lib/views/news/news.dart index f2ba1df..2577760 100644 --- a/lib/views/news/news.dart +++ b/lib/views/news/news.dart @@ -71,7 +71,9 @@ class _NewsState extends State { title: Text( 'دنیای فولاد', 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, fontSize: 19), ), diff --git a/lib/views/notification_settings/notification_settings.dart b/lib/views/notification_settings/notification_settings.dart index bcb1322..fcea1f3 100644 --- a/lib/views/notification_settings/notification_settings.dart +++ b/lib/views/notification_settings/notification_settings.dart @@ -1,3 +1,5 @@ +// ignore_for_file: deprecated_member_use + import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/assets.dart'; @@ -438,7 +440,6 @@ class _CategoryExpansionGroupState extends State<_CategoryExpansionGroup> { textDirection: TextDirection.ltr, child: CupertinoSwitch( value: item.selected ?? false, - // ignore: deprecated_member_use activeColor: Theme.of(context).colorScheme.primary, onChanged: (value) { diff --git a/lib/views/onboarding/onboarding_page.dart b/lib/views/onboarding/onboarding_page.dart index 291ecd5..3f657a0 100644 --- a/lib/views/onboarding/onboarding_page.dart +++ b/lib/views/onboarding/onboarding_page.dart @@ -27,7 +27,8 @@ final List onboardingPages = [ OnboardingEntity( imagePath: 'lib/assets/images/onboarding/1.png', title: 'هوشان', - description: 'ارائه ابزارهای هوش مصنوعی مورد نیاز مدیران اعم از خلاصه‌ساز، ساخت عکس، تحلیل و ترسیم نمودار، تبدیل متن به صوت، ساخت ویدیو و ترجمه، امکان پرسش و پاسخ از مدل‌های زبانی مختلف و یا جستجوی هوشمند در محتوای داخلی از طریق دستیار اختصاصی هوش مصنوعی دیدوان.', + description: + 'ارائه ابزارهای هوش مصنوعی مورد نیاز مدیران اعم از خلاصه‌ساز، ساخت عکس، تحلیل و ترسیم نمودار، تبدیل متن به صوت، ساخت ویدیو و ترجمه، امکان پرسش و پاسخ از مدل‌های زبانی مختلف و یا جستجوی هوشمند در محتوای داخلی از طریق دستیار اختصاصی هوش مصنوعی دیدوان.', ), ]; @@ -168,7 +169,9 @@ class _OnboardingPageState extends State { ], ), ), - const SizedBox(height: 10,), + const SizedBox( + height: 10, + ), Expanded( flex: 2, child: Padding( @@ -184,7 +187,8 @@ class _OnboardingPageState extends State { theme.textTheme.headlineMedium?.copyWith( fontWeight: FontWeight.bold, fontSize: 22, - color: const Color.fromARGB(255, 0, 126, 167), + color: + const Color.fromARGB(255, 0, 126, 167), ), ), const SizedBox(height: 13), @@ -193,10 +197,10 @@ class _OnboardingPageState extends State { textDirection: TextDirection.rtl, textAlign: TextAlign.start, style: theme.textTheme.bodyMedium?.copyWith( - color: const Color.fromARGB(255, 41, 41, 41), - height: 1.6, - fontSize: 14 - ), + color: + const Color.fromARGB(255, 41, 41, 41), + height: 1.6, + fontSize: 14), ), ], ), @@ -208,7 +212,6 @@ class _OnboardingPageState extends State { }, ), ), - if (_currentPage > 0) Positioned( top: 16, @@ -234,7 +237,6 @@ class _OnboardingPageState extends State { ), ), ), - Positioned( bottom: 30, left: 24, diff --git a/lib/views/podcasts/studio_details/studio_details.mobile.dart b/lib/views/podcasts/studio_details/studio_details.mobile.dart index 6dad1c3..ca4fce9 100644 --- a/lib/views/podcasts/studio_details/studio_details.mobile.dart +++ b/lib/views/podcasts/studio_details/studio_details.mobile.dart @@ -4,7 +4,6 @@ import 'package:chewie/chewie.dart'; import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; -import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/constants/assets.dart'; import 'package:didvan/models/enums.dart'; import 'package:didvan/models/studio_details_data.dart'; diff --git a/lib/views/profile/direct_list/widgets/direct_item.dart b/lib/views/profile/direct_list/widgets/direct_item.dart index 9a7beb7..b08d5d1 100644 --- a/lib/views/profile/direct_list/widgets/direct_item.dart +++ b/lib/views/profile/direct_list/widgets/direct_item.dart @@ -1,3 +1,5 @@ +// ignore_for_file: deprecated_member_use + import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; @@ -44,7 +46,10 @@ class ChatRoomItem extends StatelessWidget { Row( children: [ 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', height: 24, width: 24, @@ -87,8 +92,12 @@ class ChatRoomItem extends StatelessWidget { child: Padding( padding: const EdgeInsets.fromLTRB(10, 8, 10, 8), child: Text( - chatRoom.lastMessage.writedByAdmin? chatRoom.lastMessage.readed - ? 'پاسخ داده شده' : 'خوانده نشده' : 'در انتظار پاسخ',), + chatRoom.lastMessage.writedByAdmin + ? chatRoom.lastMessage.readed + ? 'پاسخ داده شده' + : 'خوانده نشده' + : 'در انتظار پاسخ', + ), ), ), // Icon( diff --git a/lib/views/profile/profile.dart b/lib/views/profile/profile.dart index e1cc586..9a79c2f 100644 --- a/lib/views/profile/profile.dart +++ b/lib/views/profile/profile.dart @@ -1,3 +1,5 @@ +// ignore_for_file: avoid_print, deprecated_member_use + import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; @@ -159,11 +161,12 @@ class _ProfilePageState extends State image: (user.photo != null && user.photo!.isNotEmpty) ? DecorationImage( - image: NetworkImage('https://api.didvan.app${user.photo!}', - headers: { - 'Authorization': - 'Bearer ${RequestService.token}', - }), + image: NetworkImage( + 'https://api.didvan.app${user.photo!}', + headers: { + 'Authorization': + 'Bearer ${RequestService.token}', + }), fit: BoxFit.cover, ) : null, diff --git a/lib/views/radar/radar.dart b/lib/views/radar/radar.dart index f4fb2b7..4d71061 100644 --- a/lib/views/radar/radar.dart +++ b/lib/views/radar/radar.dart @@ -1,3 +1,5 @@ +// ignore_for_file: deprecated_member_use + import 'dart:async'; import 'dart:math'; diff --git a/lib/views/splash/splash.dart b/lib/views/splash/splash.dart index 6adeeff..473b76f 100644 --- a/lib/views/splash/splash.dart +++ b/lib/views/splash/splash.dart @@ -1,7 +1,6 @@ // ignore_for_file: deprecated_member_use, avoid_print import 'package:didvan/config/design_config.dart'; -import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/assets.dart'; import 'package:didvan/main.dart'; import 'package:didvan/providers/media.dart'; @@ -40,7 +39,7 @@ class _SplashState extends State with TickerProviderStateMixin { _pulseController = AnimationController( duration: const Duration(milliseconds: 1500), - vsync: this, + vsync: this, ); _pulseAnimation = Tween( @@ -195,18 +194,6 @@ class _SplashState extends State with TickerProviderStateMixin { ], 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 with TickerProviderStateMixin { await mediaProvider.getDownloadsList(); } + // اگر اینجا خطای سرور رخ دهد (به خاطر تغییر در user.dart)، کد به بلوک catch می‌رود final result = await userProvider.getUserInfo(); if (!result) { + // این بخش فقط زمانی اجرا می‌شود که سرور پاسخ ۴۰۱ (نامعتبر بودن توکن) داده باشد print("no results were returned for user info"); try { StorageService.delete(key: 'token'); } catch (e) { print("error in case of no user info result: $e"); - // catch } navigatorKey.currentState!.pushNamedAndRemoveUntil( @@ -281,7 +269,6 @@ class _SplashState extends State with TickerProviderStateMixin { if (destinationRoute == Routes.home) { print("destination route was home and init uri is $initialURI"); - // (routeArguments as Map)['deepLinkUri'] = initialURI; initialURI = null; } @@ -296,6 +283,7 @@ class _SplashState extends State with TickerProviderStateMixin { arguments: routeArguments, ); } catch (e) { + // اگر خطای سرور رخ دهد، اینجا گرفته می‌شود و دکمه تلاش مجدد نشان داده می‌شود setState(() { _errorOccured = true; }); diff --git a/lib/views/widgets/ai_chat_dialog.dart b/lib/views/widgets/ai_chat_dialog.dart index 7ea7131..f5d2ac3 100644 --- a/lib/views/widgets/ai_chat_dialog.dart +++ b/lib/views/widgets/ai_chat_dialog.dart @@ -1,20 +1,20 @@ // ignore_for_file: deprecated_member_use import 'package:didvan/services/ai_rag_service.dart'; -import 'package:didvan/services/ai_voice_service.dart'; import 'package:didvan/services/network/request.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/ai_voice_chat_dialog.dart'; import 'package:didvan/providers/user.dart'; +import 'package:didvan/views/widgets/glass/liquid_glass_container.dart'; +import 'package:didvan/views/widgets/glass/glass_logic.dart'; + import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:provider/provider.dart'; import 'package:record/record.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:just_audio/just_audio.dart'; import 'dart:ui'; -import 'dart:io'; class AiChatDialog extends StatefulWidget { const AiChatDialog({super.key}); @@ -32,18 +32,34 @@ class _AiChatDialogState extends State bool _isRecording = false; late AnimationController _animationController; late AnimationController _pulseController; + late AnimationController _shimmerController; final AudioRecorder _audioRecorder = AudioRecorder(); final AudioPlayer _audioPlayer = AudioPlayer(); String? _recordingPath; String? _currentPlayingUrl; bool _isPlaying = false; + final GlassShader _headerGlassShader = GlassShader(); + final GlassShader _inputGlassShader = GlassShader(); + final GlassShader _loadingGlassShader = GlassShader(); + + final GlobalKey _backgroundKey = GlobalKey(); + @override void initState() { super.initState(); + + Future.wait([ + _headerGlassShader.initialize(), + _inputGlassShader.initialize(), + _loadingGlassShader.initialize(), + ] as Iterable).then((_) { + if (mounted) setState(() {}); + }); + _animationController = AnimationController( vsync: this, - duration: const Duration(milliseconds: 600), + duration: const Duration(milliseconds: 1000), ); _audioPlayer.playerStateStream.listen((state) { @@ -59,12 +75,17 @@ class _AiChatDialogState extends State }); _pulseController = AnimationController( vsync: this, - duration: const Duration(milliseconds: 1500), + duration: const Duration(milliseconds: 3000), )..repeat(reverse: true); + _shimmerController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 4000), + )..repeat(); + _animationController.forward(); - Future.delayed(const Duration(milliseconds: 500), () { + Future.delayed(const Duration(milliseconds: 600), () { if (mounted) { setState(() { _messages.add(ChatMessage( @@ -85,6 +106,7 @@ class _AiChatDialogState extends State _scrollController.dispose(); _animationController.dispose(); _pulseController.dispose(); + _shimmerController.dispose(); _audioRecorder.dispose(); _audioPlayer.dispose(); super.dispose(); @@ -95,8 +117,8 @@ class _AiChatDialogState extends State if (_scrollController.hasClients) { _scrollController.animateTo( _scrollController.position.maxScrollExtent, - duration: const Duration(milliseconds: 300), - curve: Curves.easeOut, + duration: const Duration(milliseconds: 600), + curve: Curves.easeOutCubic, ); } }); @@ -112,37 +134,17 @@ class _AiChatDialogState extends State } else { if (_currentPlayingUrl != audioUrl) { final token = RequestService.token; - debugPrint('🎵 Audio URL: $audioUrl'); - debugPrint('🔑 Token exists: ${token != null && token.isNotEmpty}'); - AudioSource? audioSource; - try { - if (token != null && token.isNotEmpty) { - audioSource = AudioSource.uri( - Uri.parse(audioUrl), - headers: { - 'Authorization': 'Bearer $token', - }, - ); - debugPrint('✅ Trying with token (no Bearer prefix)'); - } else { - audioSource = AudioSource.uri( - Uri.parse(audioUrl), - headers: { - 'Authorization': 'Bearer $token', - }, - ); - debugPrint('✅ Trying without Authorization header'); - } - + audioSource = AudioSource.uri( + Uri.parse(audioUrl), + headers: {'Authorization': 'Bearer $token'}, + ); await _audioPlayer.setAudioSource(audioSource); setState(() { _currentPlayingUrl = audioUrl; }); } catch (headerError) { - debugPrint( - '⚠️ Error with headers, trying simple URL: $headerError'); await _audioPlayer.setUrl(audioUrl); setState(() { _currentPlayingUrl = audioUrl; @@ -196,280 +198,436 @@ class _AiChatDialogState extends State _scrollToBottom(); } - Future _startRecording() async { - try { - if (await _audioRecorder.hasPermission()) { - setState(() { - _isRecording = true; - }); - - final directory = await getTemporaryDirectory(); - final timestamp = DateTime.now().millisecondsSinceEpoch; - _recordingPath = '${directory.path}/voice_$timestamp.m4a'; - - await _audioRecorder.start( - const RecordConfig( - encoder: AudioEncoder.aacLc, - ), - path: _recordingPath!, - ); - - await Future.delayed(const Duration(milliseconds: 200)); - } - } catch (e) { - setState(() { - _isRecording = false; - }); - debugPrint('Error starting recording: $e'); - } - } - - Future _stopRecording() async { - try { - final path = await _audioRecorder.stop(); - - setState(() { - _isRecording = false; - }); - - if (path != null) { - setState(() { - _messages.add(ChatMessage( - text: '🎤 پیام صوتی', - isUser: true, - timestamp: DateTime.now(), - )); - _isLoading = true; - }); - - _scrollToBottom(); - - final response = await AiVoiceService.uploadVoice(path); - - if (response.isSuccess && response.text.isNotEmpty) { - final ragResponse = await AiRagService.sendMessage(response.text); - - setState(() { - _messages.add(ChatMessage( - text: ragResponse.output, - isUser: false, - timestamp: DateTime.now(), - sources: ragResponse.sources, - audioUrl: ragResponse.audioUrl, - )); - _isLoading = false; - }); - } else { - setState(() { - _messages.add(ChatMessage( - text: 'خطا در پردازش پیام صوتی', - isUser: false, - timestamp: DateTime.now(), - )); - _isLoading = false; - }); - } - - _scrollToBottom(); - - try { - await File(path).delete(); - } catch (e) { - debugPrint('Error deleting temp file: $e'); - } - } - } catch (e) { - setState(() { - _isRecording = false; - _isLoading = false; - }); - debugPrint('Error stopping recording: $e'); - } - } - @override Widget build(BuildContext context) { return Dialog( backgroundColor: Colors.transparent, - insetPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), - child: ScaleTransition( - scale: CurvedAnimation( - parent: _animationController, - curve: Curves.elasticOut, - ), - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), - child: Container( - constraints: const BoxConstraints(maxWidth: 500, maxHeight: 600), - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - Colors.white.withOpacity(0.95), - const Color(0xFFF8F9FF).withOpacity(0.95), - ], - ), - borderRadius: BorderRadius.circular(24), - border: Border.all( - color: Colors.white.withOpacity(0.6), - width: 1.5, - ), - boxShadow: [ - BoxShadow( - color: const Color(0xFF0066AA).withOpacity(0.15), - blurRadius: 30, - spreadRadius: 0, - offset: const Offset(0, 15), + insetPadding: EdgeInsets.zero, + child: Stack( + alignment: Alignment.center, + children: [ + Positioned.fill( + child: GestureDetector( + onTap: () => Navigator.pop(context), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), + child: Container( + color: Colors.black.withOpacity(0.01), ), - BoxShadow( - color: Colors.black.withOpacity(0.08), - blurRadius: 15, - spreadRadius: -3, - offset: const Offset(0, 8), - ), - ], - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(24), - child: Column( - children: [ - _buildHeader(context), - Expanded( - child: _buildMessageList(), - ), - if (_isLoading) _buildLoadingIndicator(), - // if (_isRecording) _buildRecordingIndicator(), - _buildInputField(), - ], ), ), ), - ), + + Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), + child: ScaleTransition( + scale: CurvedAnimation( + parent: _animationController, + curve: Curves.elasticOut, + ), + child: Container( + constraints: const BoxConstraints(maxWidth: 500, maxHeight: 680), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(40), + color: Colors.white.withOpacity(0.05), + boxShadow: [ + BoxShadow( + color: const Color(0xFF0066AA).withOpacity(0.15), + blurRadius: 40, + spreadRadius: 0, + offset: const Offset(0, 20), + ), + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 20, + offset: const Offset(0, 10), + ), + ], + ), + padding: const EdgeInsets.all(1.5), + child: ClipRRect( + borderRadius: BorderRadius.circular(39), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), + child: Stack( + children: [ + RepaintBoundary( + key: _backgroundKey, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(39), + gradient: const LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Color.fromARGB(55, 255, 255, 255), + Color.fromARGB(50, 255, 255, 255), + ], + ), + ), + child: Stack( + children: [ + Positioned.fill( + child: AnimatedBuilder( + animation: _shimmerController, + builder: (context, child) { + return Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment( + -1.5 + (_shimmerController.value * 2.5), + -0.5), + end: Alignment( + 0.5 + (_shimmerController.value * 2.5), + 1.5), + colors: [ + Colors.transparent, + Colors.white.withOpacity(0.1), + Colors.transparent, + ], + stops: const [0.0, 0.5, 1.0], + ), + ), + ); + }, + ), + ), + + Positioned.fill( + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + _buildMessageList(), + ], + ), + ), + ], + ), + ), + ), + + Positioned( + top: 0, + left: 0, + right: 0, + child: LiquidGlassContainer( + backgroundKey: _backgroundKey, + shader: _headerGlassShader, + distortion: 6.0, + blur: 30.0, + dispersion: 10.0, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(39), + topRight: Radius.circular(39), + ), + child: _buildHeaderContent(context), + ), + ), + + Positioned( + bottom: 0, + left: 0, + right: 0, + child: LiquidGlassContainer( + backgroundKey: _backgroundKey, + shader: _inputGlassShader, + distortion: 6.0, + blur: 30.0, + dispersion: 10.0, + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(39), + bottomRight: Radius.circular(39), + ), + child: _buildInputFieldContent(), + ), + ), + ], + ), + ), + ), + ), + ), + ), + ), + ], ), ); } - Widget _buildHeader(BuildContext context) { + + Widget _buildHeaderContent(BuildContext context) { return Container( - padding: const EdgeInsets.fromLTRB(16, 16, 16, 14), + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 18), decoration: BoxDecoration( - gradient: const LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - Color(0xFF0066AA), - Color(0xFF0088DD), - Color(0xFF00AAFF), - ], - ), - boxShadow: [ - BoxShadow( - color: const Color(0xFF0066AA).withOpacity(0.2), - blurRadius: 10, - offset: const Offset(0, 3), + color: const Color.fromARGB(150, 255, 255, 255), + border: Border( + bottom: BorderSide( + color: Colors.white.withOpacity(0.2), + width: 1, ), - ], + ), ), child: Row( children: [ - Stack( - children: [ - AnimatedBuilder( - animation: _pulseController, - builder: (context, child) { - return Container( - width: 44, - height: 44, - decoration: BoxDecoration( - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: Colors.white - .withOpacity(0.25 * _pulseController.value), - blurRadius: 15 + (8 * _pulseController.value), - spreadRadius: 1 + (2 * _pulseController.value), - ), - ], - ), - ); - }, + Container( + height: 48, + width: 48, + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Colors.white.withOpacity(0.9), + const Color(0xFFE0F7FF), + const Color(0xFFF0FDFF), + ], + stops: const [0.0, 0.3, 1.0], ), - Container( - width: 44, - height: 44, - decoration: BoxDecoration( - color: Colors.white, - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.15), - blurRadius: 8, - offset: const Offset(0, 3), - ), - ], + border: Border.all( + color: Colors.white, + width: 1.5, + ), + boxShadow: [ + BoxShadow( + color: const Color(0xFF0066AA).withOpacity(0.2), + blurRadius: 15, + offset: const Offset(0, 8), ), - padding: const EdgeInsets.all(10), - child: SvgPicture.asset( - 'lib/assets/icons/live ai.svg', - colorFilter: const ColorFilter.mode( - Color(0xFF0066AA), - BlendMode.srcIn, + BoxShadow( + color: Colors.white.withOpacity(0.5), + blurRadius: 10, + offset: const Offset(-2, -2), + ), + ], + ), + child: Padding( + padding: const EdgeInsets.all(10), + child: SvgPicture.asset( + 'lib/assets/icons/live ai.svg', + colorFilter: const ColorFilter.mode( + Color(0xFF0066AA), + BlendMode.srcIn, + ), + ), + ), + ), + const SizedBox(width: 14), + const Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + DidvanText( + 'دستیار هوشمند دیدوان', + fontSize: 13, + fontWeight: FontWeight.w800, + color: Color(0xFF1A2B3C), + ), + ], + ), + ), + GestureDetector( + onTap: () => Navigator.pop(context), + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + shape: BoxShape.circle, + border: Border.all( + color: Colors.white.withOpacity(0.4), + ), + ), + child: Icon( + Icons.close_rounded, + color: Colors.grey.shade800, + size: 22, + ), + ), + ), + ], + ), + ); + } + + Widget _buildInputFieldContent() { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: const Color.fromARGB(50, 255, 255, 255), + border: Border( + top: BorderSide( + color: Colors.white.withOpacity(0.3), + width: 1, + ), + ), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + GestureDetector( + onTap: _isLoading || _isRecording ? null : _sendMessage, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + width: 52, + height: 52, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: (_isLoading || _isRecording) + ? [Colors.grey.shade300, Colors.grey.shade400] + : [const Color(0xFF0066AA), const Color(0xFF0099FF)], + ), + shape: BoxShape.circle, + border: Border.all( + color: Colors.white.withOpacity(0.6), + width: 2, + ), + boxShadow: [ + BoxShadow( + color: ((_isLoading || _isRecording) + ? Colors.grey + : const Color(0xFF0066AA)) + .withOpacity(0.3), + blurRadius: 15, + offset: const Offset(0, 6), + ), + ], + ), + child: _isLoading + ? const Padding( + padding: EdgeInsets.all(14), + child: CircularProgressIndicator( + strokeWidth: 2.5, + valueColor: AlwaysStoppedAnimation(Colors.white), + ), + ) + : Padding( + padding: const EdgeInsets.all(13.0), + child: SvgPicture.asset( + 'lib/assets/icons/send.svg', + color: Colors.white, + ), + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: ClipRRect( + borderRadius: BorderRadius.circular(30), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), + child: Container( + constraints: const BoxConstraints(maxHeight: 120), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.75), + borderRadius: BorderRadius.circular(30), + border: Border.all( + color: Colors.white.withOpacity(0.5), + width: 1.5, + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.03), + blurRadius: 10, + offset: const Offset(0, 4), + ), + ], + ), + child: TextField( + controller: _messageController, + maxLines: null, + textInputAction: TextInputAction.send, + onSubmitted: (_) => _sendMessage(), + onChanged: (_) => setState(() {}), + style: const TextStyle( + fontSize: 14, + color: Color(0xFF1A2B3C), + height: 1.5, + ), + decoration: InputDecoration( + hintText: 'چیزی بنویسید...', + hintStyle: TextStyle( + color: Colors.grey.shade700, + fontSize: 11, + fontWeight: FontWeight.w500, + ), + border: InputBorder.none, + contentPadding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 16, + ), + suffixIcon: _messageController.text.isEmpty + ? GestureDetector( + onTap: () { + Navigator.pop(context); + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => + const AiVoiceChatDialog(), + ); + }, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Container( + decoration: BoxDecoration( + gradient: const LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Color(0xFF0066AA), + Color(0xFF00AAFF) + ], + ), + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 6, + offset: const Offset(0, 2), + ), + ], + ), + child: Padding( + padding: const EdgeInsets.all(5.0), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox( + width: 5, + ), + const DidvanText( + 'چت صوتی', + fontSize: 12, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + const SizedBox(width: 6), + SvgPicture.asset( + 'lib/assets/icons/voice-square.svg', + colorFilter: const ColorFilter.mode( + Colors.white, + BlendMode.srcIn, + ), + height: 24, + ), + ], + ), + ), + ), + ), + ) + : null, + suffixIconConstraints: const BoxConstraints( + minWidth: 0, + minHeight: 0, + ), + ), ), ), ), - ], - ), - const SizedBox(width: 12), - const Expanded( - child: DidvanText( - 'دستیار هوشمند دیدوان', - fontSize: 13, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - Container( - margin: const EdgeInsets.only(left: 6), - decoration: const BoxDecoration( - gradient: LinearGradient( - begin: AlignmentGeometry.topLeft, - end: AlignmentGeometry.bottomRight, - colors: [ - Color.fromARGB(255, 1, 35, 72), - Color.fromARGB(255, 27, 60, 89), - Color.fromARGB(255, 25, 93, 128), - Color.fromARGB(255, 0, 126, 167), - ], - ), - shape: BoxShape.circle, - ), - child: GestureDetector( - child: Padding( - padding: const EdgeInsets.all(6.0), - child: SvgPicture.asset('lib/assets/icons/voice-square.svg', - color: Colors.white, height: 35), - ), - onTap: () { - Navigator.pop(context); - showDialog( - context: context, - barrierDismissible: false, - builder: (context) => const AiVoiceChatDialog(), - ); - }, - ), - ), - Container( - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.2), - shape: BoxShape.circle, - ), - child: IconButton( - icon: const Icon(Icons.close_rounded, - color: Colors.white, size: 20), - onPressed: () => Navigator.pop(context), - splashRadius: 20, ), ), ], @@ -478,222 +636,157 @@ class _AiChatDialogState extends State } Widget _buildMessageList() { - return Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Colors.white.withOpacity(0.5), - const Color(0xFFF8F9FF).withOpacity(0.3), - ], - ), - ), - child: ListView.builder( - controller: _scrollController, - padding: const EdgeInsets.fromLTRB(14, 14, 14, 10), - itemCount: _messages.length, - itemBuilder: (context, index) { + return ListView.builder( + controller: _scrollController, + physics: const BouncingScrollPhysics(), + padding: const EdgeInsets.fromLTRB(20, 100, 20, 120), + itemCount: _messages.length + (_isLoading ? 1 : 0), + itemBuilder: (context, index) { + if (index < _messages.length) { final message = _messages[index]; return _buildMessageBubble(message); - }, - ), + } + else { + return Padding( + padding: const EdgeInsets.only(bottom: 18), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + _buildLiquidLoadingIndicator(), + ], + ), + ); + } + }, ); } Widget _buildMessageBubble(ChatMessage message) { return TweenAnimationBuilder( tween: Tween(begin: 0.0, end: 1.0), - duration: const Duration(milliseconds: 400), - curve: Curves.easeOutCubic, + duration: const Duration(milliseconds: 600), + curve: Curves.easeOutBack, builder: (context, value, child) { return Transform.translate( - offset: Offset(0, 20 * (1 - value)), - child: Opacity( - opacity: value, - child: child, + offset: Offset(0, 30 * (1 - value)), + child: Transform.scale( + scale: 0.9 + (0.1 * value), + child: Opacity( + opacity: value, + child: child, + ), ), ); }, child: Padding( - padding: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.only(bottom: 18), child: Row( mainAxisAlignment: message.isUser ? MainAxisAlignment.start : MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.end, children: [ - if (message.isUser) _buildUserAvatar(), - if (message.isUser) const SizedBox(width: 8), + if (message.isUser) ...[ + _buildUserAvatar(), + const SizedBox(width: 10), + ], Flexible( - child: Column( - crossAxisAlignment: message.isUser - ? CrossAxisAlignment.start - : CrossAxisAlignment.end, - children: [ - Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, vertical: 10), - decoration: BoxDecoration( - gradient: !message.isUser - ? const LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - Color(0xFF0066AA), - Color(0xFF0088DD), - ], - ) - : null, - color: !message.isUser ? null : const Color(0xFFF5F7FA), - borderRadius: BorderRadius.only( - topLeft: Radius.circular(!message.isUser ? 4 : 16), - topRight: const Radius.circular(16), - bottomLeft: const Radius.circular(16), - bottomRight: Radius.circular(!message.isUser ? 16 : 4), - ), - border: !message.isUser - ? null - : Border.all( - color: const Color(0xFFE0E5EC).withOpacity(0.5), - width: 1, - ), - boxShadow: [ - BoxShadow( - color: !message.isUser - ? const Color(0xFF0066AA).withOpacity(0.2) - : Colors.black.withOpacity(0.03), - blurRadius: 8, - offset: const Offset(0, 3), - ), - ], - ), - child: DidvanText( - message.text, - color: !message.isUser - ? Colors.white - : const Color(0xFF1A1A1A), - fontSize: 13.5, - ), - ), - const SizedBox(height: 4), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 3), - child: DidvanText( - _formatTime(message.timestamp), - fontSize: 10, - color: Colors.grey.shade400, - ), - ), - if (message.sources.isNotEmpty) ...[ - const SizedBox(height: 8), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 10, vertical: 6), - decoration: BoxDecoration( - gradient: LinearGradient( + child: Container( + padding: + const EdgeInsets.symmetric(horizontal: 18, vertical: 14), + decoration: BoxDecoration( + gradient: message.isUser + ? null + : const LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, colors: [ - const Color(0xFF0066AA).withOpacity(0.08), - const Color(0xFF0088DD).withOpacity(0.05), + Color(0xFF0066AA), + Color(0xFF0099FF), ], ), - borderRadius: BorderRadius.circular(10), - border: Border.all( - color: const Color(0xFF0066AA).withOpacity(0.2), - width: 0.8, - ), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - padding: const EdgeInsets.all(3), - decoration: BoxDecoration( - color: const Color(0xFF0066AA).withOpacity(0.1), - shape: BoxShape.circle, - ), - child: const Icon( - Icons.bookmark_rounded, - size: 12, - color: Color(0xFF0066AA), - ), - ), - const SizedBox(width: 6), - DidvanText( - 'منابع: ${message.sources.join(", ")}', - fontSize: 11, - color: const Color(0xFF0066AA), - fontWeight: FontWeight.w600, - ), - ], - ), + color: message.isUser ? Colors.white.withOpacity(0.6) : null, + borderRadius: BorderRadius.only( + topLeft: const Radius.circular(24), + topRight: const Radius.circular(24), + bottomRight: Radius.circular(message.isUser ? 4 : 24), + bottomLeft: Radius.circular(message.isUser ? 24 : 4), + ), + border: Border.all( + color: message.isUser + ? Colors.white.withOpacity(0.5) + : Colors.white.withOpacity(0.2), + width: 1, + ), + boxShadow: [ + BoxShadow( + color: message.isUser + ? Colors.black.withOpacity(0.03) + : const Color(0xFF0066AA).withOpacity(0.25), + blurRadius: 15, + offset: const Offset(0, 6), ), ], - // if (!message.isUser && - // message.audioUrl != null && - // message.audioUrl!.isNotEmpty) ...[ - // const SizedBox(height: 8), - // InkWell( - // onTap: () => _toggleAudioPlayback(message.audioUrl!), - // borderRadius: BorderRadius.circular(20), - // child: Container( - // padding: const EdgeInsets.symmetric( - // horizontal: 12, vertical: 8), - // decoration: BoxDecoration( - // gradient: LinearGradient( - // colors: [ - // const Color(0xFF0066AA).withOpacity(0.1), - // const Color(0xFF0088DD).withOpacity(0.08), - // ], - // ), - // borderRadius: BorderRadius.circular(20), - // border: Border.all( - // color: const Color(0xFF0066AA).withOpacity(0.3), - // width: 1, - // ), - // ), - // child: Row( - // mainAxisSize: MainAxisSize.min, - // children: [ - // Container( - // padding: const EdgeInsets.all(4), - // decoration: const BoxDecoration( - // gradient: LinearGradient( - // colors: [ - // Color(0xFF0066AA), - // Color(0xFF0088DD), - // ], - // ), - // shape: BoxShape.circle, - // ), - // child: Icon( - // (_isPlaying && - // _currentPlayingUrl == message.audioUrl) - // ? Icons.pause_rounded - // : Icons.play_arrow_rounded, - // size: 16, - // color: Colors.white, - // ), - // ), - // const SizedBox(width: 8), - // DidvanText( - // (_isPlaying && - // _currentPlayingUrl == message.audioUrl) - // ? 'در حال پخش...' - // : 'پخش صوتی پاسخ', - // fontSize: 12, - // color: const Color(0xFF0066AA), - // fontWeight: FontWeight.w600, - // ), - // ], - // ), - // ), - // ), - // ], - ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + DidvanText( + message.text, + color: + message.isUser ? const Color(0xFF1A2B3C) : Colors.white, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + const SizedBox(height: 4), + Align( + alignment: Alignment.centerLeft, + child: DidvanText( + _formatTime(message.timestamp), + fontSize: 10, + color: message.isUser + ? Colors.grey.shade600 + : Colors.white.withOpacity(0.7), + ), + ), + if (message.sources.isNotEmpty) ...[ + const SizedBox(height: 10), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 10, vertical: 6), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.15), + borderRadius: BorderRadius.circular(14), + border: Border.all( + color: Colors.white.withOpacity(0.2), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.bookmark_rounded, + size: 12, + color: Colors.white, + ), + const SizedBox(width: 6), + DidvanText( + 'منابع: ${message.sources.join(", ")}', + fontSize: 11, + color: Colors.white, + fontWeight: FontWeight.w600, + ), + ], + ), + ), + ], + ], + ), ), ), - if (!message.isUser) const SizedBox(width: 8), - if (!message.isUser) _buildAiAvatar(), + if (!message.isUser) ...[ + const SizedBox(width: 10), + _buildAiAvatar(), + ], ], ), ), @@ -702,27 +795,25 @@ class _AiChatDialogState extends State Widget _buildAiAvatar() { return Container( - width: 32, - height: 32, + width: 38, + height: 38, decoration: BoxDecoration( gradient: const LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, - colors: [ - Color(0xFF0066AA), - Color(0xFF00AAFF), - ], + colors: [Color(0xFF0066AA), Color(0xFF00AAFF)], ), shape: BoxShape.circle, + border: Border.all(color: Colors.white.withOpacity(0.8), width: 1.5), boxShadow: [ BoxShadow( color: const Color(0xFF0066AA).withOpacity(0.3), - blurRadius: 8, - offset: const Offset(0, 3), + blurRadius: 12, + offset: const Offset(0, 4), ), ], ), - padding: const EdgeInsets.all(7), + padding: const EdgeInsets.all(9), child: SvgPicture.asset( 'lib/assets/icons/live ai.svg', colorFilter: const ColorFilter.mode( @@ -736,311 +827,124 @@ class _AiChatDialogState extends State Widget _buildUserAvatar() { return Consumer( builder: (context, userProvider, _) { - if (userProvider.user.photo != null && - userProvider.user.photo!.isNotEmpty) { - return Container( - width: 40, - height: 40, - decoration: BoxDecoration( - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.25), - blurRadius: 6, - offset: const Offset(0, 2), - ), - ], + return Container( + width: 38, + height: 38, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: Colors.white.withOpacity(0.8), + width: 1.5, ), - child: ClipRRect( - borderRadius: BorderRadius.circular(16), - child: Image.network( - userProvider.user.photo!, - fit: BoxFit.cover, - errorBuilder: (context, error, stackTrace) { - return _buildDefaultUserAvatar(); - }, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.08), + blurRadius: 10, + offset: const Offset(0, 4), ), - ), - ); - } - return _buildDefaultUserAvatar(); + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(19), + child: (userProvider.user.photo != null && + userProvider.user.photo!.isNotEmpty) + ? Image.network( + userProvider.user.photo!, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) => + _buildDefaultUserAvatar(), + ) + : _buildDefaultUserAvatar(), + ), + ); }, ); } Widget _buildDefaultUserAvatar() { return Container( - width: 40, - height: 40, decoration: BoxDecoration( gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - Colors.grey.shade400, - Colors.grey.shade500, - ], + colors: [Colors.grey.shade300, Colors.grey.shade400], ), shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.25), - blurRadius: 6, - offset: const Offset(0, 2), - ), - ], ), child: const Icon( Icons.person_rounded, - size: 18, + size: 20, color: Colors.white, ), ); } - Widget _buildLoadingIndicator() { - return Padding( - padding: const EdgeInsets.fromLTRB(14, 6, 14, 10), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Container( - padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), - decoration: BoxDecoration( - color: const Color(0xFFF5F7FA), - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: const Color(0xFFE0E5EC).withOpacity(0.5), - width: 1, - ), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.03), - blurRadius: 8, - offset: const Offset(0, 3), - ), - ], - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'در حال فکر کردن...', - style: TextStyle( - color: Colors.grey.shade600, - fontSize: 12, - fontStyle: FontStyle.italic, + Widget _buildLiquidLoadingIndicator() { + return Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + LiquidGlassContainer( + backgroundKey: _backgroundKey, + shader: _loadingGlassShader, + distortion: 3.0, + blur: 20.0, + dispersion: 2.2, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(24), + topRight: Radius.circular(24), + bottomRight: Radius.circular(24), + bottomLeft: Radius.circular(4), + ), + child: AnimatedBuilder( + animation: _shimmerController, + builder: (context, child) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + color: const Color.fromARGB(167, 255, 255, 255), + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(24), + topRight: Radius.circular(24), + bottomRight: Radius.circular(24), + bottomLeft: Radius.circular(4), + ), + border: Border.all( + color: Colors.white.withOpacity(0.3), + width: 1, ), ), - const SizedBox(width: 10), - const SpinKitThreeBounce( - color: Color(0xFF0066AA), - size: 14, - ), - ], - ), - ), - const SizedBox(width: 8), - _buildAiAvatar(), - ], - ), - ); - } - - // Widget _buildRecordingIndicator() { - // return Container( - // padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), - // margin: const EdgeInsets.only(bottom: 6), - // decoration: BoxDecoration( - // gradient: LinearGradient( - // begin: Alignment.topLeft, - // end: Alignment.bottomRight, - // colors: [ - // const Color(0xFFFF3366).withOpacity(0.1), - // const Color(0xFFFF6699).withOpacity(0.05), - // ], - // ), - // border: Border.all( - // color: const Color(0xFFFF3366).withOpacity(0.3), - // width: 1, - // ), - // ), - // child: Row( - // children: [ - // Container( - // width: 8, - // height: 8, - // decoration: BoxDecoration( - // color: const Color(0xFFFF3366), - // shape: BoxShape.circle, - // boxShadow: [ - // BoxShadow( - // color: const Color(0xFFFF3366).withOpacity(0.4), - // blurRadius: 6, - // spreadRadius: 1, - // ), - // ], - // ), - // ), - // const SizedBox(width: 10), - // const Expanded( - // child: DidvanText( - // '🎙️ در حال ضبط...', - // fontSize: 12, - // color: Color(0xFFFF3366), - // fontWeight: FontWeight.w600, - // ), - // ), - // const Icon( - // Icons.mic_rounded, - // color: Color(0xFFFF3366), - // size: 16, - // ), - // ], - // ), - // ); - // } - - Widget _buildInputField() { - return Container( - padding: const EdgeInsets.all(14), - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.95), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.06), - blurRadius: 15, - offset: const Offset(0, -3), - ), - ], - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - GestureDetector( - onTap: _isLoading || _isRecording ? null : _sendMessage, - child: AnimatedContainer( - duration: const Duration(milliseconds: 200), - width: 44, - height: 55, - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: (_isLoading || _isRecording) - ? [Colors.grey.shade300, Colors.grey.shade400] - : [const Color(0xFF0066AA), const Color(0xFF00AAFF)], - ), - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: ((_isLoading || _isRecording) - ? Colors.grey.shade400 - : const Color(0xFF0066AA)) - .withOpacity(0.35), - blurRadius: 12, - offset: const Offset(0, 3), - ), - ], - ), - child: _isLoading - ? const Padding( - padding: EdgeInsets.all(11), - child: CircularProgressIndicator( - strokeWidth: 2, - valueColor: AlwaysStoppedAnimation(Colors.white), - ), - ) - : Padding( - padding: const EdgeInsets.all(8.0), - child: SvgPicture.asset( - 'lib/assets/icons/send.svg', - color: Colors.white, - height: 5, - ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + DidvanText( + 'در حال فکر کردن...', + color: const Color(0xFF1A2B3C).withOpacity(0.8), + fontSize: 12, + fontWeight: FontWeight.w700, ), - ), + const SizedBox(width: 10), + Stack( + alignment: Alignment.center, + children: [ + SpinKitPulse( + color: const Color(0xFF0066AA).withOpacity(0.5), + size: 24, + ), + const Icon( + Icons.auto_awesome, + size: 14, + color: Color(0xFF0066AA), + ) + ], + ), + ], + ), + ); + }, ), - const SizedBox(width: 10), - Expanded( - child: Container( - constraints: const BoxConstraints(maxHeight: 100), - decoration: BoxDecoration( - color: const Color(0xFFF5F7FA), - borderRadius: BorderRadius.circular(22), - border: Border.all( - color: const Color(0xFFE0E5EC).withOpacity(0.5), - width: 1.2, - ), - ), - child: TextField( - controller: _messageController, - maxLines: null, - textInputAction: TextInputAction.send, - onSubmitted: (_) => _sendMessage(), - style: const TextStyle( - fontSize: 13.5, - color: Color(0xFF1A1A1A), - ), - decoration: InputDecoration( - hintText: 'پیام خود را بنویسید...', - hintStyle: TextStyle( - color: Colors.grey.shade400, - fontSize: 13, - ), - border: InputBorder.none, - contentPadding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 12, - ), - ), - ), - ), - ), - - // GestureDetector( - // onTap: () { - // if (!_isLoading) { - // if (_isRecording) { - // _stopRecording(); - // } else { - // _startRecording(); - // } - // } - // }, - // child: AnimatedContainer( - // duration: const Duration(milliseconds: 200), - // width: 44, - // height: 44, - // decoration: BoxDecoration( - // gradient: LinearGradient( - // begin: Alignment.topLeft, - // end: Alignment.bottomRight, - // colors: _isRecording - // ? [const Color(0xFFFF3366), const Color(0xFFFF6699)] - // : [const Color(0xFF6B7280), const Color(0xFF9CA3AF)], - // ), - // shape: BoxShape.circle, - // boxShadow: [ - // BoxShadow( - // color: (_isRecording - // ? const Color(0xFFFF3366) - // : const Color(0xFF6B7280)) - // .withOpacity(0.35), - // blurRadius: _isRecording ? 16 : 10, - // offset: const Offset(0, 3), - // ), - // ], - // ), - // child: Icon( - // _isRecording ? Icons.stop_rounded : Icons.mic_rounded, - // color: Colors.white, - // size: 20, - // ), - // ), - // ), - ], - ), + ), + const SizedBox(width: 10), + _buildAiAvatar(), + ], ); } @@ -1065,4 +969,4 @@ class ChatMessage { this.sources = const [], this.audioUrl, }); -} +} \ No newline at end of file diff --git a/lib/views/widgets/ai_voice_chat_dialog.dart b/lib/views/widgets/ai_voice_chat_dialog.dart index 488e773..0621d6b 100644 --- a/lib/views/widgets/ai_voice_chat_dialog.dart +++ b/lib/views/widgets/ai_voice_chat_dialog.dart @@ -149,8 +149,7 @@ class _AiVoiceChatDialogState extends State _isAiSpeaking = false; }); } - - // <<< راه‌حل مشکل ۲: ریست کردن پاسخ قبلی + if (_currentAiResponse != null) { setState(() { _currentAiResponse = null; @@ -178,7 +177,6 @@ class _AiVoiceChatDialogState extends State await Future.delayed(const Duration(seconds: 2)); - // <<< راه‌حل مشکل ۲: بررسی وضعیت قبل از شروع ضبط if (mounted && _isPreparing) { setState(() { _isPreparing = false; @@ -188,11 +186,9 @@ class _AiVoiceChatDialogState extends State _preparingController.stop(); _waveController.repeat(); } else if (mounted) { - // اگر کاربر انگشت خود را برداشته بود (_isPreparing false شده) - await _audioRecorder.stop(); // ضبط را متوقف کن + await _audioRecorder.stop(); _waveController.stop(); } - // >>> } } catch (e) { setState(() { @@ -200,7 +196,6 @@ class _AiVoiceChatDialogState extends State _isRecording = false; _statusText = 'خطا در شروع ضبط صدا'; }); - // <<< راه‌حل مشکل ۲: توقف انیمیشن‌ها در صورت خطا _preparingController.stop(); _waveController.stop(); // >>> @@ -475,8 +470,8 @@ class _AiVoiceChatDialogState extends State top: 0, child: _buildVisualization(), ), - // وقتی پاسخ هست، کارت پاسخ را نمایش می‌دهیم - if (_currentAiResponse != null && _currentAiResponse!.isNotEmpty) + if (_currentAiResponse != null && + _currentAiResponse!.isNotEmpty) Positioned( bottom: 120, left: 24, @@ -484,7 +479,6 @@ class _AiVoiceChatDialogState extends State child: _buildResponsePreview(), ) else - // در غیر این صورت، متن status را نمایش می‌دهیم Positioned( bottom: 130, left: 24, @@ -709,7 +703,8 @@ class _AiVoiceChatDialogState extends State shape: BoxShape.circle, boxShadow: [ BoxShadow( - color: const Color(0xFF00AAFF).withOpacity(0.4), + color: const Color(0xFF00AAFF) + .withOpacity(0.4), blurRadius: 12, spreadRadius: 2, ), @@ -872,7 +867,8 @@ class _AiVoiceChatDialogState extends State shape: BoxShape.circle, boxShadow: [ BoxShadow( - color: const Color(0xFF00AAFF).withOpacity(0.4), + color: + const Color(0xFF00AAFF).withOpacity(0.4), blurRadius: 15, spreadRadius: 2, ), @@ -953,7 +949,8 @@ class _AiVoiceChatDialogState extends State Expanded( child: ElevatedButton.icon( onPressed: () { - Clipboard.setData(ClipboardData(text: _currentAiResponse ?? '')); + Clipboard.setData(ClipboardData( + text: _currentAiResponse ?? '')); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const DidvanText( @@ -1033,15 +1030,13 @@ class _AiVoiceChatDialogState extends State _startRecording(); } }, - // <<< راه‌حل مشکل ۲: (بدون تغییر، همچنان پابرجاست) onLongPressEnd: (_) { if (_isRecording) { _stopRecording(); } else if (_isPreparing) { - // کاربر انگشت خود را در حین آماده‌سازی برداشت setState(() { _isPreparing = false; - _statusText = 'برای شروع مکالمه، دکمه میکروفون را نگه دارید'; + _statusText = 'برای شروع مکالمه، دکمه میکروفون را نگه دارید'; }); _preparingController.stop(); _waveController.stop(); @@ -1236,4 +1231,4 @@ class PreparingSpinnerPainter extends CustomPainter { bool shouldRepaint(covariant PreparingSpinnerPainter oldDelegate) { return true; } -} \ No newline at end of file +} diff --git a/lib/views/widgets/didvan/bnb.dart b/lib/views/widgets/didvan/bnb.dart index 41eae07..3b7d410 100644 --- a/lib/views/widgets/didvan/bnb.dart +++ b/lib/views/widgets/didvan/bnb.dart @@ -1,3 +1,5 @@ +// ignore_for_file: deprecated_member_use + import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; @@ -312,7 +314,10 @@ class _NavBarItemState extends State<_NavBarItem> duration: const Duration(milliseconds: 150), child: DidvanText( 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, fontWeight: FontWeight.w500, ), diff --git a/lib/views/widgets/didvan/page_view.dart b/lib/views/widgets/didvan/page_view.dart index 939e359..ca31736 100644 --- a/lib/views/widgets/didvan/page_view.dart +++ b/lib/views/widgets/didvan/page_view.dart @@ -491,9 +491,53 @@ class _DidvanPageViewState extends State { const EdgeInsets.symmetric( horizontal: 16, vertical: 4), - child: MultitypeOverview( - item: item.relatedContents[i], - onMarkChanged: (id, value) {}, + child: GestureDetector( + onTap: () { + 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), diff --git a/lib/views/widgets/glass/glass_logic.dart b/lib/views/widgets/glass/glass_logic.dart new file mode 100644 index 0000000..4fcf063 --- /dev/null +++ b/lib/views/widgets/glass/glass_logic.dart @@ -0,0 +1,46 @@ +import 'dart:ui' as ui; +import 'package:flutter/material.dart'; + +class GlassShader { + ui.FragmentProgram? _program; + ui.FragmentShader? _shader; + + Future 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); + } +} diff --git a/lib/views/widgets/glass/liquid_glass_container.dart b/lib/views/widgets/glass/liquid_glass_container.dart new file mode 100644 index 0000000..2561d43 --- /dev/null +++ b/lib/views/widgets/glass/liquid_glass_container.dart @@ -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 createState() => _LiquidGlassContainerState(); +} + +class _LiquidGlassContainerState extends State + 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 _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, + ], + ), + ); + } +} diff --git a/lib/views/widgets/home_app_bar.dart b/lib/views/widgets/home_app_bar.dart index 87bc11c..0df6820 100644 --- a/lib/views/widgets/home_app_bar.dart +++ b/lib/views/widgets/home_app_bar.dart @@ -45,7 +45,9 @@ class HomeAppBar extends StatelessWidget { return Column( children: [ - const SizedBox(height: 10,), + const SizedBox( + height: 10, + ), Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), child: Row( @@ -105,8 +107,8 @@ class HomeAppBar extends StatelessWidget { ), Container( color: const Color.fromARGB(255, 224, 224, 224), - height: 20, - width: 1.5, + height: 20, + width: 1.5, ), GestureDetector( onTap: () { diff --git a/lib/views/widgets/hoshan_home_app_bar.dart b/lib/views/widgets/hoshan_home_app_bar.dart index 18d860f..8d3a045 100644 --- a/lib/views/widgets/hoshan_home_app_bar.dart +++ b/lib/views/widgets/hoshan_home_app_bar.dart @@ -534,7 +534,6 @@ class _HistoryDrawerContentState extends State { ...last7DaysChats.map((chat) => _buildChatItem(chat, historyState)), ], - // '۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹' if (last30DaysChats.isNotEmpty) ...[ _buildSectionHeader('۳۰ روز اخیر'), ...last30DaysChats.map((chat) => @@ -616,7 +615,8 @@ class _HistoryDrawerContentState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ DidvanText( - _formatDateFromString(chat.updatedAt).toPersianDigit(), + _formatDateFromString(chat.updatedAt) + .toPersianDigit(), fontSize: 12, color: Colors.grey[600], ), diff --git a/lib/views/widgets/logo_app_bar.dart b/lib/views/widgets/logo_app_bar.dart index db6f4fa..2091d22 100644 --- a/lib/views/widgets/logo_app_bar.dart +++ b/lib/views/widgets/logo_app_bar.dart @@ -127,7 +127,6 @@ class LogoAppBar extends StatelessWidget implements PreferredSizeWidget { void _onChanged(String value, BuildContext context) { final state = context.read(); - // Use the improved search functionality from HomeState if (value.length >= 2) { state.onSearchChanged(value); } else if (value.isEmpty) { diff --git a/lib/views/widgets/overview/multitype.dart b/lib/views/widgets/overview/multitype.dart index 65999b1..bf3ef86 100644 --- a/lib/views/widgets/overview/multitype.dart +++ b/lib/views/widgets/overview/multitype.dart @@ -22,7 +22,7 @@ class MultitypeOverview extends StatelessWidget { final bool enableCaption; final bool enableBookmark; final bool showDivider; - final bool useCardStyle; // **جدید: برای کنترل استایل کارت** + final bool useCardStyle; const MultitypeOverview({ Key? key, @@ -32,11 +32,10 @@ class MultitypeOverview extends StatelessWidget { this.enableCaption = false, this.enableBookmark = false, this.showDivider = true, - this.useCardStyle = false, // **مقدار پیش‌فرض false** + this.useCardStyle = false, }) : super(key: key); get _targetPageArgs { -// ... if (item.type == 'radar') { return const RadarRequestArgs(page: 0); } @@ -47,7 +46,6 @@ class MultitypeOverview extends StatelessWidget { } String? get _targetPageRouteName { -// ... if (item.type == 'radar') { return Routes.radarDetails; } @@ -64,7 +62,6 @@ class MultitypeOverview extends StatelessWidget { } String get _icon { -// ... switch (item.type) { case 'radar': return 'lib/assets/icons/Pouyesh_Ofogh_New.svg'; @@ -88,7 +85,6 @@ class MultitypeOverview extends StatelessWidget { @override Widget build(BuildContext context) { - // محتوای داخلی کارت final innerContent = Column( children: [ Row( @@ -249,7 +245,6 @@ class MultitypeOverview extends StatelessWidget { ], ], ), - // **اگر useCardStyle فعال نباشد، فاصله و خط جداکننده قبلی را اضافه می‌کنیم** if (!useCardStyle) ...[ const SizedBox( height: 10, @@ -262,7 +257,6 @@ class MultitypeOverview extends StatelessWidget { ], ); - // **اعمال استایل کارت (Container با Border و Padding) به صورت شرطی** if (useCardStyle) { return Padding( padding: const EdgeInsets.all(20.0), @@ -281,7 +275,7 @@ class MultitypeOverview extends StatelessWidget { ); } - return innerContent; // در غیر این صورت، فقط محتوای داخلی را برمی‌گرداند. + return innerContent; } static Widget get placeholder => const DidvanCard( @@ -306,4 +300,4 @@ class MultitypeOverview extends StatelessWidget { ], ), ); -} \ No newline at end of file +} diff --git a/lib/views/widgets/overview/news.dart b/lib/views/widgets/overview/news.dart index 397961e..5f9076d 100644 --- a/lib/views/widgets/overview/news.dart +++ b/lib/views/widgets/overview/news.dart @@ -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/divider.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/skeleton_image.dart'; import 'package:flutter/material.dart'; @@ -197,4 +196,4 @@ class NewsOverview extends StatelessWidget { ], ), ); -} \ No newline at end of file +} diff --git a/lib/views/widgets/overview/radar.dart b/lib/views/widgets/overview/radar.dart index b32b81a..c409ac9 100644 --- a/lib/views/widgets/overview/radar.dart +++ b/lib/views/widgets/overview/radar.dart @@ -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/divider.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/skeleton_image.dart'; import 'package:flutter/material.dart'; @@ -176,8 +175,7 @@ class RadarOverview extends StatelessWidget { svgIconOn: 'lib/assets/icons/bookmark_fill.svg', svgIconOff: 'lib/assets/icons/archive-tick.svg', color: const Color.fromARGB(255, 0, 126, 167), - unbookmarkedColor: - Theme.of(context).colorScheme.caption, + unbookmarkedColor: Theme.of(context).colorScheme.caption, ), ], ), diff --git a/lib/views/widgets/state_handlers/empty_connection.dart b/lib/views/widgets/state_handlers/empty_connection.dart index 6702eb4..2a4079f 100644 --- a/lib/views/widgets/state_handlers/empty_connection.dart +++ b/lib/views/widgets/state_handlers/empty_connection.dart @@ -13,7 +13,7 @@ class EmptyConnection extends StatelessWidget { title: 'اتصال اینترنت برقرار نیست', titleColor: const Color.fromARGB(255, 0, 126, 167), action: onRetry, - buttonTitle: 'تلاش مجدد' , + buttonTitle: 'تلاش مجدد', ); } } diff --git a/lib/views/widgets/state_handlers/empty_state.dart b/lib/views/widgets/state_handlers/empty_state.dart index c8e8f65..774e97c 100644 --- a/lib/views/widgets/state_handlers/empty_state.dart +++ b/lib/views/widgets/state_handlers/empty_state.dart @@ -29,8 +29,11 @@ class EmptyState extends StatelessWidget { final isSvg = asset.toLowerCase().endsWith('.svg'); return Column( - mainAxisAlignment: subtitle != null ? MainAxisAlignment.start : MainAxisAlignment.center, - crossAxisAlignment: subtitle != null ? CrossAxisAlignment.start : CrossAxisAlignment.center, + mainAxisAlignment: + subtitle != null ? MainAxisAlignment.start : MainAxisAlignment.center, + crossAxisAlignment: subtitle != null + ? CrossAxisAlignment.start + : CrossAxisAlignment.center, children: [ SizedBox( width: double.infinity, diff --git a/lib/views/widgets/state_handlers/sliver_state_handler.dart b/lib/views/widgets/state_handlers/sliver_state_handler.dart index 0c980b7..f4741cc 100644 --- a/lib/views/widgets/state_handlers/sliver_state_handler.dart +++ b/lib/views/widgets/state_handlers/sliver_state_handler.dart @@ -1,3 +1,5 @@ +// ignore_for_file: prefer_const_constructors + import 'package:didvan/models/enums.dart'; import 'package:didvan/providers/core.dart'; import 'package:didvan/views/widgets/state_handlers/empty_connection.dart'; diff --git a/pubspec.lock b/pubspec.lock index 7cdab4a..8baa8d4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -933,6 +933,14 @@ packages: url: "https://pub.dev" source: hosted 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: dependency: transitive description: @@ -949,6 +957,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.5.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" markdown: dependency: transitive description: @@ -1005,6 +1021,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.6" + motor: + dependency: transitive + description: + name: motor + sha256: d6fd00496a2b934f8f54e660acc0d039d0e4bf8e667b6977b0480be97385779e + url: "https://pub.dev" + source: hosted + version: "1.0.1" nested: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 944afc7..a9fb977 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -117,6 +117,7 @@ dependencies: syncfusion_flutter_charts: ^31.1.19 flutter_animate: ^4.5.2 syncfusion_flutter_pdfviewer: ^31.1.21 + liquid_glass_renderer: ^0.2.0-dev.4 # image_gallery_saver: ^2.0.3 # fading_edge_scrollview: ^4.1.1 @@ -135,6 +136,10 @@ dev_dependencies: # The following section is specific to 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 # included with your application, so that you can use the icons in # the material Icons class.