diff --git a/lib/initialize_app.dart b/lib/initialize_app.dart index 7917c018..2e474067 100644 --- a/lib/initialize_app.dart +++ b/lib/initialize_app.dart @@ -10,6 +10,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:google_sign_in/google_sign_in.dart'; +import 'package:liquid_glass_widgets/liquid_glass_widgets.dart'; import 'package:skelter/core/services/injection_container.dart'; import 'package:skelter/firebase_options_dev.dart' as dev; import 'package:skelter/firebase_options_prod.dart' as prod; @@ -30,6 +31,8 @@ Future initializeApp({ Dio? dio, }) async { WidgetsFlutterBinding.ensureInitialized(); + // Precache `liquid_glass_widgets` shader to avoid a first-paint white flash. + await LiquidGlassWidgets.initialize(); tz.initializeTimeZones(); final firebaseOptions = switch (AppConfig.appFlavor) { diff --git a/lib/main.dart b/lib/main.dart index 16e8d4bf..d0355cba 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -10,6 +10,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:liquid_glass_widgets/liquid_glass_widgets.dart'; import 'package:sizer/sizer.dart'; import 'package:skelter/constants/constants.dart'; import 'package:skelter/core/clarity_analytics/clarity_route_observer.dart'; @@ -214,27 +215,31 @@ class _MainAppState extends State { builder: (context, orientation, screenType) { return BlocBuilder( builder: (context, state) { - return MaterialApp.router( - debugShowCheckedModeBanner: false, - supportedLocales: I18n.all, - localizationsDelegates: const [ - AppLocalizations.delegate, - CountryLocalizations.delegate, - GlobalMaterialLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - ], - routerConfig: appRouter.config( - navigatorObservers: () => [ClarityRouteObserver()], + // Speeds up `liquid_glass_widgets` rendering when multiple glass + // widgets appear on screen. Safe to keep even if no glass is used. + return GlassBackdropScope( + child: MaterialApp.router( + debugShowCheckedModeBanner: false, + supportedLocales: I18n.all, + localizationsDelegates: const [ + AppLocalizations.delegate, + CountryLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + routerConfig: appRouter.config( + navigatorObservers: () => [ClarityRouteObserver()], + ), + theme: AppThemesData.themeData[AppThemeEnum.LightTheme]!, + darkTheme: AppThemesData.themeData[AppThemeEnum.DarkTheme]!, + themeMode: state.themeMode, + builder: (context, child) { + final localization = AppLocalizations.of(context)!; + SubscriptionService().setLocalization(localization); + return child!; + }, ), - theme: AppThemesData.themeData[AppThemeEnum.LightTheme]!, - darkTheme: AppThemesData.themeData[AppThemeEnum.DarkTheme]!, - themeMode: state.themeMode, - builder: (context, child) { - final localization = AppLocalizations.of(context)!; - SubscriptionService().setLocalization(localization); - return child!; - }, ); }, ); diff --git a/lib/presentation/ai_chat/widgets/ai_chat_bottom_sheet.dart b/lib/presentation/ai_chat/widgets/ai_chat_bottom_sheet.dart index 84c666e4..b7715908 100644 --- a/lib/presentation/ai_chat/widgets/ai_chat_bottom_sheet.dart +++ b/lib/presentation/ai_chat/widgets/ai_chat_bottom_sheet.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_tabler_icons/flutter_tabler_icons.dart'; +import 'package:liquid_glass_widgets/liquid_glass_widgets.dart'; import 'package:skelter/common/theme/text_style/app_text_styles.dart'; import 'package:skelter/i18n/localization.dart'; import 'package:skelter/presentation/ai_chat/bloc/ai_chat_bloc.dart'; @@ -52,11 +53,15 @@ class _AiChatSheetContent extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( + // iOS 26 liquid-glass surface for the sheet. `GlassContainer` (not + // `GlassSheet`) — it gives bounded height to the inner `Expanded` list. + return GlassContainer( height: MediaQuery.sizeOf(context).height * 0.7, - decoration: BoxDecoration( - color: context.currentTheme.bgSurfaceBase, - borderRadius: const BorderRadius.vertical(top: Radius.circular(20)), + // Tinted glass — keeps text readable while showing refraction. + settings: LiquidGlassSettings( + blur: 10, + glassColor: context.currentTheme.bgSurfaceBase.withValues(alpha: 0.78), + lightIntensity: 1.2, ), child: GestureDetector( onTap: () => FocusManager.instance.primaryFocus?.unfocus(), @@ -185,7 +190,18 @@ class _AiChatSheetContent extends StatelessWidget { itemCount: suggestions.length, separatorBuilder: (_, _) => const SizedBox(width: 8), itemBuilder: (context, index) { - return GestureDetector( + // Pill-shaped glass chip from `liquid_glass_widgets`. + return GlassChip( + label: suggestions[index], + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 4, + ), + labelStyle: AppTextStyles.p3Medium.copyWith( + color: state.isGenerating + ? context.currentTheme.textNeutralSecondary + : context.currentTheme.textBrandSecondary, + ), onTap: state.isGenerating ? null : () { @@ -193,31 +209,6 @@ class _AiChatSheetContent extends StatelessWidget { SendMessageEvent(message: suggestions[index]), ); }, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 14, - vertical: 6, - ), - decoration: BoxDecoration( - color: state.isGenerating - ? context.currentTheme.bgNeutralLight50 - : context.currentTheme.bgBrandLight50, - borderRadius: BorderRadius.circular(20), - border: Border.all( - color: state.isGenerating - ? context.currentTheme.strokeNeutralLight100 - : context.currentTheme.strokeBrandDefault, - ), - ), - child: Text( - suggestions[index], - style: AppTextStyles.p3Medium.copyWith( - color: state.isGenerating - ? context.currentTheme.textNeutralSecondary - : context.currentTheme.textBrandSecondary, - ), - ), - ), ); }, ), @@ -308,7 +299,7 @@ class _AiChatMessageListState extends State<_AiChatMessageList> { Icon( TablerIcons.message_chatbot, size: 40, - color: context.currentTheme.strokeNeutralHover, + color: context.currentTheme.iconBrandPrimary, ), const SizedBox(height: 16), Text( @@ -322,7 +313,7 @@ class _AiChatMessageListState extends State<_AiChatMessageList> { context.localization.ai_chat_description, textAlign: TextAlign.center, style: AppTextStyles.p3Regular.copyWith( - color: context.currentTheme.textNeutralSecondary, + color: context.currentTheme.textNeutralPrimary, ), ), ], diff --git a/pubspec.lock b/pubspec.lock index 027d60dd..70e680f4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -955,6 +955,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.2" + flutter_shaders: + dependency: transitive + description: + name: flutter_shaders + sha256: "34794acadd8275d971e02df03afee3dee0f98dbfb8c4837082ad0034f612a3e2" + url: "https://pub.dev" + source: hosted + version: "0.1.3" flutter_svg: dependency: "direct main" description: @@ -1373,6 +1381,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.0" + liquid_glass_widgets: + dependency: "direct main" + description: + name: liquid_glass_widgets + sha256: "13c4b3295187e69a54eb531125358e3602d47c656488babba2f7e52f0e0dd0a8" + url: "https://pub.dev" + source: hosted + version: "0.5.0" local_auth: dependency: "direct main" description: @@ -1485,6 +1501,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + motor: + dependency: transitive + description: + name: motor + sha256: cbd49f21b00e568c2b1a55f134ed803614a107782f4fea7769693bca32940c58 + url: "https://pub.dev" + source: hosted + version: "1.1.0" native_toolchain_c: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 740430d9..5cd71e8b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -87,6 +87,7 @@ dependencies: flutter_dynamic_icon_plus: ^1.4.0 cloud_firestore: ^6.1.1 rate_my_app: ^2.3.2 + liquid_glass_widgets: ^0.5.0 dev_dependencies: flutter_test: diff --git a/test/presentation/ai_chat/goldens/linux/ai_chat_bottom_sheet_empty.png b/test/presentation/ai_chat/goldens/linux/ai_chat_bottom_sheet_empty.png index d0a64a28..5db16b60 100644 Binary files a/test/presentation/ai_chat/goldens/linux/ai_chat_bottom_sheet_empty.png and b/test/presentation/ai_chat/goldens/linux/ai_chat_bottom_sheet_empty.png differ diff --git a/test/presentation/ai_chat/goldens/linux/ai_chat_bottom_sheet_error.png b/test/presentation/ai_chat/goldens/linux/ai_chat_bottom_sheet_error.png index a6a8c377..1be9b184 100644 Binary files a/test/presentation/ai_chat/goldens/linux/ai_chat_bottom_sheet_error.png and b/test/presentation/ai_chat/goldens/linux/ai_chat_bottom_sheet_error.png differ diff --git a/test/presentation/ai_chat/goldens/linux/ai_chat_bottom_sheet_with_messages.png b/test/presentation/ai_chat/goldens/linux/ai_chat_bottom_sheet_with_messages.png index 074897d4..6eb593e9 100644 Binary files a/test/presentation/ai_chat/goldens/linux/ai_chat_bottom_sheet_with_messages.png and b/test/presentation/ai_chat/goldens/linux/ai_chat_bottom_sheet_with_messages.png differ diff --git a/test/presentation/ai_chat/goldens/macos/ai_chat_bottom_sheet_empty.png b/test/presentation/ai_chat/goldens/macos/ai_chat_bottom_sheet_empty.png index d0a64a28..5db16b60 100644 Binary files a/test/presentation/ai_chat/goldens/macos/ai_chat_bottom_sheet_empty.png and b/test/presentation/ai_chat/goldens/macos/ai_chat_bottom_sheet_empty.png differ diff --git a/test/presentation/ai_chat/goldens/macos/ai_chat_bottom_sheet_error.png b/test/presentation/ai_chat/goldens/macos/ai_chat_bottom_sheet_error.png index a6a8c377..1be9b184 100644 Binary files a/test/presentation/ai_chat/goldens/macos/ai_chat_bottom_sheet_error.png and b/test/presentation/ai_chat/goldens/macos/ai_chat_bottom_sheet_error.png differ diff --git a/test/presentation/ai_chat/goldens/macos/ai_chat_bottom_sheet_with_messages.png b/test/presentation/ai_chat/goldens/macos/ai_chat_bottom_sheet_with_messages.png index 074897d4..6eb593e9 100644 Binary files a/test/presentation/ai_chat/goldens/macos/ai_chat_bottom_sheet_with_messages.png and b/test/presentation/ai_chat/goldens/macos/ai_chat_bottom_sheet_with_messages.png differ