From e1d688f981076b5155aa9dbf0618b46a374756cb Mon Sep 17 00:00:00 2001 From: AhmedAdelWafdy7 Date: Mon, 6 Apr 2026 02:51:39 +0000 Subject: [PATCH 01/12] fix: make all screens responsive --- .../camera/camera_linux/example/pubspec.lock | 22 +++++------ .../presentation/screens/discover_screen.dart | 4 +- .../screens/installed_screen.dart | 8 +++- .../widgets/appscreen_content.dart | 8 ++-- .../widgets/category_section.dart | 11 ++++-- .../widgets/flatpak_permission_dialog.dart | 9 +++-- .../widgets/glassmorphism/glass_button.dart | 13 ++++--- .../presentation/widgets/navigation_bar.dart | 38 +++++++++++-------- .../lib/presentation/widgets/splash.dart | 13 ++++--- .../lib/presentation/widgets/top_bar.dart | 9 +++-- 10 files changed, 80 insertions(+), 55 deletions(-) diff --git a/packages/camera/camera_linux/example/pubspec.lock b/packages/camera/camera_linux/example/pubspec.lock index aa50c3a..c23ea17 100644 --- a/packages/camera/camera_linux/example/pubspec.lock +++ b/packages/camera/camera_linux/example/pubspec.lock @@ -378,26 +378,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.dev" source: hosted - version: "10.0.9" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" logging: dependency: transitive description: @@ -631,10 +631,10 @@ packages: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.7.6" timing: dependency: transitive description: @@ -655,10 +655,10 @@ packages: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" video_player: dependency: "direct main" description: @@ -773,5 +773,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.7.0-0 <4.0.0" + dart: ">=3.8.0-0 <4.0.0" flutter: ">=3.22.0" diff --git a/packages/flatpak/example/lib/presentation/screens/discover_screen.dart b/packages/flatpak/example/lib/presentation/screens/discover_screen.dart index 5e39ebb..7d82732 100644 --- a/packages/flatpak/example/lib/presentation/screens/discover_screen.dart +++ b/packages/flatpak/example/lib/presentation/screens/discover_screen.dart @@ -274,7 +274,7 @@ class _DiscoverScreenState extends State { ), const SizedBox(height: 20), SizedBox( - height: 200, + height: Responsive.scaleWithConstraints(context, 200, minSize: 180, maxSize: 260), child: ListView.builder( scrollDirection: Axis.horizontal, physics: const BouncingScrollPhysics(), @@ -294,7 +294,7 @@ class _DiscoverScreenState extends State { Widget _buildFeaturedCard(Application app) { return SizedBox( - width: 300, + width: Responsive.scaleWithConstraints(context, 300, minSize: 260, maxSize: 380), child: ClipRRect( borderRadius: BorderRadius.circular(20), child: BackdropFilter( diff --git a/packages/flatpak/example/lib/presentation/screens/installed_screen.dart b/packages/flatpak/example/lib/presentation/screens/installed_screen.dart index 6d52e3d..6a18405 100644 --- a/packages/flatpak/example/lib/presentation/screens/installed_screen.dart +++ b/packages/flatpak/example/lib/presentation/screens/installed_screen.dart @@ -4,6 +4,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; +import '../../responsive.dart'; import '../../business_logic/app_launch/app_launch_cubit.dart'; import '../../business_logic/app_launch/app_launch_state.dart'; import '../../business_logic/app_status/app_status_cubit.dart'; @@ -186,7 +187,12 @@ class _InstalledScreenState extends State child: SafeArea( bottom: false, child: Container( - padding: const EdgeInsets.fromLTRB(24, 16, 24, 20), + padding: EdgeInsets.fromLTRB( + Responsive.scaleWithConstraints(context, 24, minSize: 16, maxSize: 40), + Responsive.scaleWithConstraints(context, 16, minSize: 12, maxSize: 24), + Responsive.scaleWithConstraints(context, 24, minSize: 16, maxSize: 40), + Responsive.scaleWithConstraints(context, 20, minSize: 12, maxSize: 28), + ), decoration: BoxDecoration( border: Border( bottom: BorderSide( diff --git a/packages/flatpak/example/lib/presentation/widgets/appscreen_content.dart b/packages/flatpak/example/lib/presentation/widgets/appscreen_content.dart index 89ccc27..fa0f922 100644 --- a/packages/flatpak/example/lib/presentation/widgets/appscreen_content.dart +++ b/packages/flatpak/example/lib/presentation/widgets/appscreen_content.dart @@ -11,7 +11,6 @@ import '../../business_logic/app_status/app_status_state.dart'; import '../../business_logic/installation/installation_cubit.dart'; import '../../business_logic/installation/installation_state.dart'; import '../../data/models/application_model.dart'; -import '../../helpers/id_utils.dart'; import 'package:flatpak_flutter_example/responsive.dart'; import 'package:flatpak_flutter_example/presentation/widgets/screenshot_widget.dart'; import 'app_info.dart'; @@ -59,9 +58,9 @@ class AppscreenContent extends StatelessWidget { filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), child: Container( width: double.infinity, - padding: const EdgeInsets.symmetric( - horizontal: 54, - vertical: 15, + padding: EdgeInsets.symmetric( + horizontal: Responsive.scaleWithConstraints(context, 54, minSize: 20, maxSize: 80), + vertical: Responsive.scaleWithConstraints(context, 15, minSize: 10, maxSize: 24), ), child: _buildAppHeader(context), ), @@ -740,7 +739,6 @@ class _GlassActionButton extends StatelessWidget { final textColor = isFilled ? Colors.white : effectiveColor; final showProgress = isLoading && progress != null && progress! > 0; - final showSpinner = isLoading && (progress == null || progress! == 0); return Material( color: Colors.transparent, diff --git a/packages/flatpak/example/lib/presentation/widgets/category_section.dart b/packages/flatpak/example/lib/presentation/widgets/category_section.dart index 4edb2c8..cfef5f8 100644 --- a/packages/flatpak/example/lib/presentation/widgets/category_section.dart +++ b/packages/flatpak/example/lib/presentation/widgets/category_section.dart @@ -26,7 +26,9 @@ class CategorySection extends StatelessWidget { return Column( children: [ Padding( - padding: const EdgeInsets.symmetric(horizontal: 32), + padding: EdgeInsets.symmetric( + horizontal: Responsive.scaleWithConstraints(context, 32, minSize: 16, maxSize: 64) + ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -54,8 +56,11 @@ class CategorySection extends StatelessWidget { ], ), ), - const SizedBox(height: 20), - SizedBox(height: 234, child: _buildContent(context)), + SizedBox(height: Responsive.scaleWithConstraints(context, 20, minSize: 10, maxSize: 30)), + SizedBox( + height: Responsive.scaleWithConstraints(context, 234, minSize: 220, maxSize: 280), + child: _buildContent(context), + ), ], ); } diff --git a/packages/flatpak/example/lib/presentation/widgets/flatpak_permission_dialog.dart b/packages/flatpak/example/lib/presentation/widgets/flatpak_permission_dialog.dart index 6e8398c..a36fcbd 100644 --- a/packages/flatpak/example/lib/presentation/widgets/flatpak_permission_dialog.dart +++ b/packages/flatpak/example/lib/presentation/widgets/flatpak_permission_dialog.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import '../../core/permissions/permission_types.dart'; import '../../data/models/flatpak_permission_model.dart'; +import '../../responsive.dart'; import 'glassmorphism/glass_button.dart'; import 'glassmorphism/glass_container.dart'; @@ -77,17 +78,17 @@ class _PermissionDialogContentState extends State<_PermissionDialogContent> child: Dialog( backgroundColor: Colors.transparent, elevation: 0, - insetPadding: const EdgeInsets.all(20), + insetPadding: EdgeInsets.all(Responsive.scaleWithConstraints(context, 20, minSize: 10, maxSize: 40)), child: GlassContainer( - borderRadius: 32, + borderRadius: Responsive.scaleWithConstraints(context, 32, minSize: 16, maxSize: 48), blur: 20, borderOpacity: 0.2, color: isDark ? Colors.black.withValues(alpha: 0.6) : Colors.white.withValues(alpha: 0.7), child: Container( - constraints: const BoxConstraints(maxWidth: 400), - padding: const EdgeInsets.all(32), + constraints: BoxConstraints(maxWidth: Responsive.scaleWithConstraints(context, 400, minSize: 300, maxSize: 600)), + padding: EdgeInsets.all(Responsive.scaleWithConstraints(context, 32, minSize: 16, maxSize: 48)), child: Column( mainAxisSize: MainAxisSize.min, children: [ diff --git a/packages/flatpak/example/lib/presentation/widgets/glassmorphism/glass_button.dart b/packages/flatpak/example/lib/presentation/widgets/glassmorphism/glass_button.dart index 5ff0ef3..e39d268 100644 --- a/packages/flatpak/example/lib/presentation/widgets/glassmorphism/glass_button.dart +++ b/packages/flatpak/example/lib/presentation/widgets/glassmorphism/glass_button.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import '../../../responsive.dart'; class GlassButton extends StatelessWidget { final VoidCallback onTap; @@ -27,7 +28,9 @@ class GlassButton extends StatelessWidget { return GestureDetector( onTap: onTap, child: Container( - padding: padding ?? const EdgeInsets.symmetric(horizontal: 20), + padding: padding ?? EdgeInsets.symmetric( + horizontal: Responsive.scaleWithConstraints(context, 20, minSize: 12, maxSize: 32), + ), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, @@ -37,7 +40,7 @@ class GlassButton extends StatelessWidget { glassColor.withValues(alpha: isPrimary ? 0.9 : 0.05), ], ), - borderRadius: BorderRadius.circular(16), + borderRadius: BorderRadius.circular(Responsive.scaleWithConstraints(context, 16, minSize: 8, maxSize: 24)), border: Border.all( color: borderColor, width: 1, @@ -56,15 +59,15 @@ class GlassButton extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ if (icon != null) ...[ - Icon(icon, size: 20, color: textColor), - if (label.isNotEmpty) const SizedBox(width: 8), + Icon(icon, size: Responsive.scaleWithConstraints(context, 20, minSize: 16, maxSize: 32), color: textColor), + if (label.isNotEmpty) SizedBox(width: Responsive.scaleWithConstraints(context, 8, minSize: 4, maxSize: 16)), ], if (label.isNotEmpty) Text( label, style: TextStyle( fontWeight: FontWeight.w600, - fontSize: 16, + fontSize: Responsive.scaleWithConstraints(context, 16, minSize: 12, maxSize: 24), color: textColor, letterSpacing: 0.3, ), diff --git a/packages/flatpak/example/lib/presentation/widgets/navigation_bar.dart b/packages/flatpak/example/lib/presentation/widgets/navigation_bar.dart index 50aec35..eb2c5bd 100644 --- a/packages/flatpak/example/lib/presentation/widgets/navigation_bar.dart +++ b/packages/flatpak/example/lib/presentation/widgets/navigation_bar.dart @@ -1,5 +1,6 @@ import 'dart:ui'; import 'package:flutter/material.dart'; +import '../../responsive.dart'; class NavigationBar extends StatefulWidget { final int currentIndex; @@ -70,11 +71,15 @@ class _NavigationBarState extends State @override Widget build(BuildContext context) { + final margin = Responsive.scaleWithConstraints(context, 24, minSize: 12, maxSize: 48); + final navHeight = Responsive.scaleWithConstraints(context, 80, minSize: 60, maxSize: 100); + final borderRadius = navHeight / 2; + return Container( - margin: const EdgeInsets.only(left: 24, right: 24, bottom: 24), - height: 80, + margin: EdgeInsets.only(left: margin, right: margin, bottom: margin), + height: navHeight, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(40), + borderRadius: BorderRadius.circular(borderRadius), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.1), @@ -84,13 +89,13 @@ class _NavigationBarState extends State ], ), child: ClipRRect( - borderRadius: BorderRadius.circular(40), + borderRadius: BorderRadius.circular(borderRadius), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20), child: Container( decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.3), - borderRadius: BorderRadius.circular(40), + borderRadius: BorderRadius.circular(borderRadius), border: Border.all( color: Colors.white.withValues(alpha: 0.3), width: 1.5, @@ -101,11 +106,11 @@ class _NavigationBarState extends State AnimatedPositioned( duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, - left: _calculateIndicatorPosition(), - top: 10, + left: _calculateIndicatorPosition(margin, navHeight), + top: (navHeight - (navHeight * 0.75)) / 2, child: Container( - width: 60, - height: 60, + width: navHeight * 0.75, + height: navHeight * 0.75, decoration: BoxDecoration( shape: BoxShape.circle, gradient: LinearGradient( @@ -132,7 +137,7 @@ class _NavigationBarState extends State mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: List.generate( widget.items.length, - (index) => _buildNavItem(index), + (index) => _buildNavItem(index, navHeight), ), ), ], @@ -143,13 +148,14 @@ class _NavigationBarState extends State ); } - double _calculateIndicatorPosition() { + double _calculateIndicatorPosition(double margin, double navHeight) { final screenWidth = MediaQuery.of(context).size.width; - final itemWidth = (screenWidth - 48) / widget.items.length; - return (itemWidth * widget.currentIndex) + (itemWidth / 2) - 30; + final itemWidth = (screenWidth - (margin * 2)) / widget.items.length; + final indicatorSize = navHeight * 0.75; + return (itemWidth * widget.currentIndex) + (itemWidth / 2) - (indicatorSize / 2); } - Widget _buildNavItem(int index) { + Widget _buildNavItem(int index, double navHeight) { final item = widget.items[index]; final isSelected = widget.currentIndex == index; @@ -168,11 +174,11 @@ class _NavigationBarState extends State child: Opacity( opacity: _opacityAnimations[index].value, child: SizedBox( - height: 80, + height: navHeight, child: Center( child: Icon( item.icon, - size: 28, + size: Responsive.scaleWithConstraints(context, 28, minSize: 20, maxSize: 40), color: isSelected ? Colors.black87 : Colors.black.withValues(alpha: 0.5), diff --git a/packages/flatpak/example/lib/presentation/widgets/splash.dart b/packages/flatpak/example/lib/presentation/widgets/splash.dart index 9a64160..7f01a69 100644 --- a/packages/flatpak/example/lib/presentation/widgets/splash.dart +++ b/packages/flatpak/example/lib/presentation/widgets/splash.dart @@ -181,10 +181,8 @@ class _SplashScreenState extends State setState(() => _loadingMessage = 'Organizing categories...'); await _loadAllCategories(); - int totalCategoryApps = 0; for (final entry in categories.entries) { final apps = context.read().getCategoryApps(entry.key); - totalCategoryApps += apps.length; debugPrint('[SplashScreen] ${entry.key}: ${apps.length} apps'); } // Small delay before transition @@ -263,8 +261,8 @@ class _SplashScreenState extends State opacity: _fadeAnimation, child: SvgPicture.asset( 'assets/logos/AGL.svg', - width: 168, - height: 36, + width: Responsive.scaleWithConstraints(context, 168, minSize: 100, maxSize: 300), + height: Responsive.scaleWithConstraints(context, 36, minSize: 24, maxSize: 72), ), ), ), @@ -337,7 +335,12 @@ class _SplashScreenState extends State child: Column( children: [ SizedBox( - width: 200, + width: Responsive.scaleWithConstraints( + context, + 200, + minSize: 150, + maxSize: 400, + ), child: LinearProgressIndicator( backgroundColor: Colors.grey[200], valueColor: AlwaysStoppedAnimation( diff --git a/packages/flatpak/example/lib/presentation/widgets/top_bar.dart b/packages/flatpak/example/lib/presentation/widgets/top_bar.dart index 61d979a..8c1f6f5 100644 --- a/packages/flatpak/example/lib/presentation/widgets/top_bar.dart +++ b/packages/flatpak/example/lib/presentation/widgets/top_bar.dart @@ -32,7 +32,10 @@ class AppTopBar extends StatelessWidget implements PreferredSizeWidget { child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), child: Container( - padding: EdgeInsets.symmetric(horizontal: 65, vertical: 33.0), + padding: EdgeInsets.symmetric( + horizontal: Responsive.scaleWithConstraints(context, 65, minSize: 16, maxSize: 100), + vertical: Responsive.scaleWithConstraints(context, 33, minSize: 12, maxSize: 40) + ), child: Row( children: [ _buildLogo(context), @@ -195,8 +198,8 @@ class AppTopBar extends StatelessWidget implements PreferredSizeWidget { ///onTap: , child: SvgPicture.asset( 'assets/icons/bell.svg', - width: 17.05, - height: 19.5, + width: Responsive.scaleWithConstraints(context, 17.05, minSize: 12, maxSize: 24), + height: Responsive.scaleWithConstraints(context, 19.5, minSize: 14, maxSize: 28), ), ), From 65f45fcc1e93d797735d866da5c193fa65e5a2c9 Mon Sep 17 00:00:00 2001 From: AhmedAdelWafdy7 Date: Mon, 6 Apr 2026 03:51:35 +0000 Subject: [PATCH 02/12] refactor: ensure dialog is in screen center --- .../widgets/flatpak_permission_dialog.dart | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/packages/flatpak/example/lib/presentation/widgets/flatpak_permission_dialog.dart b/packages/flatpak/example/lib/presentation/widgets/flatpak_permission_dialog.dart index a36fcbd..ef71e21 100644 --- a/packages/flatpak/example/lib/presentation/widgets/flatpak_permission_dialog.dart +++ b/packages/flatpak/example/lib/presentation/widgets/flatpak_permission_dialog.dart @@ -75,23 +75,24 @@ class _PermissionDialogContentState extends State<_PermissionDialogContent> scale: _scaleAnimation, child: FadeTransition( opacity: _fadeAnimation, - child: Dialog( - backgroundColor: Colors.transparent, - elevation: 0, - insetPadding: EdgeInsets.all(Responsive.scaleWithConstraints(context, 20, minSize: 10, maxSize: 40)), - child: GlassContainer( - borderRadius: Responsive.scaleWithConstraints(context, 32, minSize: 16, maxSize: 48), - blur: 20, - borderOpacity: 0.2, - color: isDark - ? Colors.black.withValues(alpha: 0.6) - : Colors.white.withValues(alpha: 0.7), - child: Container( - constraints: BoxConstraints(maxWidth: Responsive.scaleWithConstraints(context, 400, minSize: 300, maxSize: 600)), - padding: EdgeInsets.all(Responsive.scaleWithConstraints(context, 32, minSize: 16, maxSize: 48)), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ + child: Center( + child: Dialog( + backgroundColor: Colors.transparent, + elevation: 0, + insetPadding: EdgeInsets.all(Responsive.scaleWithConstraints(context, 20, minSize: 10, maxSize: 40)), + child: GlassContainer( + borderRadius: Responsive.scaleWithConstraints(context, 32, minSize: 16, maxSize: 48), + blur: 20, + borderOpacity: 0.2, + color: isDark + ? Colors.black.withValues(alpha: 0.6) + : Colors.white.withValues(alpha: 0.7), + child: Container( + constraints: BoxConstraints(maxWidth: Responsive.scaleWithConstraints(context, 400, minSize: 300, maxSize: 600)), + padding: EdgeInsets.all(Responsive.scaleWithConstraints(context, 32, minSize: 16, maxSize: 48)), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ _buildIconHeader(colorScheme, permission), const SizedBox(height: 28), @@ -154,6 +155,7 @@ class _PermissionDialogContentState extends State<_PermissionDialogContent> ), ), ), + ), ), ); } From e4dd11af9dafc94b1f12e3bad501118860cd3a57 Mon Sep 17 00:00:00 2001 From: AhmedAdelWafdy7 Date: Mon, 6 Apr 2026 04:38:30 +0000 Subject: [PATCH 03/12] fix: center splash screen, fix permission dialog overflow --- .../flatpak/example/devtools_options.yaml | 3 + .../widgets/flatpak_permission_dialog.dart | 13 +- .../lib/presentation/widgets/splash.dart | 280 ++++++++---------- 3 files changed, 141 insertions(+), 155 deletions(-) create mode 100644 packages/flatpak/example/devtools_options.yaml diff --git a/packages/flatpak/example/devtools_options.yaml b/packages/flatpak/example/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/packages/flatpak/example/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/packages/flatpak/example/lib/presentation/widgets/flatpak_permission_dialog.dart b/packages/flatpak/example/lib/presentation/widgets/flatpak_permission_dialog.dart index ef71e21..6ed3a14 100644 --- a/packages/flatpak/example/lib/presentation/widgets/flatpak_permission_dialog.dart +++ b/packages/flatpak/example/lib/presentation/widgets/flatpak_permission_dialog.dart @@ -88,11 +88,15 @@ class _PermissionDialogContentState extends State<_PermissionDialogContent> ? Colors.black.withValues(alpha: 0.6) : Colors.white.withValues(alpha: 0.7), child: Container( - constraints: BoxConstraints(maxWidth: Responsive.scaleWithConstraints(context, 400, minSize: 300, maxSize: 600)), + constraints: BoxConstraints( + maxWidth: Responsive.scaleWithConstraints(context, 400, minSize: 300, maxSize: 600), + maxHeight: MediaQuery.of(context).size.height * 0.85, + ), padding: EdgeInsets.all(Responsive.scaleWithConstraints(context, 32, minSize: 16, maxSize: 48)), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ _buildIconHeader(colorScheme, permission), const SizedBox(height: 28), @@ -152,6 +156,7 @@ class _PermissionDialogContentState extends State<_PermissionDialogContent> _buildActionButtons(colorScheme, isDark), ], ), + ), ), ), ), diff --git a/packages/flatpak/example/lib/presentation/widgets/splash.dart b/packages/flatpak/example/lib/presentation/widgets/splash.dart index 7f01a69..e5f5822 100644 --- a/packages/flatpak/example/lib/presentation/widgets/splash.dart +++ b/packages/flatpak/example/lib/presentation/widgets/splash.dart @@ -238,171 +238,149 @@ class _SplashScreenState extends State @override Widget build(BuildContext context) { return Scaffold( - body: Center( - child: AnimatedBuilder( - animation: Listenable.merge([_animationController, _fadeController]), - builder: (context, child) { - return Stack( - children: [ - SizedBox( + body: AnimatedBuilder( + animation: Listenable.merge([_animationController, _fadeController]), + builder: (context, child) { + return Stack( + children: [ + // Background gradient (behind everything) + Opacity( + opacity: _fadeToGradientAnimation.value, + child: Container( width: double.infinity, height: double.infinity, - child: Center( - child: Column( + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0xFF070E20), Color(0xFF1E3B86)], + ), + ), + ), + ), + + // Centered content (on top of gradient) + Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Row( mainAxisAlignment: MainAxisAlignment.center, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Transform.scale( - scale: _scaleAnimation.value, - child: FadeTransition( - opacity: _fadeAnimation, - child: SvgPicture.asset( - 'assets/logos/AGL.svg', - width: Responsive.scaleWithConstraints(context, 168, minSize: 100, maxSize: 300), - height: Responsive.scaleWithConstraints(context, 36, minSize: 24, maxSize: 72), + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Transform.scale( + scale: _scaleAnimation.value, + child: FadeTransition( + opacity: _fadeAnimation, + child: SvgPicture.asset( + 'assets/logos/AGL.svg', + width: Responsive.scaleWithConstraints(context, 168, minSize: 100, maxSize: 300), + height: Responsive.scaleWithConstraints(context, 36, minSize: 24, maxSize: 72), + ), + ), + ), + const SizedBox(width: 12), + SlideTransition( + position: _slideAnimation, + child: FadeTransition( + opacity: _fadeAnimation, + child: ShaderMask( + shaderCallback: (bounds) => + const LinearGradient( + colors: [Color(0x00000000), Color(0xFF33D17A)], + begin: Alignment.bottomRight, + end: Alignment.topLeft, + ).createShader(bounds), + child: Text( + 'Store', + style: TextStyle( + fontSize: Responsive.scaleWithConstraints(context, 40, minSize: 36, maxSize: 72), + fontWeight: FontWeight.w600, + color: Colors.white, + letterSpacing: -0.5, + height: 1.0, ), ), ), - const SizedBox(width: 12), - SlideTransition( - position: _slideAnimation, - child: FadeTransition( - opacity: _fadeAnimation, - child: ShaderMask( - shaderCallback: (bounds) => - const LinearGradient( - colors: [ - Color(0x00000000), - Color(0xFF33D17A), - ], - begin: Alignment.bottomRight, - end: Alignment.topLeft, - ).createShader(bounds), - child: Text( - 'Store', - style: TextStyle( - fontSize: Responsive.scaleWithConstraints( - context, - 40, - minSize: 36, - maxSize: 72, - ), - fontWeight: FontWeight.w600, - color: Colors.white, - letterSpacing: -0.5, - height: 1.0, - ), - ), - ), - ), - ), - ], - ), - const SizedBox(height: 16), - - // Subtitle - AnimatedBuilder( - animation: Listenable.merge([ - _subtitleSlideAnimation, - _subtitleFadeAnimation, - ]), - builder: (context, child) { - return SlideTransition( - position: _subtitleSlideAnimation, - child: Opacity( - opacity: _subtitleFadeAnimation.value, - child: Text( - 'AGL Store powered by Automotive Grade Linux', - style: TextStyle( - fontSize: 14, - color: Colors.black.withValues(alpha: 0.7), - fontWeight: FontWeight.w300, - letterSpacing: 0.5, - ), - ), - ), - ); - }, - ), - - const SizedBox(height: 32), - - FadeTransition( - opacity: _fadeAnimation, - child: Column( - children: [ - SizedBox( - width: Responsive.scaleWithConstraints( - context, - 200, - minSize: 150, - maxSize: 400, - ), - child: LinearProgressIndicator( - backgroundColor: Colors.grey[200], - valueColor: AlwaysStoppedAnimation( - _hasError - ? Colors.red - : const Color(0xFF33D17A), - ), - ), - ), - const SizedBox(height: 16), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 40), - child: Text( - _hasError ? _errorMessage : _loadingMessage, - style: TextStyle( - fontSize: 12, - color: _hasError - ? Colors.red - : Colors.black.withValues(alpha: 0.5), - fontWeight: FontWeight.w300, - ), - textAlign: TextAlign.center, - ), - ), - if (_hasError && _retryCount < 3) - Padding( - padding: const EdgeInsets.only(top: 8), - child: Text( - 'Retrying... (attempt $_retryCount/3)', - style: TextStyle( - fontSize: 11, - color: Colors.orange.withValues(alpha: 0.7), - fontWeight: FontWeight.w400, - ), - ), - ), - ], ), ), ], ), - ), - ), + const SizedBox(height: 16), - Opacity( - opacity: _fadeToGradientAnimation.value, - child: Container( - width: double.infinity, - height: double.infinity, - decoration: const BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [Color(0xFF070E20), Color(0xFF1E3B86)], + // Subtitle + AnimatedBuilder( + animation: Listenable.merge([_subtitleSlideAnimation, _subtitleFadeAnimation]), + builder: (context, child) { + return SlideTransition( + position: _subtitleSlideAnimation, + child: Opacity( + opacity: _subtitleFadeAnimation.value, + child: Text( + 'AGL Store powered by Automotive Grade Linux', + style: TextStyle( + fontSize: 14, + color: Colors.black.withValues(alpha: 0.7), + fontWeight: FontWeight.w300, + letterSpacing: 0.5, + ), + ), + ), + ); + }, + ), + + const SizedBox(height: 32), + + FadeTransition( + opacity: _fadeAnimation, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: Responsive.scaleWithConstraints(context, 200, minSize: 150, maxSize: 400), + child: LinearProgressIndicator( + backgroundColor: Colors.grey[200], + valueColor: AlwaysStoppedAnimation( + _hasError ? Colors.red : const Color(0xFF33D17A), + ), + ), + ), + const SizedBox(height: 16), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 40), + child: Text( + _hasError ? _errorMessage : _loadingMessage, + style: TextStyle( + fontSize: 12, + color: _hasError ? Colors.red : Colors.black.withValues(alpha: 0.5), + fontWeight: FontWeight.w300, + ), + textAlign: TextAlign.center, + ), + ), + if (_hasError && _retryCount < 3) + Padding( + padding: const EdgeInsets.only(top: 8), + child: Text( + 'Retrying... (attempt $_retryCount/3)', + style: TextStyle( + fontSize: 11, + color: Colors.orange.withValues(alpha: 0.7), + fontWeight: FontWeight.w400, + ), + ), + ), + ], ), ), - ), + ], ), - ], - ); - }, - ), + ), + ], + ); + }, ), ); } From 3904709802665b5fbd10985c6eac7516d08f6ba1 Mon Sep 17 00:00:00 2001 From: AhmedAdelWafdy7 Date: Mon, 6 Apr 2026 04:53:52 +0000 Subject: [PATCH 04/12] =?UTF-8?q?fix:=20fix=20splash=20to=20follow=20refer?= =?UTF-8?q?rence=201024=C3=97768?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/presentation/widgets/splash.dart | 26 +-- packages/flatpak/example/lib/responsive.dart | 154 +++++++++++++----- 2 files changed, 122 insertions(+), 58 deletions(-) diff --git a/packages/flatpak/example/lib/presentation/widgets/splash.dart b/packages/flatpak/example/lib/presentation/widgets/splash.dart index e5f5822..7f34b97 100644 --- a/packages/flatpak/example/lib/presentation/widgets/splash.dart +++ b/packages/flatpak/example/lib/presentation/widgets/splash.dart @@ -275,12 +275,12 @@ class _SplashScreenState extends State opacity: _fadeAnimation, child: SvgPicture.asset( 'assets/logos/AGL.svg', - width: Responsive.scaleWithConstraints(context, 168, minSize: 100, maxSize: 300), - height: Responsive.scaleWithConstraints(context, 36, minSize: 24, maxSize: 72), + width: Responsive.scale(context, 168), + height: Responsive.scale(context, 36), ), ), ), - const SizedBox(width: 12), + Responsive.hGap(context, 12), SlideTransition( position: _slideAnimation, child: FadeTransition( @@ -295,7 +295,7 @@ class _SplashScreenState extends State child: Text( 'Store', style: TextStyle( - fontSize: Responsive.scaleWithConstraints(context, 40, minSize: 36, maxSize: 72), + fontSize: Responsive.fontSize(context, 40), fontWeight: FontWeight.w600, color: Colors.white, letterSpacing: -0.5, @@ -307,7 +307,7 @@ class _SplashScreenState extends State ), ], ), - const SizedBox(height: 16), + Responsive.vGap(context, 16), // Subtitle AnimatedBuilder( @@ -320,7 +320,7 @@ class _SplashScreenState extends State child: Text( 'AGL Store powered by Automotive Grade Linux', style: TextStyle( - fontSize: 14, + fontSize: Responsive.fontSize(context, 14), color: Colors.black.withValues(alpha: 0.7), fontWeight: FontWeight.w300, letterSpacing: 0.5, @@ -331,7 +331,7 @@ class _SplashScreenState extends State }, ), - const SizedBox(height: 32), + Responsive.vGap(context, 32), FadeTransition( opacity: _fadeAnimation, @@ -339,7 +339,7 @@ class _SplashScreenState extends State mainAxisSize: MainAxisSize.min, children: [ SizedBox( - width: Responsive.scaleWithConstraints(context, 200, minSize: 150, maxSize: 400), + width: Responsive.scale(context, 200), child: LinearProgressIndicator( backgroundColor: Colors.grey[200], valueColor: AlwaysStoppedAnimation( @@ -347,13 +347,13 @@ class _SplashScreenState extends State ), ), ), - const SizedBox(height: 16), + Responsive.vGap(context, 16), Padding( - padding: const EdgeInsets.symmetric(horizontal: 40), + padding: Responsive.paddingSymmetric(context, horizontal: 40), child: Text( _hasError ? _errorMessage : _loadingMessage, style: TextStyle( - fontSize: 12, + fontSize: Responsive.fontSize(context, 12), color: _hasError ? Colors.red : Colors.black.withValues(alpha: 0.5), fontWeight: FontWeight.w300, ), @@ -362,11 +362,11 @@ class _SplashScreenState extends State ), if (_hasError && _retryCount < 3) Padding( - padding: const EdgeInsets.only(top: 8), + padding: EdgeInsets.only(top: Responsive.scale(context, 8)), child: Text( 'Retrying... (attempt $_retryCount/3)', style: TextStyle( - fontSize: 11, + fontSize: Responsive.fontSize(context, 11), color: Colors.orange.withValues(alpha: 0.7), fontWeight: FontWeight.w400, ), diff --git a/packages/flatpak/example/lib/responsive.dart b/packages/flatpak/example/lib/responsive.dart index 02f03d5..a42985b 100644 --- a/packages/flatpak/example/lib/responsive.dart +++ b/packages/flatpak/example/lib/responsive.dart @@ -1,69 +1,89 @@ import 'package:flutter/material.dart'; class Responsive { - // Design breakpoints - static const double designWidth = 1440.0; - static const double mobileBreakpoint = 768.0; - static const double tabletBreakpoint = 1024.0; + static const double _designWidth = 1024.0; + static const double _designHeight = 768.0; - static bool isMobile(BuildContext context) => - MediaQuery.of(context).size.width < mobileBreakpoint; + static const double _smallBreakpoint = 600.0; // small embedded / phone + static const double _mediumBreakpoint = 1024.0; // tablet / RPi screen + static const double _largeBreakpoint = 1440.0; // desktop + // anything above 1440 = xl (4K, large monitors) - static bool isTablet(BuildContext context) => - MediaQuery.of(context).size.width >= mobileBreakpoint && - MediaQuery.of(context).size.width < tabletBreakpoint; + static bool isSmall(BuildContext context) => + _w(context) < _smallBreakpoint; - static bool isDesktop(BuildContext context) => - MediaQuery.of(context).size.width >= tabletBreakpoint; + static bool isMedium(BuildContext context) => + _w(context) >= _smallBreakpoint && _w(context) < _mediumBreakpoint; - static double width(BuildContext context) => - MediaQuery.of(context).size.width; + static bool isLarge(BuildContext context) => + _w(context) >= _mediumBreakpoint && _w(context) < _largeBreakpoint; - static double height(BuildContext context) => - MediaQuery.of(context).size.height; + static bool isXL(BuildContext context) => + _w(context) >= _largeBreakpoint; + static bool isMobile(BuildContext context) => isSmall(context); + static bool isTablet(BuildContext context) => isMedium(context); + static bool isDesktop(BuildContext context) => isLarge(context) || isXL(context); + + static double width(BuildContext context) => _w(context); + static double height(BuildContext context) => _h(context); + + static double widthFraction(BuildContext context, double fraction) => + _w(context) * fraction; + + static double heightFraction(BuildContext context, double fraction) => + _h(context) * fraction; + + /// Returns the value matching the current screen category. + /// Falls back to the next smaller value if a larger one is not provided. static T responsive( BuildContext context, { - required T mobile, - T? tablet, - T? desktop, + required T small, + T? medium, + T? large, + T? xl, }) { - if (isDesktop(context) && desktop != null) return desktop; - if (isTablet(context) && tablet != null) return tablet; - return mobile; + if (isXL(context) && xl != null) return xl; + if (isLarge(context) && large != null) return large; + if (isMedium(context) && medium != null) return medium; + return small; } static double responsiveValue( BuildContext context, { - required double mobile, - double? tablet, - double? desktop, - }) { - return responsive( - context, - mobile: mobile, - tablet: tablet, - desktop: desktop, - ); - } + required double small, + double? medium, + double? large, + double? xl, + }) => + responsive(context, small: small, medium: medium, large: large, xl: xl); - // Scale a dimension from Figma design to current screen + /// Scales [figmaSize] (designed at 1024×768) to the current screen. + /// + /// Uses the geometric mean of the width and height ratios so the result + /// feels correct in both axes — no overflow vertically on wide screens, + /// no tiny text on tall narrow screens. static double scale(BuildContext context, double figmaSize) { - final screenWidth = MediaQuery.of(context).size.width; - final scaleFactor = screenWidth / designWidth; + final wRatio = _w(context) / _designWidth; + final hRatio = _h(context) / _designHeight; - if (isMobile(context)) { - return (figmaSize * scaleFactor * 1.2).clamp( - figmaSize * 0.6, - figmaSize * 1.2, - ); - } else if (isTablet(context)) { - return (figmaSize * scaleFactor).clamp(figmaSize * 0.8, figmaSize * 1.1); + // Geometric mean keeps proportions balanced across wildly different aspects + final scale = _sqrt(wRatio * hRatio); + + // Per-category clamping so things stay sensible at extremes + if (isSmall(context)) { + return (figmaSize * scale).clamp(figmaSize * 0.45, figmaSize * 0.85); + } else if (isMedium(context)) { + return (figmaSize * scale).clamp(figmaSize * 0.70, figmaSize * 1.10); + } else if (isLarge(context)) { + return (figmaSize * scale).clamp(figmaSize * 0.90, figmaSize * 1.40); } else { - return (figmaSize * scaleFactor).clamp(figmaSize * 0.9, figmaSize * 1.3); + // XL / 4K + return (figmaSize * scale).clamp(figmaSize * 1.10, figmaSize * 2.00); } } + /// Same as [scale] but lets call sites override the min/max clamp. static double scaleWithConstraints( BuildContext context, double figmaSize, { @@ -71,6 +91,50 @@ class Responsive { double? maxSize, }) { final scaled = scale(context, figmaSize); - return scaled.clamp(minSize ?? figmaSize * 0.5, maxSize ?? figmaSize * 2.0); + return scaled.clamp( + minSize ?? figmaSize * 0.45, + maxSize ?? figmaSize * 2.00, + ); } -} + + /// Like [scale] but never exceeds the system text-scale factor. + /// Use this for font sizes so they respect accessibility settings. + static double fontSize(BuildContext context, double figmaFontSize) { + final textScale = MediaQuery.of(context).textScaler.scale(1.0); + final base = scale(context, figmaFontSize); + return base / textScale; // let Flutter re-apply text scale naturally + } + + /// Scaled SizedBox height gap — convenience shorthand. + static Widget vGap(BuildContext context, double figmaGap) => + SizedBox(height: scale(context, figmaGap)); + + /// Scaled SizedBox width gap — convenience shorthand. + static Widget hGap(BuildContext context, double figmaGap) => + SizedBox(width: scale(context, figmaGap)); + + /// Scaled EdgeInsets.all + static EdgeInsets paddingAll(BuildContext context, double figmaPadding) => + EdgeInsets.all(scale(context, figmaPadding)); + + /// Scaled EdgeInsets.symmetric + static EdgeInsets paddingSymmetric( + BuildContext context, { + double vertical = 0, + double horizontal = 0, + }) => + EdgeInsets.symmetric( + vertical: scale(context, vertical), + horizontal: scale(context, horizontal), + ); + + static double dialogMaxWidth(BuildContext context) => + (_w(context) * 0.90).clamp(280, 640); + + static double dialogMaxHeight(BuildContext context) => + _h(context) * 0.85; + + static double _w(BuildContext context) => MediaQuery.of(context).size.width; + static double _h(BuildContext context) => MediaQuery.of(context).size.height; + static double _sqrt(double x) => x <= 0 ? 1.0 : x < 1 ? x + (1 - x) * 0.5 : x * 0.5 + 0.5; +} \ No newline at end of file From e26258ea5522a7278fc5f96a7fe0b62c42c38477 Mon Sep 17 00:00:00 2001 From: AhmedAdelWafdy7 Date: Mon, 6 Apr 2026 05:09:51 +0000 Subject: [PATCH 05/12] =?UTF-8?q?fix:=20fix=20widgets=20to=20follow=20refe?= =?UTF-8?q?rrence=201024=C3=97768?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/presentation/screens/home_screen.dart | 9 +- .../lib/presentation/widgets/app_card.dart | 60 +++----- .../lib/presentation/widgets/app_info.dart | 25 ++-- .../widgets/appscreen_content.dart | 82 +++++------ .../presentation/widgets/navigation_bar.dart | 6 +- .../widgets/screenshot_widget.dart | 69 ++------- .../lib/presentation/widgets/top_bar.dart | 131 +++++++----------- 7 files changed, 150 insertions(+), 232 deletions(-) diff --git a/packages/flatpak/example/lib/presentation/screens/home_screen.dart b/packages/flatpak/example/lib/presentation/screens/home_screen.dart index fdc3acb..6927409 100644 --- a/packages/flatpak/example/lib/presentation/screens/home_screen.dart +++ b/packages/flatpak/example/lib/presentation/screens/home_screen.dart @@ -12,6 +12,7 @@ import '../widgets/category_section.dart'; import '../widgets/hero_widget.dart'; import '../widgets/top_bar.dart'; import '../../app_router.dart'; +import '../../responsive.dart'; class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); @@ -88,10 +89,10 @@ class _HomeScreenState extends State child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon(Icons.error, size: 64, color: Colors.red), - const SizedBox(height: 16), + Icon(Icons.error, size: Responsive.scale(context, 64), color: Colors.red), + Responsive.vGap(context, 16), Text('Error: ${state.message}'), - const SizedBox(height: 16), + Responsive.vGap(context, 16), ElevatedButton( onPressed: _loadData, child: const Text('Retry'), @@ -138,7 +139,7 @@ class _HomeScreenState extends State onTap: (app) => _navigateToApp(app, context), onInstall: (app) => _handleInstall(app, context), ), - const SizedBox(height: 32), + Responsive.vGap(context, 32), ], ); }, diff --git a/packages/flatpak/example/lib/presentation/widgets/app_card.dart b/packages/flatpak/example/lib/presentation/widgets/app_card.dart index 29e2e17..04ef16b 100644 --- a/packages/flatpak/example/lib/presentation/widgets/app_card.dart +++ b/packages/flatpak/example/lib/presentation/widgets/app_card.dart @@ -86,19 +86,8 @@ class _AppCardState extends State with TickerProviderStateMixin { @override Widget build(BuildContext context) { - final cardWidth = Responsive.scaleWithConstraints( - context, - 220, - minSize: 200, - maxSize: 250, - ); - - final cardHeight = Responsive.scaleWithConstraints( - context, - 220, - minSize: 200, - maxSize: 250, - ); + final cardWidth = Responsive.scale(context, 220); + final cardHeight = Responsive.scale(context, 220); return AnimatedBuilder( animation: _hoverController, @@ -113,11 +102,9 @@ class _AppCardState extends State with TickerProviderStateMixin { child: Container( width: cardWidth, height: cardHeight, - margin: EdgeInsets.all( - Responsive.scale(context, 12.0).clamp(10.0, 16.0), - ), + margin: Responsive.paddingAll(context, 12.0), decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), + borderRadius: BorderRadius.circular(Responsive.scale(context, 16)), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.1), @@ -127,7 +114,7 @@ class _AppCardState extends State with TickerProviderStateMixin { ], ), child: ClipRRect( - borderRadius: BorderRadius.circular(16), + borderRadius: BorderRadius.circular(Responsive.scale(context, 16)), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), child: Container( @@ -135,7 +122,7 @@ class _AppCardState extends State with TickerProviderStateMixin { color: _isHovered ? Colors.white.withValues(alpha: 0.35) : Colors.white.withValues(alpha: 0.25), - borderRadius: BorderRadius.circular(16), + borderRadius: BorderRadius.circular(Responsive.scale(context, 16)), border: Border.all( color: _isHovered ? Colors.white.withValues(alpha: 0.4) @@ -144,9 +131,7 @@ class _AppCardState extends State with TickerProviderStateMixin { ), ), child: Padding( - padding: EdgeInsets.all( - Responsive.scale(context, 12.0).clamp(10.0, 16.0), - ), + padding: Responsive.paddingAll(context, 12.0), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -168,12 +153,12 @@ class _AppCardState extends State with TickerProviderStateMixin { } Widget _buildAppIcon(BuildContext context, Application app) { - final iconSize = Responsive.scale(context, 92).clamp(64.0, 128.0); + final iconSize = Responsive.scale(context, 92); return Container( width: iconSize, height: iconSize, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(Responsive.scale(context, 12)), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.1), @@ -183,7 +168,7 @@ class _AppCardState extends State with TickerProviderStateMixin { ], ), child: ClipRRect( - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(Responsive.scale(context, 12)), child: _buildIcon(iconSize), ), ); @@ -238,19 +223,19 @@ class _AppCardState extends State with TickerProviderStateMixin { _getAppName(app), style: Theme.of(context).textTheme.titleLarge?.copyWith( fontWeight: FontWeight.bold, - fontSize: Responsive.scale(context, 20.0).clamp(16.0, 24.0), + fontSize: Responsive.fontSize(context, 20.0), fontFamily: 'khand', ), maxLines: 1, overflow: TextOverflow.ellipsis, ), - const SizedBox(height: 4), + Responsive.vGap(context, 4), Text( _getAppSummary(app), style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).textTheme.bodyMedium?.color ?.withValues(alpha: 0.7), - fontSize: Responsive.scale(context, 12.0).clamp(10.0, 14.0), + fontSize: Responsive.fontSize(context, 12.0), fontFamily: 'general-sans', ), maxLines: 1, @@ -259,10 +244,10 @@ class _AppCardState extends State with TickerProviderStateMixin { ], ), ), - const SizedBox(width: 8), + Responsive.hGap(context, 8), ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 130), + constraints: BoxConstraints(maxWidth: Responsive.scale(context, 130)), child: _buildActionButton(context), ), ], @@ -301,18 +286,19 @@ class _AppCardState extends State with TickerProviderStateMixin { ? () => _handleButtonTap(context, appStatus) : null, child: ClipRRect( - borderRadius: BorderRadius.circular(20), + borderRadius: BorderRadius.circular(Responsive.scale(context, 20)), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), child: AnimatedContainer( duration: const Duration(milliseconds: 200), - padding: const EdgeInsets.symmetric( + padding: Responsive.paddingSymmetric( + context, horizontal: 16, vertical: 8, ), decoration: BoxDecoration( color: config.backgroundColor, - borderRadius: BorderRadius.circular(20), + borderRadius: BorderRadius.circular(Responsive.scale(context, 20)), border: Border.all( width: 1.5, color: Colors.white.withValues(alpha: 0.3), @@ -329,8 +315,8 @@ class _AppCardState extends State with TickerProviderStateMixin { return Transform.rotate( angle: _rotationAnimation.value * 2.0 * 3.141592653589793, child: SizedBox( - width: 14, - height: 14, + width: Responsive.scale(context, 14), + height: Responsive.scale(context, 14), child: CircularProgressIndicator( value: progress != null && progress > 0 ? progress : null, strokeWidth: 2, @@ -342,7 +328,7 @@ class _AppCardState extends State with TickerProviderStateMixin { ); }, ), - const SizedBox(width: 8), + Responsive.hGap(context, 8), ], Flexible( child: Text( @@ -351,7 +337,7 @@ class _AppCardState extends State with TickerProviderStateMixin { color: config.textColor, fontWeight: FontWeight.w500, fontFamily: 'general-sans', - fontSize: 14, + fontSize: Responsive.fontSize(context, 14), ), maxLines: 1, overflow: TextOverflow.ellipsis, diff --git a/packages/flatpak/example/lib/presentation/widgets/app_info.dart b/packages/flatpak/example/lib/presentation/widgets/app_info.dart index d71be21..90b72ca 100644 --- a/packages/flatpak/example/lib/presentation/widgets/app_info.dart +++ b/packages/flatpak/example/lib/presentation/widgets/app_info.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import '../../responsive.dart'; class AppInfo extends StatelessWidget { const AppInfo({ @@ -27,7 +28,7 @@ class AppInfo extends StatelessWidget { Expanded( flex: 1, child: Padding( - padding: const EdgeInsets.all(8.0), + padding: Responsive.paddingAll(context, 8.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -43,7 +44,7 @@ class AppInfo extends StatelessWidget { fontFamily: 'general-sans', ), ), - const SizedBox(height: 4), + Responsive.vGap(context, 4), Text( version, style: Theme.of(context).textTheme.bodyMedium?.copyWith( @@ -54,7 +55,7 @@ class AppInfo extends StatelessWidget { ), ], ), - const SizedBox(height: 16), + Responsive.vGap(context, 16), /// Size Column( @@ -68,7 +69,7 @@ class AppInfo extends StatelessWidget { fontFamily: 'general-sans', ), ), - const SizedBox(height: 4), + Responsive.vGap(context, 4), Text( size, style: Theme.of(context).textTheme.bodyMedium?.copyWith( @@ -79,7 +80,7 @@ class AppInfo extends StatelessWidget { ), ], ), - const SizedBox(height: 16), + Responsive.vGap(context, 16), /// Last Update Column( @@ -93,7 +94,7 @@ class AppInfo extends StatelessWidget { fontFamily: 'general-sans', ), ), - const SizedBox(height: 4), + Responsive.vGap(context, 4), Text( last_upadate, style: Theme.of(context).textTheme.bodyMedium?.copyWith( @@ -115,7 +116,7 @@ class AppInfo extends StatelessWidget { Expanded( flex: 1, child: Padding( - padding: const EdgeInsets.all(8.0), + padding: Responsive.paddingAll(context, 8.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -131,7 +132,7 @@ class AppInfo extends StatelessWidget { fontFamily: 'general-sans', ), ), - const SizedBox(height: 4), + Responsive.vGap(context, 4), Text( License, style: Theme.of(context).textTheme.bodyMedium?.copyWith( @@ -143,7 +144,7 @@ class AppInfo extends StatelessWidget { ], ), - const SizedBox(height: 16), + Responsive.vGap(context, 16), /// Developer Column( @@ -157,7 +158,7 @@ class AppInfo extends StatelessWidget { fontFamily: 'general-sans', ), ), - const SizedBox(height: 4), + Responsive.vGap(context, 4), GestureDetector( /// onTap: onTap, TODO: Add UrlLauncher child: Text( @@ -173,7 +174,7 @@ class AppInfo extends StatelessWidget { ), ], ), - const SizedBox(height: 16), + Responsive.vGap(context, 16), /// Content Rating Column( @@ -187,7 +188,7 @@ class AppInfo extends StatelessWidget { fontFamily: 'general-sans', ), ), - const SizedBox(height: 4), + Responsive.vGap(context, 4), Text( content_rating, style: Theme.of(context).textTheme.bodyMedium?.copyWith( diff --git a/packages/flatpak/example/lib/presentation/widgets/appscreen_content.dart b/packages/flatpak/example/lib/presentation/widgets/appscreen_content.dart index fa0f922..3f901e8 100644 --- a/packages/flatpak/example/lib/presentation/widgets/appscreen_content.dart +++ b/packages/flatpak/example/lib/presentation/widgets/appscreen_content.dart @@ -58,9 +58,10 @@ class AppscreenContent extends StatelessWidget { filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), child: Container( width: double.infinity, - padding: EdgeInsets.symmetric( - horizontal: Responsive.scaleWithConstraints(context, 54, minSize: 20, maxSize: 80), - vertical: Responsive.scaleWithConstraints(context, 15, minSize: 10, maxSize: 24), + padding: Responsive.paddingSymmetric( + context, + horizontal: 54, + vertical: 15, ), child: _buildAppHeader(context), ), @@ -68,11 +69,11 @@ class AppscreenContent extends StatelessWidget { ), ), - const SizedBox(height: 18), + Responsive.vGap(context, 18), _buildScreenshot(context), - const SizedBox(height: 18), + Responsive.vGap(context, 18), _buildInfo(context), - const SizedBox(height: 40), + Responsive.vGap(context, 40), ], ), ); @@ -94,12 +95,12 @@ class AppscreenContent extends StatelessWidget { return Container( width: double.infinity, - padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 6.0), + padding: Responsive.paddingSymmetric(context, horizontal: 20.0, vertical: 6.0), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ _buildAppIcon(context, app), - const SizedBox(width: 14), + Responsive.hGap(context, 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -109,7 +110,7 @@ class AppscreenContent extends StatelessWidget { app.name, style: TextStyle( color: Colors.black87, - fontSize: Responsive.scale(context, 18.0).clamp(15.0, 20.0), + fontSize: Responsive.fontSize(context, 18.0), fontWeight: FontWeight.w600, fontFamily: 'khand', height: 1.2, @@ -117,24 +118,23 @@ class AppscreenContent extends StatelessWidget { maxLines: 2, overflow: TextOverflow.ellipsis, ), - const SizedBox(height: 5), + Responsive.vGap(context, 5), if (developerName.isNotEmpty) Text( developerName, style: TextStyle( color: const Color(0xFF8B8B8B), - fontSize: - Responsive.scale(context, 13.0).clamp(11.0, 15.0), + fontSize: Responsive.fontSize(context, 13.0), height: 1.1, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), if (categories.isNotEmpty) ...[ - const SizedBox(height: 8), + Responsive.vGap(context, 8), ConstrainedBox( constraints: BoxConstraints( - maxWidth: MediaQuery.of(context).size.width - 200, + maxWidth: Responsive.width(context) - Responsive.scale(context, 200), ), child: Wrap( spacing: 6, @@ -146,7 +146,7 @@ class AppscreenContent extends StatelessWidget { ], ), ), - const SizedBox(width: 16), + Responsive.hGap(context, 16), _buildActionButtons(context), ], ), @@ -170,7 +170,7 @@ class AppscreenContent extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ _buildOpenButton(context), - const SizedBox(width: 12), + Responsive.hGap(context, 12), if (needsUpdate || status == AppStatus.updating) _buildUpdateButton(context) else @@ -288,14 +288,14 @@ class AppscreenContent extends StatelessWidget { } Widget _buildAppIcon(BuildContext context, Application app) { - final cardSize = Responsive.scale(context, 80).clamp(68.0, 92.0); + final cardSize = Responsive.scale(context, 80); final iconSize = cardSize * 0.7; return Container( width: cardSize, height: cardSize, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), + borderRadius: BorderRadius.circular(Responsive.scale(context, 16)), color: Colors.white.withValues(alpha: 0.7), boxShadow: [ BoxShadow( @@ -307,7 +307,7 @@ class AppscreenContent extends StatelessWidget { ), child: Center( child: ClipRRect( - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(Responsive.scale(context, 12)), child: _buildIcon(iconSize), ), ), @@ -364,13 +364,13 @@ class AppscreenContent extends StatelessWidget { width: double.infinity, height: Responsive.responsiveValue( context, - mobile: Responsive.height(context) * 0.3, - tablet: Responsive.height(context) * 0.4, - desktop: Responsive.height(context) * 0.5, + small: Responsive.height(context) * 0.3, + medium: Responsive.height(context) * 0.4, + large: Responsive.height(context) * 0.5, ).clamp(300.0, 600.0), - padding: EdgeInsets.symmetric( - horizontal: Responsive.responsiveValue(context, - mobile: 16.0, tablet: 24.0, desktop: 32.0), + padding: Responsive.paddingSymmetric( + context, + horizontal: 32.0, vertical: 16.0, ), child: Screenshot(images: images, captions: captions), @@ -387,7 +387,7 @@ class AppscreenContent extends StatelessWidget { return Container( width: double.infinity, - padding: const EdgeInsets.symmetric(horizontal: 40.0, vertical: 10.0), + padding: Responsive.paddingSymmetric(context, horizontal: 40.0, vertical: 10.0), alignment: Alignment.topLeft, decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.25), @@ -409,40 +409,40 @@ class AppscreenContent extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( + Text( "About this app", style: TextStyle( color: Colors.black87, - fontSize: 18, + fontSize: Responsive.fontSize(context, 18), fontWeight: FontWeight.w500, fontFamily: 'general-sans', ), ), - const SizedBox(height: 30), + Responsive.vGap(context, 30), Text( description.isNotEmpty ? description : "No description available.", - style: const TextStyle( - color: Color(0xFF4B5563), - fontSize: 16, + style: TextStyle( + color: const Color(0xFF4B5563), + fontSize: Responsive.fontSize(context, 16), fontWeight: FontWeight.normal, fontFamily: 'general-sans', ), ), - const SizedBox(height: 30), + Responsive.vGap(context, 30), Container( width: double.infinity, height: 1, color: Colors.grey.shade300, ), - const Padding( - padding: EdgeInsets.all(12.0), + Padding( + padding: Responsive.paddingAll(context, 12.0), child: Text( "App Info", style: TextStyle( color: Colors.black87, - fontSize: 18, + fontSize: Responsive.fontSize(context, 18), fontWeight: FontWeight.w500, fontFamily: 'general-sans', ), @@ -578,17 +578,17 @@ class AppscreenContent extends StatelessWidget { return categories.take(3).where((c) => c != null).map((category) { return Container( - padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), + padding: Responsive.paddingSymmetric(context, horizontal: 6, vertical: 2), decoration: BoxDecoration( color: Colors.grey.shade100, - borderRadius: BorderRadius.circular(4), + borderRadius: BorderRadius.circular(Responsive.scale(context, 4)), border: Border.all(color: Colors.grey.shade300, width: 0.5), ), child: Text( category.toString().trim(), - style: const TextStyle( - fontSize: 11, - color: Color(0xFF374151), + style: TextStyle( + fontSize: Responsive.fontSize(context, 11), + color: const Color(0xFF374151), fontWeight: FontWeight.w400, ), ), diff --git a/packages/flatpak/example/lib/presentation/widgets/navigation_bar.dart b/packages/flatpak/example/lib/presentation/widgets/navigation_bar.dart index eb2c5bd..c49ffe4 100644 --- a/packages/flatpak/example/lib/presentation/widgets/navigation_bar.dart +++ b/packages/flatpak/example/lib/presentation/widgets/navigation_bar.dart @@ -71,8 +71,8 @@ class _NavigationBarState extends State @override Widget build(BuildContext context) { - final margin = Responsive.scaleWithConstraints(context, 24, minSize: 12, maxSize: 48); - final navHeight = Responsive.scaleWithConstraints(context, 80, minSize: 60, maxSize: 100); + final margin = Responsive.scale(context, 24); + final navHeight = Responsive.scale(context, 80); final borderRadius = navHeight / 2; return Container( @@ -178,7 +178,7 @@ class _NavigationBarState extends State child: Center( child: Icon( item.icon, - size: Responsive.scaleWithConstraints(context, 28, minSize: 20, maxSize: 40), + size: Responsive.scale(context, 28), color: isSelected ? Colors.black87 : Colors.black.withValues(alpha: 0.5), diff --git a/packages/flatpak/example/lib/presentation/widgets/screenshot_widget.dart b/packages/flatpak/example/lib/presentation/widgets/screenshot_widget.dart index b808bca..911974d 100644 --- a/packages/flatpak/example/lib/presentation/widgets/screenshot_widget.dart +++ b/packages/flatpak/example/lib/presentation/widgets/screenshot_widget.dart @@ -28,22 +28,13 @@ class _screenshotstate extends State { builder: (BuildContext context) { return Container( width: MediaQuery.of(context).size.width, - margin: EdgeInsets.symmetric( - horizontal: Responsive.responsiveValue( - context, - mobile: 4.0, - tablet: 6.0, - desktop: 8.0, - ), + margin: Responsive.paddingSymmetric( + context, + horizontal: 6.0, ), decoration: BoxDecoration( borderRadius: BorderRadius.circular( - Responsive.responsiveValue( - context, - mobile: 8.0, - tablet: 12.0, - desktop: 16.0, - ), + Responsive.scale(context, 12.0), ), boxShadow: [ BoxShadow( @@ -55,12 +46,7 @@ class _screenshotstate extends State { ), child: ClipRRect( borderRadius: BorderRadius.circular( - Responsive.responsiveValue( - context, - mobile: 8.0, - tablet: 12.0, - desktop: 16.0, - ), + Responsive.scale(context, 12.0), ), child: Image.network( imagePath, @@ -95,23 +81,11 @@ class _screenshotstate extends State { widget.captions!.isNotEmpty && _current < widget.captions!.length) Padding( - padding: EdgeInsets.all( - Responsive.responsiveValue( - context, - mobile: 8.0, - tablet: 12.0, - desktop: 16.0, - ), - ), + padding: Responsive.paddingAll(context, 12.0), child: Text( widget.captions![_current], style: Theme.of(context).textTheme.bodyMedium?.copyWith( - fontSize: Responsive.responsiveValue( - context, - mobile: 14.0, - tablet: 16.0, - desktop: 18.0, - ), + fontSize: Responsive.fontSize(context, 16.0), ), textAlign: TextAlign.center, maxLines: 2, @@ -124,31 +98,12 @@ class _screenshotstate extends State { return GestureDetector( onTap: () => _controller.animateToPage(entry.key), child: Container( - width: Responsive.responsiveValue( + width: Responsive.scale(context, 10.0), + height: Responsive.scale(context, 10.0), + margin: Responsive.paddingSymmetric( context, - mobile: 8.0, - tablet: 10.0, - desktop: 12.0, - ), - height: Responsive.responsiveValue( - context, - mobile: 8.0, - tablet: 10.0, - desktop: 12.0, - ), - margin: EdgeInsets.symmetric( - vertical: Responsive.responsiveValue( - context, - mobile: 6.0, - tablet: 8.0, - desktop: 10.0, - ), - horizontal: Responsive.responsiveValue( - context, - mobile: 3.0, - tablet: 4.0, - desktop: 5.0, - ), + vertical: 8.0, + horizontal: 4.0, ), decoration: BoxDecoration( shape: BoxShape.circle, diff --git a/packages/flatpak/example/lib/presentation/widgets/top_bar.dart b/packages/flatpak/example/lib/presentation/widgets/top_bar.dart index 8c1f6f5..b1a2b81 100644 --- a/packages/flatpak/example/lib/presentation/widgets/top_bar.dart +++ b/packages/flatpak/example/lib/presentation/widgets/top_bar.dart @@ -32,9 +32,10 @@ class AppTopBar extends StatelessWidget implements PreferredSizeWidget { child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), child: Container( - padding: EdgeInsets.symmetric( - horizontal: Responsive.scaleWithConstraints(context, 65, minSize: 16, maxSize: 100), - vertical: Responsive.scaleWithConstraints(context, 33, minSize: 12, maxSize: 40) + padding: Responsive.paddingSymmetric( + context, + horizontal: 65, + vertical: 33, ), child: Row( children: [ @@ -42,14 +43,7 @@ class AppTopBar extends StatelessWidget implements PreferredSizeWidget { Spacer(), _buildSearchBar(context), - SizedBox( - width: Responsive.scaleWithConstraints( - context, - 61, - minSize: 24, - maxSize: 72, - ), - ), + Responsive.hGap(context, 61), _buildUserSection(context), ], ), @@ -63,57 +57,49 @@ class AppTopBar extends StatelessWidget implements PreferredSizeWidget { Size get preferredSize => const Size.fromHeight(100); Widget _buildLogo(BuildContext context) { - final logoH = Responsive.scaleWithConstraints( - context, - 36, - minSize: 24, - maxSize: 48, - ); - - final logoW = Responsive.scaleWithConstraints( - context, - 168, - minSize: 112, - maxSize: 224, - ); + final logoH = Responsive.scale(context, 36); + final logoW = Responsive.scale(context, 168); return Expanded( child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ - SizedBox( - height: logoH, - width: logoW, - child: SvgPicture.asset( - 'assets/logos/AGL.svg', + Flexible( + child: SizedBox( height: logoH, width: logoW, - fit: BoxFit.contain, + child: SvgPicture.asset( + 'assets/logos/AGL.svg', + height: logoH, + width: logoW, + fit: BoxFit.contain, + ), ), ), - const SizedBox(width: 7), - Container( - height: logoH, - alignment: Alignment.centerLeft, - child: ShaderMask( - shaderCallback: (bounds) => const LinearGradient( - colors: [Color(0x00000000), Color(0xFF33D17A)], - begin: Alignment.bottomRight, - end: Alignment.topLeft, - ).createShader(bounds), - child: Text( - 'Store', - style: TextStyle( - fontSize: Responsive.scaleWithConstraints( - context, - 36, - minSize: 24, - maxSize: 48, + Responsive.hGap(context, 7), + Flexible( + child: Container( + height: logoH, + alignment: Alignment.centerLeft, + child: ShaderMask( + shaderCallback: (bounds) => const LinearGradient( + colors: [Color(0x00000000), Color(0xFF33D17A)], + begin: Alignment.bottomRight, + end: Alignment.topLeft, + ).createShader(bounds), + child: FittedBox( + fit: BoxFit.scaleDown, + alignment: Alignment.centerLeft, + child: Text( + 'Store', + style: TextStyle( + fontSize: Responsive.fontSize(context, 36), + fontWeight: FontWeight.w600, + color: Colors.white, + letterSpacing: -0.5, + height: 1.0, + ), ), - fontWeight: FontWeight.w600, - color: Colors.white, - letterSpacing: -0.5, - height: 1.0, ), ), ), @@ -124,19 +110,8 @@ class AppTopBar extends StatelessWidget implements PreferredSizeWidget { } Widget _buildSearchBar(BuildContext context) { - final searchBarWidth = Responsive.scaleWithConstraints( - context, - 280, - minSize: 200, - maxSize: 400, - ); - - final searchBarHeight = Responsive.scaleWithConstraints( - context, - 42, - minSize: 36, - maxSize: 48, - ); + final searchBarWidth = Responsive.scale(context, 280); + final searchBarHeight = Responsive.scale(context, 42); return Container( width: searchBarWidth, @@ -149,7 +124,7 @@ class AppTopBar extends StatelessWidget implements PreferredSizeWidget { child: Container( decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.7), - borderRadius: BorderRadius.circular(AppTheme.borderRadiusMedium), + borderRadius: BorderRadius.circular(Responsive.scale(context, AppTheme.borderRadiusMedium)), border: Border.all( color: Colors.white.withValues(alpha: 0.3), width: 1.0, @@ -167,23 +142,23 @@ class AppTopBar extends StatelessWidget implements PreferredSizeWidget { hintText: 'Search for apps...', hintStyle: TextStyle( color: const Color(0xFFADAEBC).withValues(alpha: 0.8), - fontSize: Responsive.scale(context, 16).clamp(12.0, 18.0), + fontSize: Responsive.fontSize(context, 16), ), prefixIcon: Icon( Icons.search, color: const Color(0xFF9CA3AF).withValues(alpha: 0.8), - size: Responsive.scale(context, 16).clamp(12.0, 18.0), + size: Responsive.scale(context, 16), ), border: InputBorder.none, contentPadding: EdgeInsets.symmetric( - horizontal: Responsive.scale(context, 16).clamp(12.0, 20.0), - vertical: (searchBarHeight - 20) / 2, + horizontal: Responsive.scale(context, 16), + vertical: (searchBarHeight - Responsive.scale(context, 20)) / 2, ), isDense: true, ), style: TextStyle( color: Color(0xFF212121), - fontSize: Responsive.scale(context, 14.0).clamp(12.0, 16.0), + fontSize: Responsive.fontSize(context, 14.0), ), ), ), @@ -198,12 +173,12 @@ class AppTopBar extends StatelessWidget implements PreferredSizeWidget { ///onTap: , child: SvgPicture.asset( 'assets/icons/bell.svg', - width: Responsive.scaleWithConstraints(context, 17.05, minSize: 12, maxSize: 24), - height: Responsive.scaleWithConstraints(context, 19.5, minSize: 14, maxSize: 28), + width: Responsive.scale(context, 17.05), + height: Responsive.scale(context, 19.5), ), ), - const SizedBox(width: 16), + Responsive.hGap(context, 16), GestureDetector( onTap: () => Navigator.push( @@ -211,10 +186,10 @@ class AppTopBar extends StatelessWidget implements PreferredSizeWidget { MaterialPageRoute(builder: (context) => ProfileScreen()), ), child: Container( - width: 31.18, - height: 31.18, + width: Responsive.scale(context, 31.18), + height: Responsive.scale(context, 31.18), decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), + borderRadius: BorderRadius.circular(Responsive.scale(context, 10)), border: Border.all( color: Colors.white.withValues(alpha: 0.3), width: 2, @@ -228,7 +203,7 @@ class AppTopBar extends StatelessWidget implements PreferredSizeWidget { ], ), child: ClipRRect( - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(Responsive.scale(context, 8)), child: Image.asset('assets/images/person.png', fit: BoxFit.cover), ), ), From 2412f3c460ac9cd0ff1e1732aefa612f364ada82 Mon Sep 17 00:00:00 2001 From: AhmedAdelWafdy7 Date: Mon, 6 Apr 2026 05:20:10 +0000 Subject: [PATCH 06/12] topbar fix for 1920x1080 --- .../lib/presentation/widgets/top_bar.dart | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/flatpak/example/lib/presentation/widgets/top_bar.dart b/packages/flatpak/example/lib/presentation/widgets/top_bar.dart index b1a2b81..5c7d54d 100644 --- a/packages/flatpak/example/lib/presentation/widgets/top_bar.dart +++ b/packages/flatpak/example/lib/presentation/widgets/top_bar.dart @@ -10,8 +10,10 @@ class AppTopBar extends StatelessWidget implements PreferredSizeWidget { @override Widget build(BuildContext context) { + final barHeight = Responsive.scale(context, 100); + return Container( - height: preferredSize.height, + height: barHeight, decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.25), border: Border( @@ -54,7 +56,31 @@ class AppTopBar extends StatelessWidget implements PreferredSizeWidget { } @override - Size get preferredSize => const Size.fromHeight(100); + Size get preferredSize { + try { + final view = PlatformDispatcher.instance.implicitView ?? PlatformDispatcher.instance.views.first; + final w = view.physicalSize.width / view.devicePixelRatio; + final h = view.physicalSize.height / view.devicePixelRatio; + + final wRatio = w / 1024.0; + final hRatio = h / 768.0; + final val = wRatio * hRatio; + double scale = val <= 0 ? 1.0 : (val < 1 ? val + (1 - val) * 0.5 : val * 0.5 + 0.5); + + if (w < 600) { + scale = scale.clamp(0.45, 0.85); + } else if (w < 1024) { + scale = scale.clamp(0.70, 1.10); + } else if (w < 1440) { + scale = scale.clamp(0.90, 1.40); + } else { + scale = scale.clamp(1.10, 2.00); + } + return Size.fromHeight(100.0 * scale); + } catch (_) { + return const Size.fromHeight(100.0); + } + } Widget _buildLogo(BuildContext context) { final logoH = Responsive.scale(context, 36); From 3ac51d30f701362d914947c54597cdb7e2333cec Mon Sep 17 00:00:00 2001 From: AhmedAdelWafdy7 Date: Mon, 6 Apr 2026 06:52:05 +0000 Subject: [PATCH 07/12] dialog fix for 1920x1080 --- .../lib/presentation/widgets/app_card.dart | 20 +- .../lib/presentation/widgets/app_info.dart | 12 + .../widgets/appscreen_content.dart | 45 ++-- .../presentation/widgets/appscreen_head.dart | 106 ++++----- .../widgets/category_section.dart | 96 ++++---- .../widgets/flatpak_permission_dialog.dart | 219 ++++++++++-------- 6 files changed, 258 insertions(+), 240 deletions(-) diff --git a/packages/flatpak/example/lib/presentation/widgets/app_card.dart b/packages/flatpak/example/lib/presentation/widgets/app_card.dart index 04ef16b..6624ed9 100644 --- a/packages/flatpak/example/lib/presentation/widgets/app_card.dart +++ b/packages/flatpak/example/lib/presentation/widgets/app_card.dart @@ -331,16 +331,18 @@ class _AppCardState extends State with TickerProviderStateMixin { Responsive.hGap(context, 8), ], Flexible( - child: Text( - config.text, - style: TextStyle( - color: config.textColor, - fontWeight: FontWeight.w500, - fontFamily: 'general-sans', - fontSize: Responsive.fontSize(context, 14), + child: FittedBox( + fit: BoxFit.scaleDown, + child: Text( + config.text, + style: TextStyle( + color: config.textColor, + fontWeight: FontWeight.w500, + fontFamily: 'general-sans', + fontSize: Responsive.fontSize(context, 14), + ), + maxLines: 1, ), - maxLines: 1, - overflow: TextOverflow.ellipsis, ), ), ], diff --git a/packages/flatpak/example/lib/presentation/widgets/app_info.dart b/packages/flatpak/example/lib/presentation/widgets/app_info.dart index 90b72ca..bc6d030 100644 --- a/packages/flatpak/example/lib/presentation/widgets/app_info.dart +++ b/packages/flatpak/example/lib/presentation/widgets/app_info.dart @@ -40,6 +40,7 @@ class AppInfo extends StatelessWidget { "Version", style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Colors.grey[600], + fontSize: Responsive.fontSize(context, 12), fontWeight: FontWeight.w500, fontFamily: 'general-sans', ), @@ -48,6 +49,7 @@ class AppInfo extends StatelessWidget { Text( version, style: Theme.of(context).textTheme.bodyMedium?.copyWith( + fontSize: Responsive.fontSize(context, 14), fontWeight: FontWeight.w500, fontFamily: 'general-sans', ), @@ -65,6 +67,7 @@ class AppInfo extends StatelessWidget { "Installed Size", style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Colors.grey[600], + fontSize: Responsive.fontSize(context, 12), fontWeight: FontWeight.w500, fontFamily: 'general-sans', ), @@ -73,6 +76,7 @@ class AppInfo extends StatelessWidget { Text( size, style: Theme.of(context).textTheme.bodyMedium?.copyWith( + fontSize: Responsive.fontSize(context, 14), fontWeight: FontWeight.w500, fontFamily: 'general-sans', ), @@ -90,6 +94,7 @@ class AppInfo extends StatelessWidget { "Last Update", style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Colors.grey[600], + fontSize: Responsive.fontSize(context, 12), fontWeight: FontWeight.w500, fontFamily: 'general-sans', ), @@ -98,6 +103,7 @@ class AppInfo extends StatelessWidget { Text( last_upadate, style: Theme.of(context).textTheme.bodyMedium?.copyWith( + fontSize: Responsive.fontSize(context, 14), fontWeight: FontWeight.w500, fontFamily: 'general-sans', ), @@ -128,6 +134,7 @@ class AppInfo extends StatelessWidget { "License", style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Colors.grey[600], + fontSize: Responsive.fontSize(context, 12), fontWeight: FontWeight.w500, fontFamily: 'general-sans', ), @@ -136,6 +143,7 @@ class AppInfo extends StatelessWidget { Text( License, style: Theme.of(context).textTheme.bodyMedium?.copyWith( + fontSize: Responsive.fontSize(context, 14), fontWeight: FontWeight.w500, fontFamily: 'general-sans', ), @@ -154,6 +162,7 @@ class AppInfo extends StatelessWidget { "Developer", style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Colors.grey[600], + fontSize: Responsive.fontSize(context, 12), fontWeight: FontWeight.w500, fontFamily: 'general-sans', ), @@ -165,6 +174,7 @@ class AppInfo extends StatelessWidget { url, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Colors.blue, + fontSize: Responsive.fontSize(context, 14), decoration: TextDecoration.underline, fontWeight: FontWeight.w500, fontFamily: 'general-sans', @@ -184,6 +194,7 @@ class AppInfo extends StatelessWidget { "Content Rating", style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Colors.grey[600], + fontSize: Responsive.fontSize(context, 12), fontWeight: FontWeight.w500, fontFamily: 'general-sans', ), @@ -192,6 +203,7 @@ class AppInfo extends StatelessWidget { Text( content_rating, style: Theme.of(context).textTheme.bodyMedium?.copyWith( + fontSize: Responsive.fontSize(context, 14), fontWeight: FontWeight.w500, ), overflow: TextOverflow.ellipsis, diff --git a/packages/flatpak/example/lib/presentation/widgets/appscreen_content.dart b/packages/flatpak/example/lib/presentation/widgets/appscreen_content.dart index 3f901e8..58478f4 100644 --- a/packages/flatpak/example/lib/presentation/widgets/appscreen_content.dart +++ b/packages/flatpak/example/lib/presentation/widgets/appscreen_content.dart @@ -137,8 +137,8 @@ class AppscreenContent extends StatelessWidget { maxWidth: Responsive.width(context) - Responsive.scale(context, 200), ), child: Wrap( - spacing: 6, - runSpacing: 4, + spacing: Responsive.scale(context, 6), + runSpacing: Responsive.scale(context, 4), children: categories, ), ), @@ -433,7 +433,7 @@ class AppscreenContent extends StatelessWidget { Responsive.vGap(context, 30), Container( width: double.infinity, - height: 1, + height: Responsive.scale(context, 1), color: Colors.grey.shade300, ), Padding( @@ -714,7 +714,7 @@ class _GlassActionButton extends StatelessWidget { effectiveColor.withValues(alpha: 0.7), ], ), - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(Responsive.scale(context, 12)), border: Border.all( color: Colors.white.withValues(alpha: 0.2), width: 1.0, @@ -722,14 +722,14 @@ class _GlassActionButton extends StatelessWidget { boxShadow: [ BoxShadow( color: effectiveColor.withValues(alpha: 0.3), - blurRadius: 12, - offset: const Offset(0, 4), + blurRadius: Responsive.scale(context, 12), + offset: Offset(0, Responsive.scale(context, 4)), ), ], ) : BoxDecoration( color: effectiveColor.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(Responsive.scale(context, 12)), border: Border.all( color: effectiveColor.withValues(alpha: 0.4), width: 1.5, @@ -744,24 +744,24 @@ class _GlassActionButton extends StatelessWidget { color: Colors.transparent, child: InkWell( onTap: onTap, - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(Responsive.scale(context, 12)), child: Container( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + padding: Responsive.paddingSymmetric(context, horizontal: 24, vertical: 12), decoration: decoration, child: Row( mainAxisSize: MainAxisSize.min, children: [ if (isLoading) ...[ SizedBox( - width: 16, - height: 16, + width: Responsive.scale(context, 16), + height: Responsive.scale(context, 16), child: CircularProgressIndicator( value: showProgress ? progress : null, strokeWidth: 2, color: textColor, ), ), - const SizedBox(width: 8), + Responsive.hGap(context, 8), ], if (showProgress) @@ -769,19 +769,24 @@ class _GlassActionButton extends StatelessWidget { '${(progress! * 100).toInt()}%', style: TextStyle( color: textColor, - fontSize: 15, + fontSize: Responsive.fontSize(context, 15), fontWeight: FontWeight.w600, letterSpacing: 0.3, ), ) else - Text( - label, - style: TextStyle( - color: textColor, - fontSize: 15, - fontWeight: FontWeight.w600, - letterSpacing: 0.3, + Flexible( + child: FittedBox( + fit: BoxFit.scaleDown, + child: Text( + label, + style: TextStyle( + color: textColor, + fontSize: Responsive.fontSize(context, 15), + fontWeight: FontWeight.w600, + letterSpacing: 0.3, + ), + ), ), ), ], diff --git a/packages/flatpak/example/lib/presentation/widgets/appscreen_head.dart b/packages/flatpak/example/lib/presentation/widgets/appscreen_head.dart index c906901..05e992c 100644 --- a/packages/flatpak/example/lib/presentation/widgets/appscreen_head.dart +++ b/packages/flatpak/example/lib/presentation/widgets/appscreen_head.dart @@ -10,8 +10,10 @@ class AppscreenHead extends StatelessWidget implements PreferredSizeWidget { @override Widget build(BuildContext context) { + final barHeight = Responsive.scale(context, 100); + return Container( - height: preferredSize.height, + height: barHeight, decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.25), border: Border( @@ -32,7 +34,7 @@ class AppscreenHead extends StatelessWidget implements PreferredSizeWidget { child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), child: Container( - padding: EdgeInsets.symmetric(horizontal: 40, vertical: 25), + padding: Responsive.paddingSymmetric(context, horizontal: 40, vertical: 25), child: Row( children: [ _buildBackWidget(context), @@ -47,22 +49,35 @@ class AppscreenHead extends StatelessWidget implements PreferredSizeWidget { } @override - Size get preferredSize => const Size.fromHeight(100); + Size get preferredSize { + try { + final view = PlatformDispatcher.instance.implicitView ?? PlatformDispatcher.instance.views.first; + final w = view.physicalSize.width / view.devicePixelRatio; + final h = view.physicalSize.height / view.devicePixelRatio; + + final wRatio = w / 1024.0; + final hRatio = h / 768.0; + final val = wRatio * hRatio; + double scale = val <= 0 ? 1.0 : (val < 1 ? val + (1 - val) * 0.5 : val * 0.5 + 0.5); + + if (w < 600) { + scale = scale.clamp(0.45, 0.85); + } else if (w < 1024) { + scale = scale.clamp(0.70, 1.10); + } else if (w < 1440) { + scale = scale.clamp(0.90, 1.40); + } else { + scale = scale.clamp(1.10, 2.00); + } + return Size.fromHeight(100.0 * scale); + } catch (_) { + return const Size.fromHeight(100.0); + } + } Widget _buildBackWidget(BuildContext context) { - final backW = Responsive.scaleWithConstraints( - context, - 26, - minSize: 22, - maxSize: 28, - ); - - final backH = Responsive.scaleWithConstraints( - context, - 26, - minSize: 22, - maxSize: 28, - ); + final backW = Responsive.scale(context, 26); + final backH = Responsive.scale(context, 26); return GestureDetector( onTap: () => Navigator.pop(context), @@ -71,18 +86,16 @@ class AppscreenHead extends StatelessWidget implements PreferredSizeWidget { SizedBox( height: backH, width: backW, - child: Icon(CupertinoIcons.back, color: Colors.black), + child: FittedBox( + fit: BoxFit.scaleDown, + child: Icon(CupertinoIcons.back, color: Colors.black, size: Responsive.scale(context, 24)), + ), ), - SizedBox(width: 10), + Responsive.hGap(context, 10), Text( appname, style: TextStyle( - fontSize: Responsive.scaleWithConstraints( - context, - 20, - minSize: 16, - maxSize: 24, - ), + fontSize: Responsive.fontSize(context, 20), fontWeight: FontWeight.bold, fontFamily: 'khand', color: Colors.black, @@ -94,19 +107,8 @@ class AppscreenHead extends StatelessWidget implements PreferredSizeWidget { } Widget _buildEngageWidget(BuildContext context) { - final engageW = Responsive.scaleWithConstraints( - context, - 56, - minSize: 48, - maxSize: 64, - ); - - final engageH = Responsive.scaleWithConstraints( - context, - 20, - minSize: 16, - maxSize: 24, - ); + final engageW = Responsive.scale(context, 56); + final engageH = Responsive.scale(context, 20); return Row( children: [ @@ -117,34 +119,14 @@ class AppscreenHead extends StatelessWidget implements PreferredSizeWidget { children: [ SvgPicture.asset( 'assets/icons/like.svg', - width: Responsive.scaleWithConstraints( - context, - 20, - minSize: 16, - maxSize: 24, - ), - height: Responsive.scaleWithConstraints( - context, - 20, - minSize: 16, - maxSize: 24, - ), + width: Responsive.scale(context, 20), + height: Responsive.scale(context, 20), ), - SizedBox(width: 12), + Responsive.hGap(context, 12), SvgPicture.asset( 'assets/icons/share.svg', - width: Responsive.scaleWithConstraints( - context, - 20, - minSize: 16, - maxSize: 24, - ), - height: Responsive.scaleWithConstraints( - context, - 20, - minSize: 16, - maxSize: 24, - ), + width: Responsive.scale(context, 20), + height: Responsive.scale(context, 20), ), ], ), diff --git a/packages/flatpak/example/lib/presentation/widgets/category_section.dart b/packages/flatpak/example/lib/presentation/widgets/category_section.dart index cfef5f8..e041a14 100644 --- a/packages/flatpak/example/lib/presentation/widgets/category_section.dart +++ b/packages/flatpak/example/lib/presentation/widgets/category_section.dart @@ -26,19 +26,23 @@ class CategorySection extends StatelessWidget { return Column( children: [ Padding( - padding: EdgeInsets.symmetric( - horizontal: Responsive.scaleWithConstraints(context, 32, minSize: 16, maxSize: 64) - ), + padding: Responsive.paddingSymmetric(context, horizontal: 32), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - category_heading, - style: TextStyle( - color: const Color(0xFF111827), - fontSize: Responsive.scale(context, 24).clamp(16, 28), - fontWeight: FontWeight.bold, - fontFamily: 'khand', + Flexible( + child: FittedBox( + fit: BoxFit.scaleDown, + alignment: Alignment.centerLeft, + child: Text( + category_heading, + style: TextStyle( + color: const Color(0xFF111827), + fontSize: Responsive.fontSize(context, 24), + fontWeight: FontWeight.bold, + fontFamily: 'khand', + ), + ), ), ), GestureDetector( @@ -47,7 +51,7 @@ class CategorySection extends StatelessWidget { "See All", style: TextStyle( color: const Color(0xFF2563EB), - fontSize: Responsive.scale(context, 20).clamp(14, 24), + fontSize: Responsive.fontSize(context, 20), fontWeight: FontWeight.normal, fontFamily: 'general-sans', ), @@ -56,9 +60,9 @@ class CategorySection extends StatelessWidget { ], ), ), - SizedBox(height: Responsive.scaleWithConstraints(context, 20, minSize: 10, maxSize: 30)), + Responsive.vGap(context, 20), SizedBox( - height: Responsive.scaleWithConstraints(context, 234, minSize: 220, maxSize: 280), + height: Responsive.scale(context, 234), child: _buildContent(context), ), ], @@ -67,27 +71,27 @@ class CategorySection extends StatelessWidget { Widget _buildContent(BuildContext context) { if (isLoading && apps.isEmpty) { - return _buildLoadingState(); + return _buildLoadingState(context); } if (apps.isEmpty && !isLoading) { - return _buildEmptyState(); + return _buildEmptyState(context); } return ListView.builder( scrollDirection: Axis.horizontal, physics: const BouncingScrollPhysics(), - padding: const EdgeInsets.symmetric(horizontal: 16), + padding: Responsive.paddingSymmetric(context, horizontal: 16), itemCount: apps.length + (isLoading ? 1 : 0), itemBuilder: (context, index) { if (index >= apps.length) { - return _buildLoadingCard(); + return _buildLoadingCard(context); } final app = apps[index]; return Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), + padding: Responsive.paddingSymmetric(context, horizontal: 8.0), child: AppCard( application: app, onTap: () => onTap(app), @@ -97,64 +101,64 @@ class CategorySection extends StatelessWidget { ); } - Widget _buildLoadingState() { + Widget _buildLoadingState(BuildContext context) { return ListView.builder( scrollDirection: Axis.horizontal, physics: const BouncingScrollPhysics(), - padding: const EdgeInsets.symmetric(horizontal: 16), + padding: Responsive.paddingSymmetric(context, horizontal: 16), itemCount: 5, itemBuilder: (context, index) { return Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: _buildLoadingCard(), + padding: Responsive.paddingSymmetric(context, horizontal: 8.0), + child: _buildLoadingCard(context), ); }, ); } - Widget _buildLoadingCard() { + Widget _buildLoadingCard(BuildContext context) { return Container( - width: 160, + width: Responsive.scale(context, 160), decoration: BoxDecoration( color: Colors.grey[200], - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(Responsive.scale(context, 12)), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( - width: 64, - height: 64, + width: Responsive.scale(context, 64), + height: Responsive.scale(context, 64), decoration: BoxDecoration( color: Colors.grey[300], - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(Responsive.scale(context, 8)), ), ), - const SizedBox(height: 12), + Responsive.vGap(context, 12), Container( - width: 100, - height: 16, + width: Responsive.scale(context, 100), + height: Responsive.scale(context, 16), decoration: BoxDecoration( color: Colors.grey[300], - borderRadius: BorderRadius.circular(4), + borderRadius: BorderRadius.circular(Responsive.scale(context, 4)), ), ), - const SizedBox(height: 8), + Responsive.vGap(context, 8), Container( - width: 80, - height: 14, + width: Responsive.scale(context, 80), + height: Responsive.scale(context, 14), decoration: BoxDecoration( color: Colors.grey[300], - borderRadius: BorderRadius.circular(4), + borderRadius: BorderRadius.circular(Responsive.scale(context, 4)), ), ), - const SizedBox(height: 12), + Responsive.vGap(context, 12), Container( - width: 120, - height: 36, + width: Responsive.scale(context, 120), + height: Responsive.scale(context, 36), decoration: BoxDecoration( color: Colors.grey[300], - borderRadius: BorderRadius.circular(18), + borderRadius: BorderRadius.circular(Responsive.scale(context, 18)), ), ), ], @@ -162,21 +166,21 @@ class CategorySection extends StatelessWidget { ); } - Widget _buildEmptyState() { + Widget _buildEmptyState(BuildContext context) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(Icons.apps, size: 48, color: Colors.grey[400]), - const SizedBox(height: 8), + Icon(Icons.apps, size: Responsive.scale(context, 48), color: Colors.grey[400]), + Responsive.vGap(context, 8), Text( 'No apps available in this category', - style: TextStyle(color: Colors.grey[600], fontSize: 16), + style: TextStyle(color: Colors.grey[600], fontSize: Responsive.fontSize(context, 16)), ), - const SizedBox(height: 8), + Responsive.vGap(context, 8), Text( 'Pull to refresh or check your connection', - style: TextStyle(color: Colors.grey[500], fontSize: 12), + style: TextStyle(color: Colors.grey[500], fontSize: Responsive.fontSize(context, 12)), ), ], ), diff --git a/packages/flatpak/example/lib/presentation/widgets/flatpak_permission_dialog.dart b/packages/flatpak/example/lib/presentation/widgets/flatpak_permission_dialog.dart index 6ed3a14..252068b 100644 --- a/packages/flatpak/example/lib/presentation/widgets/flatpak_permission_dialog.dart +++ b/packages/flatpak/example/lib/presentation/widgets/flatpak_permission_dialog.dart @@ -50,10 +50,7 @@ class _PermissionDialogContentState extends State<_PermissionDialogContent> curve: Curves.easeOutBack, ); - _fadeAnimation = CurvedAnimation( - parent: _controller, - curve: Curves.easeIn, - ); + _fadeAnimation = CurvedAnimation(parent: _controller, curve: Curves.easeIn); _controller.forward(); } @@ -75,124 +72,126 @@ class _PermissionDialogContentState extends State<_PermissionDialogContent> scale: _scaleAnimation, child: FadeTransition( opacity: _fadeAnimation, - child: Center( - child: Dialog( - backgroundColor: Colors.transparent, - elevation: 0, - insetPadding: EdgeInsets.all(Responsive.scaleWithConstraints(context, 20, minSize: 10, maxSize: 40)), - child: GlassContainer( - borderRadius: Responsive.scaleWithConstraints(context, 32, minSize: 16, maxSize: 48), - blur: 20, - borderOpacity: 0.2, - color: isDark - ? Colors.black.withValues(alpha: 0.6) - : Colors.white.withValues(alpha: 0.7), - child: Container( - constraints: BoxConstraints( - maxWidth: Responsive.scaleWithConstraints(context, 400, minSize: 300, maxSize: 600), - maxHeight: MediaQuery.of(context).size.height * 0.85, - ), - padding: EdgeInsets.all(Responsive.scaleWithConstraints(context, 32, minSize: 16, maxSize: 48)), - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - _buildIconHeader(colorScheme, permission), + child: Dialog( + backgroundColor: Colors.transparent, + elevation: 0, + insetPadding: Responsive.paddingAll(context, 20), + child: GlassContainer( + borderRadius: Responsive.scale(context, 32), + blur: 20, + borderOpacity: 0.2, + color: isDark + ? Colors.black.withValues(alpha: 0.6) + : Colors.white.withValues(alpha: 0.7), + child: Container( + constraints: BoxConstraints( + maxWidth: Responsive.dialogMaxWidth(context), + maxHeight: Responsive.dialogMaxHeight(context), + ), + padding: Responsive.paddingAll(context, 32), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildIconHeader(context, colorScheme, permission), - const SizedBox(height: 28), + Responsive.vGap(context, 28), - Text( - 'Permission Required', - style: theme.textTheme.headlineSmall?.copyWith( - fontWeight: FontWeight.bold, - // Ensure text is readable on glass - color: isDark ? Colors.white : Colors.black87, - letterSpacing: -0.5, + Text( + 'Permission Required', + style: theme.textTheme.headlineSmall?.copyWith( + fontWeight: FontWeight.bold, + fontSize: Responsive.fontSize(context, 24), + // Ensure text is readable on glass + color: isDark ? Colors.white : Colors.black87, + letterSpacing: -0.5, + ), + textAlign: TextAlign.center, ), - textAlign: TextAlign.center, - ), - - const SizedBox(height: 12), - - RichText( - textAlign: TextAlign.center, - text: TextSpan( - style: theme.textTheme.bodyLarge?.copyWith( - color: isDark ? Colors.white70 : Colors.black54, - height: 1.5, - ), - children: [ - TextSpan( - text: _getAppDisplayName(widget.event.appId), - style: TextStyle( - fontWeight: FontWeight.w700, - color: isDark ? Colors.white : Colors.black, + Responsive.vGap(context, 12), + RichText( + textAlign: TextAlign.center, + text: TextSpan( + style: theme.textTheme.bodyLarge?.copyWith( + color: isDark ? Colors.white70 : Colors.black54, + fontSize: Responsive.fontSize(context, 16), + height: 1.5, + ), + children: [ + TextSpan( + text: _getAppDisplayName(widget.event.appId), + style: TextStyle( + fontWeight: FontWeight.w700, + color: isDark ? Colors.white : Colors.black, + ), ), - ), - const TextSpan(text: ' wants to access your '), - TextSpan( - text: permission.displayName.toLowerCase(), - style: TextStyle( - fontWeight: FontWeight.w700, - color: isDark ? Colors.white : Colors.black, + const TextSpan(text: ' wants to access your '), + TextSpan( + text: permission.displayName.toLowerCase(), + style: TextStyle( + fontWeight: FontWeight.w700, + color: isDark ? Colors.white : Colors.black, + ), ), - ), - ], + ], + ), ), - ), - const SizedBox(height: 24), + Responsive.vGap(context, 24), - _buildDescriptionCard(context, permission), + _buildDescriptionCard(context, permission), - if (widget.event.total != null && widget.event.total! > 1) ...[ - const SizedBox(height: 24), - _buildProgressIndicator(colorScheme), - ], + if (widget.event.total != null && + widget.event.total! > 1) ...[ + Responsive.vGap(context, 24), + _buildProgressIndicator(context, colorScheme), + ], - const SizedBox(height: 32), + Responsive.vGap(context, 32), - // Action buttons - _buildActionButtons(colorScheme, isDark), - ], + // Action buttons + _buildActionButtons(context, colorScheme, isDark), + ], + ), ), - ), ), ), ), - ), ), ); } Widget _buildIconHeader( - ColorScheme colorScheme, FlatpakPermission permission) { + BuildContext context, + ColorScheme colorScheme, + FlatpakPermission permission, + ) { return Stack( alignment: Alignment.center, children: [ Container( - width: 80, - height: 80, + width: Responsive.scale(context, 80), + height: Responsive.scale(context, 80), decoration: BoxDecoration( shape: BoxShape.circle, color: colorScheme.primary.withValues(alpha: 0.2), boxShadow: [ BoxShadow( color: colorScheme.primary.withValues(alpha: 0.4), - blurRadius: 40, + blurRadius: Responsive.scale(context, 40), spreadRadius: 0, ), ], ), ), GlassContainer( - borderRadius: 50, + borderRadius: Responsive.scale(context, 50), blur: 10, color: colorScheme.surface.withValues(alpha: 0.1), borderOpacity: 0.2, child: Container( - width: 80, - height: 80, + width: Responsive.scale(context, 80), + height: Responsive.scale(context, 80), alignment: Alignment.center, decoration: BoxDecoration( shape: BoxShape.circle, @@ -207,7 +206,7 @@ class _PermissionDialogContentState extends State<_PermissionDialogContent> ), child: Icon( permission.icon, - size: 36, + size: Responsive.scale(context, 36), color: colorScheme.primary, ), ), @@ -216,32 +215,35 @@ class _PermissionDialogContentState extends State<_PermissionDialogContent> ); } - Widget _buildDescriptionCard(BuildContext context, FlatpakPermission permission) { + Widget _buildDescriptionCard( + BuildContext context, + FlatpakPermission permission, + ) { final isDark = Theme.of(context).brightness == Brightness.dark; return GlassContainer( - borderRadius: 16, + borderRadius: Responsive.scale(context, 16), blur: 0, color: isDark ? Colors.white.withValues(alpha: 0.05) : Colors.black.withValues(alpha: 0.03), borderOpacity: 0.1, child: Padding( - padding: const EdgeInsets.all(16), + padding: Responsive.paddingAll(context, 16), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon( Icons.info_outline_rounded, - size: 20, + size: Responsive.scale(context, 20), color: isDark ? Colors.white70 : Colors.black54, ), - const SizedBox(width: 12), + Responsive.hGap(context, 12), Expanded( child: Text( permission.description, style: TextStyle( - fontSize: 13, + fontSize: Responsive.fontSize(context, 13), color: isDark ? Colors.white70 : Colors.black54, height: 1.4, ), @@ -253,7 +255,10 @@ class _PermissionDialogContentState extends State<_PermissionDialogContent> ); } - Widget _buildProgressIndicator(ColorScheme colorScheme) { + Widget _buildProgressIndicator( + BuildContext context, + ColorScheme colorScheme, + ) { final progress = widget.event.progress!; final total = widget.event.total!; @@ -265,7 +270,7 @@ class _PermissionDialogContentState extends State<_PermissionDialogContent> Text( 'Permission $progress of $total', style: TextStyle( - fontSize: 12, + fontSize: Responsive.fontSize(context, 12), fontWeight: FontWeight.w600, color: colorScheme.primary, letterSpacing: 0.5, @@ -273,11 +278,11 @@ class _PermissionDialogContentState extends State<_PermissionDialogContent> ), ], ), - const SizedBox(height: 12), + Responsive.vGap(context, 12), Container( - height: 6, + height: Responsive.scale(context, 6), decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), + borderRadius: BorderRadius.circular(Responsive.scale(context, 10)), color: colorScheme.onSurface.withValues(alpha: 0.1), ), child: FractionallySizedBox( @@ -285,7 +290,9 @@ class _PermissionDialogContentState extends State<_PermissionDialogContent> widthFactor: progress / total, child: Container( decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), + borderRadius: BorderRadius.circular( + Responsive.scale(context, 10), + ), gradient: LinearGradient( colors: [ colorScheme.primary.withValues(alpha: 0.7), @@ -295,8 +302,8 @@ class _PermissionDialogContentState extends State<_PermissionDialogContent> boxShadow: [ BoxShadow( color: colorScheme.primary.withValues(alpha: 0.4), - blurRadius: 6, - offset: const Offset(0, 2), + blurRadius: Responsive.scale(context, 6), + offset: Offset(0, Responsive.scale(context, 2)), ), ], ), @@ -307,7 +314,11 @@ class _PermissionDialogContentState extends State<_PermissionDialogContent> ); } - Widget _buildActionButtons(ColorScheme colorScheme, bool isDark) { + Widget _buildActionButtons( + BuildContext context, + ColorScheme colorScheme, + bool isDark, + ) { return Row( children: [ Expanded( @@ -325,7 +336,7 @@ class _PermissionDialogContentState extends State<_PermissionDialogContent> ), ), - const SizedBox(width: 16), + Responsive.hGap(context, 16), Expanded( child: GlassButton( @@ -352,11 +363,13 @@ class _PermissionDialogContentState extends State<_PermissionDialogContent> final name = parts.last; return name .split('_') - .map((word) => word.isNotEmpty - ? word[0].toUpperCase() + word.substring(1) - : '') + .map( + (word) => word.isNotEmpty + ? word[0].toUpperCase() + word.substring(1) + : '', + ) .join(' '); } return appId; } -} \ No newline at end of file +} From a65c76c31f19d6fbf90695f89189241d020a5e36 Mon Sep 17 00:00:00 2001 From: AhmedAdelWafdy7 Date: Mon, 6 Apr 2026 07:16:34 +0000 Subject: [PATCH 08/12] dialog fix drop filter effect --- .../widgets/flatpak_permission_dialog.dart | 140 +++++++++--------- 1 file changed, 72 insertions(+), 68 deletions(-) diff --git a/packages/flatpak/example/lib/presentation/widgets/flatpak_permission_dialog.dart b/packages/flatpak/example/lib/presentation/widgets/flatpak_permission_dialog.dart index 252068b..e96cfc5 100644 --- a/packages/flatpak/example/lib/presentation/widgets/flatpak_permission_dialog.dart +++ b/packages/flatpak/example/lib/presentation/widgets/flatpak_permission_dialog.dart @@ -72,86 +72,90 @@ class _PermissionDialogContentState extends State<_PermissionDialogContent> scale: _scaleAnimation, child: FadeTransition( opacity: _fadeAnimation, - child: Dialog( - backgroundColor: Colors.transparent, - elevation: 0, - insetPadding: Responsive.paddingAll(context, 20), - child: GlassContainer( - borderRadius: Responsive.scale(context, 32), - blur: 20, - borderOpacity: 0.2, - color: isDark - ? Colors.black.withValues(alpha: 0.6) - : Colors.white.withValues(alpha: 0.7), + child: Center( + child: Material( + color: Colors.transparent, + type: MaterialType.transparency, child: Container( + margin: Responsive.paddingAll(context, 20), constraints: BoxConstraints( maxWidth: Responsive.dialogMaxWidth(context), maxHeight: Responsive.dialogMaxHeight(context), ), - padding: Responsive.paddingAll(context, 32), - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - _buildIconHeader(context, colorScheme, permission), + child: GlassContainer( + borderRadius: Responsive.scale(context, 32), + blur: 20, + borderOpacity: 0.2, + color: isDark + ? Colors.black.withValues(alpha: 0.6) + : Colors.white.withValues(alpha: 0.7), + child: Padding( + padding: Responsive.paddingAll(context, 32), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildIconHeader(context, colorScheme, permission), - Responsive.vGap(context, 28), + Responsive.vGap(context, 28), - Text( - 'Permission Required', - style: theme.textTheme.headlineSmall?.copyWith( - fontWeight: FontWeight.bold, - fontSize: Responsive.fontSize(context, 24), - // Ensure text is readable on glass - color: isDark ? Colors.white : Colors.black87, - letterSpacing: -0.5, - ), - textAlign: TextAlign.center, - ), - Responsive.vGap(context, 12), - RichText( - textAlign: TextAlign.center, - text: TextSpan( - style: theme.textTheme.bodyLarge?.copyWith( - color: isDark ? Colors.white70 : Colors.black54, - fontSize: Responsive.fontSize(context, 16), - height: 1.5, + Text( + 'Permission Required', + style: theme.textTheme.headlineSmall?.copyWith( + fontWeight: FontWeight.bold, + fontSize: Responsive.fontSize(context, 24), + // Ensure text is readable on glass + color: isDark ? Colors.white : Colors.black87, + letterSpacing: -0.5, + ), + textAlign: TextAlign.center, ), - children: [ - TextSpan( - text: _getAppDisplayName(widget.event.appId), - style: TextStyle( - fontWeight: FontWeight.w700, - color: isDark ? Colors.white : Colors.black, - ), - ), - const TextSpan(text: ' wants to access your '), - TextSpan( - text: permission.displayName.toLowerCase(), - style: TextStyle( - fontWeight: FontWeight.w700, - color: isDark ? Colors.white : Colors.black, + Responsive.vGap(context, 12), + RichText( + textAlign: TextAlign.center, + text: TextSpan( + style: theme.textTheme.bodyLarge?.copyWith( + color: isDark ? Colors.white70 : Colors.black54, + fontSize: Responsive.fontSize(context, 16), + height: 1.5, ), + children: [ + TextSpan( + text: _getAppDisplayName(widget.event.appId), + style: TextStyle( + fontWeight: FontWeight.w700, + color: isDark ? Colors.white : Colors.black, + ), + ), + const TextSpan(text: ' wants to access your '), + TextSpan( + text: permission.displayName.toLowerCase(), + style: TextStyle( + fontWeight: FontWeight.w700, + color: isDark ? Colors.white : Colors.black, + ), + ), + ], ), + ), + + Responsive.vGap(context, 24), + + _buildDescriptionCard(context, permission), + + if (widget.event.total != null && + widget.event.total! > 1) ...[ + Responsive.vGap(context, 24), + _buildProgressIndicator(context, colorScheme), ], - ), + + Responsive.vGap(context, 32), + + // Action buttons + _buildActionButtons(context, colorScheme, isDark), + ], ), - - Responsive.vGap(context, 24), - - _buildDescriptionCard(context, permission), - - if (widget.event.total != null && - widget.event.total! > 1) ...[ - Responsive.vGap(context, 24), - _buildProgressIndicator(context, colorScheme), - ], - - Responsive.vGap(context, 32), - - // Action buttons - _buildActionButtons(context, colorScheme, isDark), - ], + ), ), ), ), From c53e34148e19f1923b44a17a0093d77e9e89e819 Mon Sep 17 00:00:00 2001 From: AhmedAdelWafdy7 Date: Mon, 6 Apr 2026 08:06:35 +0000 Subject: [PATCH 09/12] Hero fix responsivness --- .../lib/presentation/widgets/hero_widget.dart | 62 ++++++++++++------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/packages/flatpak/example/lib/presentation/widgets/hero_widget.dart b/packages/flatpak/example/lib/presentation/widgets/hero_widget.dart index 037f3ef..d107fe8 100644 --- a/packages/flatpak/example/lib/presentation/widgets/hero_widget.dart +++ b/packages/flatpak/example/lib/presentation/widgets/hero_widget.dart @@ -1,6 +1,7 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import '../../responsive.dart'; import 'hero_apps_icons.dart'; class HeroWidget extends StatelessWidget { @@ -20,21 +21,29 @@ class HeroWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Container( - margin: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), - height: 280, + margin: Responsive.paddingSymmetric( + context, + horizontal: 16.0, + vertical: 12.0, + ), + height: Responsive.scale(context, 280), child: Stack( children: [ // Background image Positioned.fill( child: ClipRRect( - borderRadius: BorderRadius.circular(24.0), + borderRadius: BorderRadius.circular( + Responsive.scale(context, 24.0), + ), child: Image.asset(imageUrl, fit: BoxFit.cover), ), ), // Glass effect container with content Positioned.fill( child: ClipRRect( - borderRadius: BorderRadius.circular(24.0), + borderRadius: BorderRadius.circular( + Responsive.scale(context, 24.0), + ), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 0, sigmaY: 0), child: Container( @@ -47,7 +56,9 @@ class HeroWidget extends StatelessWidget { Colors.white.withValues(alpha: 0.1), ], ), - borderRadius: BorderRadius.circular(24.0), + borderRadius: BorderRadius.circular( + Responsive.scale(context, 24.0), + ), border: Border.all( color: Colors.white.withValues(alpha: 0.3), width: 1.5, @@ -55,13 +66,13 @@ class HeroWidget extends StatelessWidget { boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.1), - blurRadius: 20, - offset: const Offset(0, 10), + blurRadius: Responsive.scale(context, 20), + offset: Offset(0, Responsive.scale(context, 10)), ), ], ), child: Padding( - padding: const EdgeInsets.all(24.0), + padding: Responsive.paddingAll(context, 24.0), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -75,13 +86,13 @@ class HeroWidget extends StatelessWidget { // Title Text( title, - style: const TextStyle( - fontSize: 32.0, + style: TextStyle( + fontSize: Responsive.fontSize(context, 32), fontWeight: FontWeight.bold, color: Colors.white, fontFamily: 'khand', height: 1.2, - shadows: [ + shadows: const [ Shadow( color: Colors.black26, offset: Offset(0, 2), @@ -90,11 +101,11 @@ class HeroWidget extends StatelessWidget { ], ), ), - const SizedBox(height: 12.0), + Responsive.vGap(context, 12), Text( subtitle, style: TextStyle( - fontSize: 14.0, + fontSize: Responsive.fontSize(context, 14), color: Colors.white.withValues(alpha: 0.9), fontFamily: 'general-sans', height: 1.4, @@ -107,13 +118,13 @@ class HeroWidget extends StatelessWidget { ], ), ), - const SizedBox(height: 20.0), + Responsive.vGap(context, 20), // Explore button with glass effect _buildExploreButton(context), ], ), ), - const SizedBox(width: 24.0), + Responsive.hGap(context, 24), // Right side - App icons const Expanded(flex: 2, child: AppsIcons()), ], @@ -135,11 +146,12 @@ class HeroWidget extends StatelessWidget { context.push('/discover'); }, child: ClipRRect( - borderRadius: BorderRadius.circular(25.0), + borderRadius: BorderRadius.circular(Responsive.scale(context, 25)), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), child: Container( - padding: const EdgeInsets.symmetric( + padding: Responsive.paddingSymmetric( + context, horizontal: 28.0, vertical: 14.0, ), @@ -150,23 +162,25 @@ class HeroWidget extends StatelessWidget { Colors.green.shade600.withValues(alpha: 0.9), ], ), - borderRadius: BorderRadius.circular(25.0), + borderRadius: BorderRadius.circular( + Responsive.scale(context, 25), + ), border: Border.all( color: Colors.white.withValues(alpha: 0.4), width: 1.5, ), - boxShadow: const [ + boxShadow: [ BoxShadow( - color: Color(0xFF33D17A), - blurRadius: 12, - offset: Offset(0, 4), + color: const Color(0xFF33D17A), + blurRadius: Responsive.scale(context, 12), + offset: Offset(0, Responsive.scale(context, 4)), ), ], ), - child: const Text( + child: Text( 'Explore Apps', style: TextStyle( - fontSize: 16.0, + fontSize: Responsive.fontSize(context, 16), fontWeight: FontWeight.w600, color: Colors.white, fontFamily: 'general-sans', From aac256e49fb6041843ddb86649603af20d7bd2d4 Mon Sep 17 00:00:00 2001 From: AhmedAdelWafdy7 Date: Tue, 7 Apr 2026 21:47:49 +0000 Subject: [PATCH 10/12] feat: add StorageErrorDialog widget for handling storage issues --- .../lib/data/models/flatpak_event_model.dart | 6 +- .../presentation/widgets/storge_error.dart | 453 ++++++++++++++++++ 2 files changed, 458 insertions(+), 1 deletion(-) create mode 100644 packages/flatpak/example/lib/presentation/widgets/storge_error.dart diff --git a/packages/flatpak/example/lib/data/models/flatpak_event_model.dart b/packages/flatpak/example/lib/data/models/flatpak_event_model.dart index 8d79370..1bdd829 100644 --- a/packages/flatpak/example/lib/data/models/flatpak_event_model.dart +++ b/packages/flatpak/example/lib/data/models/flatpak_event_model.dart @@ -18,6 +18,7 @@ enum FlatpakEventType { uninstallationStarted, updateStarted, connectionEstablished, + insufficientSpace, } class FlatpakEventModel { @@ -38,6 +39,7 @@ class FlatpakEventModel { final bool? isMainApp; final bool? success; final List? operations; + final int? availableMb; FlatpakEventModel({ required this.type, @@ -57,6 +59,7 @@ class FlatpakEventModel { this.isMainApp, this.success, this.operations, + this.availableMb, }); factory FlatpakEventModel.fromMap(Map map) { @@ -70,7 +73,8 @@ class FlatpakEventModel { final typeStr = map['type'] as String?; FlatpakEventType eventType = FlatpakEventType.unknown; - + + // Parse event type if (typeStr != null) { switch (typeStr) { diff --git a/packages/flatpak/example/lib/presentation/widgets/storge_error.dart b/packages/flatpak/example/lib/presentation/widgets/storge_error.dart new file mode 100644 index 0000000..7e96714 --- /dev/null +++ b/packages/flatpak/example/lib/presentation/widgets/storge_error.dart @@ -0,0 +1,453 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; + +class StorageErrorDialog extends StatefulWidget { + final String appName; + final double usedGB; + final double totalGB; + final VoidCallback onDismiss; + final VoidCallback onSettings; + + const StorageErrorDialog({ + super.key, + required this.appName, + required this.usedGB, + required this.totalGB, + required this.onDismiss, + required this.onSettings, + }); + + @override + State createState() => _StorageErrorDialogState(); +} + +class _StorageErrorDialogState extends State + with SingleTickerProviderStateMixin { + late final AnimationController _ctrl; + late final Animation _scale; + late final Animation _fade; + + @override + void initState() { + super.initState(); + _ctrl = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 520), + )..forward(); + + _scale = CurvedAnimation(parent: _ctrl, curve: Curves.elasticOut); + _fade = CurvedAnimation(parent: _ctrl, curve: Curves.easeIn); + } + + @override + void dispose() { + _ctrl.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final screen = MediaQuery.of(context).size; + final dialogWidth = screen.width.clamp(280.0, 400.0); + + return FadeTransition( + opacity: _fade, + child: ScaleTransition( + scale: _scale, + child: SizedBox( + width: dialogWidth, + child: ClipRRect( + borderRadius: BorderRadius.circular(28), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 28, sigmaY: 28), + child: Container( + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.14), + borderRadius: BorderRadius.circular(28), + border: Border.all( + color: Colors.white.withOpacity(0.28), + width: 1.2, + ), + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Colors.white.withOpacity(0.22), + Colors.white.withOpacity(0.06), + ], + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.35), + blurRadius: 40, + offset: const Offset(0, 20), + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildHeader(), + _buildBody(dialogWidth), + _buildDivider(), + _buildActions(), + ], + ), + ), + ), + ), + ), + ), + ); + } + + Widget _buildHeader() { + return Padding( + padding: const EdgeInsets.fromLTRB(24, 28, 24, 0), + child: Column( + children: [ + // Glowing warning icon + Container( + width: 64, + height: 64, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: const Color(0xFFFF4444).withOpacity(0.18), + border: Border.all( + color: const Color(0xFFFF6B6B).withOpacity(0.5), + width: 1.5, + ), + boxShadow: [ + BoxShadow( + color: const Color(0xFFFF4444).withOpacity(0.4), + blurRadius: 20, + spreadRadius: 2, + ), + ], + ), + child: const Icon( + Icons.storage_rounded, + color: Color(0xFFFF6B6B), + size: 30, + ), + ), + const SizedBox(height: 16), + + // Title + Text( + 'Unable to Install', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w700, + color: Colors.white.withOpacity(0.95), + letterSpacing: -0.3, + height: 1.2, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 4), + Text( + '"${widget.appName}"', + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.w700, + color: Color(0xFFFF8C8C), + letterSpacing: -0.3, + height: 1.2, + ), + textAlign: TextAlign.center, + ), + ], + ), + ); + } + + Widget _buildBody(double dialogWidth) { + final freeGB = widget.totalGB - widget.usedGB; + final usedFraction = widget.usedGB / widget.totalGB; + + return Padding( + padding: const EdgeInsets.fromLTRB(24, 16, 24, 24), + child: Column( + children: [ + Text( + 'Your device does not have enough available storage to install this app.', + style: TextStyle( + fontSize: 14, + color: Colors.white.withOpacity(0.75), + height: 1.5, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 20), + + // Storage bar card + ClipRRect( + borderRadius: BorderRadius.circular(16), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), + child: Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.08), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: Colors.white.withOpacity(0.15), + width: 1, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'iPhone Storage', + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w600, + color: Colors.white.withOpacity(0.85), + ), + ), + Text( + '${widget.usedGB} / ${widget.totalGB.toInt()} GB', + style: TextStyle( + fontSize: 12, + color: Colors.white.withOpacity(0.55), + ), + ), + ], + ), + const SizedBox(height: 10), + _StorageBar(usedFraction: usedFraction), + const SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _StorageLegend( + color: const Color(0xFFFF6B6B), + label: 'Used', + value: '${widget.usedGB} GB', + ), + _StorageLegend( + color: Colors.white.withOpacity(0.25), + label: 'Free', + value: + '${freeGB.toStringAsFixed(1)} GB', + ), + ], + ), + ], + ), + ), + ), + ), + ], + ), + ); + } + + Widget _buildDivider() { + return Container( + height: 1, + color: Colors.white.withOpacity(0.12), + ); + } + + Widget _buildActions() { + return IntrinsicHeight( + child: Row( + children: [ + Expanded( + child: _GlassButton( + label: 'OK', + onTap: widget.onDismiss, + isPrimary: false, + ), + ), + VerticalDivider( + width: 1, + color: Colors.white.withOpacity(0.12), + ), + Expanded( + child: _GlassButton( + label: 'Settings', + onTap: widget.onSettings, + isPrimary: true, + ), + ), + ], + ), + ); + } +} + +class _StorageBar extends StatefulWidget { + final double usedFraction; + const _StorageBar({required this.usedFraction}); + + @override + State<_StorageBar> createState() => _StorageBarState(); +} + +class _StorageBarState extends State<_StorageBar> + with SingleTickerProviderStateMixin { + late final AnimationController _ctrl; + late final Animation _anim; + + @override + void initState() { + super.initState(); + _ctrl = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 900), + )..forward(); + _anim = CurvedAnimation(parent: _ctrl, curve: Curves.easeOutCubic); + } + + @override + void dispose() { + _ctrl.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _anim, + builder: (_, __) { + return LayoutBuilder( + builder: (ctx, constraints) { + return Container( + height: 8, + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.1), + borderRadius: BorderRadius.circular(4), + ), + child: Stack( + children: [ + FractionallySizedBox( + widthFactor: _anim.value * widget.usedFraction, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + gradient: const LinearGradient( + colors: [Color(0xFFFF4444), Color(0xFFFF8C00)], + ), + boxShadow: [ + BoxShadow( + color: const Color(0xFFFF4444).withOpacity(0.6), + blurRadius: 6, + ), + ], + ), + ), + ), + ], + ), + ); + }, + ); + }, + ); + } +} + +class _StorageLegend extends StatelessWidget { + final Color color; + final String label; + final String value; + + const _StorageLegend({ + required this.color, + required this.label, + required this.value, + }); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Container( + width: 8, + height: 8, + decoration: BoxDecoration(color: color, shape: BoxShape.circle), + ), + const SizedBox(width: 6), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: TextStyle( + fontSize: 11, + color: Colors.white.withOpacity(0.45), + ), + ), + Text( + value, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Colors.white.withOpacity(0.8), + ), + ), + ], + ), + ], + ); + } +} + +class _GlassButton extends StatefulWidget { + final String label; + final VoidCallback onTap; + final bool isPrimary; + + const _GlassButton({ + required this.label, + required this.onTap, + required this.isPrimary, + }); + + @override + State<_GlassButton> createState() => _GlassButtonState(); +} + +class _GlassButtonState extends State<_GlassButton> { + bool _pressed = false; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTapDown: (_) => setState(() => _pressed = true), + onTapUp: (_) { + setState(() => _pressed = false); + widget.onTap(); + }, + onTapCancel: () => setState(() => _pressed = false), + child: AnimatedContainer( + duration: const Duration(milliseconds: 80), + padding: const EdgeInsets.symmetric(vertical: 16), + decoration: BoxDecoration( + color: _pressed + ? Colors.white.withOpacity(0.12) + : Colors.transparent, + ), + alignment: Alignment.center, + child: Text( + widget.label, + style: TextStyle( + fontSize: 17, + fontWeight: + widget.isPrimary ? FontWeight.w600 : FontWeight.w400, + color: widget.isPrimary + ? const Color(0xFF64B5FF) + : Colors.white.withOpacity(0.75), + ), + ), + ), + ); + } +} \ No newline at end of file From 777baff70b198d54d605c31350a9061ff20faa71 Mon Sep 17 00:00:00 2001 From: AhmedAdelWafdy7 Date: Tue, 7 Apr 2026 23:09:26 +0000 Subject: [PATCH 11/12] feat: implement system storage handling and improve installation error management --- packages/flatpak/example/lib/app.dart | 3 +- .../installation/installation_cubit.dart | 47 +- .../installation/installation_state.dart | 15 + .../system_info/system_info_cubit.dart | 11 + .../system_info/system_info_state.dart | 5 + .../data_sources/flatpak_permission_data.dart | 13 + .../lib/data/models/flatpak_event_model.dart | 9 +- .../data/repositories/flatpak_repository.dart | 1 + .../repositories/flatpak_repository_impl.dart | 10 + .../screens/installed_screen.dart | 239 +++++---- .../widgets/snackbar_listener.dart | 29 +- .../presentation/widgets/storge_error.dart | 503 ++++-------------- 12 files changed, 347 insertions(+), 538 deletions(-) diff --git a/packages/flatpak/example/lib/app.dart b/packages/flatpak/example/lib/app.dart index fbda36a..9caa48d 100644 --- a/packages/flatpak/example/lib/app.dart +++ b/packages/flatpak/example/lib/app.dart @@ -47,7 +47,8 @@ class FlatpakApp extends StatelessWidget { ), BlocProvider( - create: (context) => sl(), + create: (context) => sl()..loadSystemInfo(), + lazy: false, ), ], child: PermissionHandlerWrapper( diff --git a/packages/flatpak/example/lib/business_logic/installation/installation_cubit.dart b/packages/flatpak/example/lib/business_logic/installation/installation_cubit.dart index 437065a..2667f5b 100644 --- a/packages/flatpak/example/lib/business_logic/installation/installation_cubit.dart +++ b/packages/flatpak/example/lib/business_logic/installation/installation_cubit.dart @@ -72,13 +72,15 @@ class InstallationCubit extends Cubit { result.fold( (failure) { debugPrint('[InstallationCubit] Failed to start install: ${failure.message}'); - _cleanup(shortId); - appStatusCubit.updateAppStatus(shortId, AppStatus.notInstalled); - emit(InstallationFailure( - appId: shortId, - error: failure.message, - operation: 'install', - )); + if (state is! InstallationInsufficientSpace) { + _cleanup(shortId); + appStatusCubit.updateAppStatus(shortId, AppStatus.notInstalled); + emit(InstallationFailure( + appId: shortId, + error: failure.message, + operation: 'install', + )); + } }, (success) { debugPrint('[InstallationCubit] Install initiated for $shortId'); @@ -197,7 +199,8 @@ class InstallationCubit extends Cubit { } void _handleTransactionEvent(String appId, FlatpakEventModel event) { - final tracker = _operationTrackers[appId]; + final appIdUtils = AppIdUtils.extractShortId(appId); + final tracker = _operationTrackers[appIdUtils]; if (tracker == null) return; switch (event.type) { @@ -223,10 +226,10 @@ class InstallationCubit extends Cubit { ? AppStatus.updating : AppStatus.installing; - appStatusCubit.updateAppStatus(appId, appStatus, progress: progress); + appStatusCubit.updateAppStatus(appIdUtils, appStatus, progress: progress); emit(InstallationInProgress( - appId: appId, + appId: appIdUtils, status: InstallationStatus.downloading, progress: progress, message: event.message ?? 'Processing...', @@ -238,25 +241,35 @@ class InstallationCubit extends Cubit { break; case FlatpakEventType.installComplete: - appStatusCubit.markInstalled(appId); - _completeOperation(appId, 'install'); + appStatusCubit.markInstalled(appIdUtils); + _completeOperation(appIdUtils, 'install'); break; case FlatpakEventType.uninstallComplete: - appStatusCubit.markUninstalled(appId); - _completeOperation(appId, 'uninstall'); + appStatusCubit.markUninstalled(appIdUtils); + _completeOperation(appIdUtils, 'uninstall'); break; case FlatpakEventType.updateComplete: - appStatusCubit.markUpdated(appId); - _completeOperation(appId, 'update'); + appStatusCubit.markUpdated(appIdUtils); + _completeOperation(appIdUtils, 'update'); break; case FlatpakEventType.installFailed: case FlatpakEventType.uninstallFailed: case FlatpakEventType.updateFailed: final error = event.error ?? event.message ?? 'Unknown error'; - _failOperation(appId, tracker.operationType, error); + _failOperation(appIdUtils, tracker.operationType, error); + break; + + case FlatpakEventType.insufficientSpace: + appStatusCubit.updateAppStatus(appIdUtils, AppStatus.notInstalled); + emit(InstallationInsufficientSpace( + appId: appIdUtils, + requiredMb: event.requiredMb ?? 0, + availableMb: event.availableMb ?? 0, + )); + _cleanup(appIdUtils); break; default: diff --git a/packages/flatpak/example/lib/business_logic/installation/installation_state.dart b/packages/flatpak/example/lib/business_logic/installation/installation_state.dart index f9fa5b9..58e2285 100644 --- a/packages/flatpak/example/lib/business_logic/installation/installation_state.dart +++ b/packages/flatpak/example/lib/business_logic/installation/installation_state.dart @@ -49,3 +49,18 @@ class InstallationFailure extends InstallationState { @override List get props => [appId, error, operation]; } + +class InstallationInsufficientSpace extends InstallationState { + final String appId; + final int requiredMb; + final int availableMb; + + InstallationInsufficientSpace({ + required this.appId, + required this.requiredMb, + required this.availableMb, + }); + + @override + List get props => [appId, requiredMb, availableMb]; +} diff --git a/packages/flatpak/example/lib/business_logic/system_info/system_info_cubit.dart b/packages/flatpak/example/lib/business_logic/system_info/system_info_cubit.dart index e7ba354..dd62c83 100644 --- a/packages/flatpak/example/lib/business_logic/system_info/system_info_cubit.dart +++ b/packages/flatpak/example/lib/business_logic/system_info/system_info_cubit.dart @@ -20,6 +20,7 @@ class SystemInfoCubit extends Cubit { flatpakRepository.getSupportedArches(), flatpakRepository.getSystemInstallations(), flatpakRepository.getUserInstallation(), + flatpakRepository.getSystemStorage(), ]); final versionResult = results[0]; @@ -27,12 +28,14 @@ class SystemInfoCubit extends Cubit { final archesResult = results[2]; final installationsResult = results[3]; final userInstallationResult = results[4]; + final systemStorageResult = results[5]; String version = ''; String defaultArch = ''; List supportedArches = []; List systemInstallations = []; Installation? userInstallation; + Map? systemStorage; versionResult.fold( (failure) => debugPrint( @@ -69,6 +72,13 @@ class SystemInfoCubit extends Cubit { (i) => userInstallation = i as Installation, ); + systemStorageResult.fold( + (failure) => debugPrint( + '[SystemInfoCubit] Failed to get system storage: ${failure.message}', + ), + (s) => systemStorage = s as Map, + ); + emit( SystemInfoLoaded( version: version, @@ -76,6 +86,7 @@ class SystemInfoCubit extends Cubit { supportedArches: supportedArches, systemInstallations: systemInstallations, userInstallation: userInstallation, + systemStorage: systemStorage, ), ); diff --git a/packages/flatpak/example/lib/business_logic/system_info/system_info_state.dart b/packages/flatpak/example/lib/business_logic/system_info/system_info_state.dart index c5368ef..285d556 100644 --- a/packages/flatpak/example/lib/business_logic/system_info/system_info_state.dart +++ b/packages/flatpak/example/lib/business_logic/system_info/system_info_state.dart @@ -22,6 +22,7 @@ class SystemInfoLoaded extends SystemInfoState { final List supportedArches; final List systemInstallations; final Installation? userInstallation; + final Map? systemStorage; const SystemInfoLoaded({ required this.version, @@ -29,6 +30,7 @@ class SystemInfoLoaded extends SystemInfoState { required this.supportedArches, required this.systemInstallations, this.userInstallation, + this.systemStorage, }); @override @@ -38,6 +40,7 @@ class SystemInfoLoaded extends SystemInfoState { supportedArches, systemInstallations, userInstallation, + systemStorage, ]; SystemInfoLoaded copyWith({ @@ -46,6 +49,7 @@ class SystemInfoLoaded extends SystemInfoState { List? supportedArches, List? systemInstallations, Installation? userInstallation, + Map? systemStorage, }) { return SystemInfoLoaded( version: version ?? this.version, @@ -53,6 +57,7 @@ class SystemInfoLoaded extends SystemInfoState { supportedArches: supportedArches ?? this.supportedArches, systemInstallations: systemInstallations ?? this.systemInstallations, userInstallation: userInstallation ?? this.userInstallation, + systemStorage: systemStorage ?? this.systemStorage, ); } } diff --git a/packages/flatpak/example/lib/data/data_sources/flatpak_permission_data.dart b/packages/flatpak/example/lib/data/data_sources/flatpak_permission_data.dart index 25fe419..9b1d9b0 100644 --- a/packages/flatpak/example/lib/data/data_sources/flatpak_permission_data.dart +++ b/packages/flatpak/example/lib/data/data_sources/flatpak_permission_data.dart @@ -121,6 +121,19 @@ class FlatpakPermissionDataSource { } } + Future?> getSystemStorage() async { + try { + final result = await _methodChannel.invokeMethod('getSystemStorage'); + if (result != null && result is Map) { + return Map.from(result); + } + return null; + } catch (e) { + debugPrint('[FlatpakPermissionData] Error getting system storage: $e'); + return null; + } + } + Future grantPermission({ required String appId, required FlatpakPermission permission, diff --git a/packages/flatpak/example/lib/data/models/flatpak_event_model.dart b/packages/flatpak/example/lib/data/models/flatpak_event_model.dart index 1bdd829..40f1ed5 100644 --- a/packages/flatpak/example/lib/data/models/flatpak_event_model.dart +++ b/packages/flatpak/example/lib/data/models/flatpak_event_model.dart @@ -40,6 +40,7 @@ class FlatpakEventModel { final bool? success; final List? operations; final int? availableMb; + final int? requiredMb; FlatpakEventModel({ required this.type, @@ -60,6 +61,7 @@ class FlatpakEventModel { this.success, this.operations, this.availableMb, + this.requiredMb, }); factory FlatpakEventModel.fromMap(Map map) { @@ -74,7 +76,6 @@ class FlatpakEventModel { final typeStr = map['type'] as String?; FlatpakEventType eventType = FlatpakEventType.unknown; - // Parse event type if (typeStr != null) { switch (typeStr) { @@ -168,6 +169,10 @@ class FlatpakEventModel { eventType = FlatpakEventType.transactionReady; break; + case 'insufficient_space': + eventType = FlatpakEventType.insufficientSpace; + break; + default: eventType = FlatpakEventType.unknown; } @@ -206,6 +211,8 @@ class FlatpakEventModel { isMainApp: toBool(map['is_main_app']), success: toBool(map['success']), operations: ops, + availableMb: map['available_mb'] as int?, + requiredMb: map['required_mb'] as int?, ); } diff --git a/packages/flatpak/example/lib/data/repositories/flatpak_repository.dart b/packages/flatpak/example/lib/data/repositories/flatpak_repository.dart index 23c7f27..ab73e7b 100644 --- a/packages/flatpak/example/lib/data/repositories/flatpak_repository.dart +++ b/packages/flatpak/example/lib/data/repositories/flatpak_repository.dart @@ -24,6 +24,7 @@ abstract class FlatpakRepository { Future>> getSupportedArches(); Future>> getSystemInstallations(); Future> getUserInstallation(); + Future>> getSystemStorage(); // Application Discovery Future>> getApplicationsInstalled(); diff --git a/packages/flatpak/example/lib/data/repositories/flatpak_repository_impl.dart b/packages/flatpak/example/lib/data/repositories/flatpak_repository_impl.dart index 242651a..268a8b7 100644 --- a/packages/flatpak/example/lib/data/repositories/flatpak_repository_impl.dart +++ b/packages/flatpak/example/lib/data/repositories/flatpak_repository_impl.dart @@ -103,6 +103,16 @@ class FlatpakRepositoryImpl implements FlatpakRepository { } } + @override + Future>> getSystemStorage() async { + final result = await permissionDataSource.getSystemStorage(); + if (result != null) { + return Right(result); + } else { + return const Left(PlatformFailure("Failed to get storage Info")); + } + } + @override Future>> getApplicationsInstalled() async { try { diff --git a/packages/flatpak/example/lib/presentation/screens/installed_screen.dart b/packages/flatpak/example/lib/presentation/screens/installed_screen.dart index 6a18405..d686718 100644 --- a/packages/flatpak/example/lib/presentation/screens/installed_screen.dart +++ b/packages/flatpak/example/lib/presentation/screens/installed_screen.dart @@ -13,6 +13,10 @@ import '../../business_logic/discovery/discovery_cubit.dart'; import '../../business_logic/installation/installation_cubit.dart'; import '../../business_logic/installation/installation_state.dart'; import '../../business_logic/discovery/discovery_state.dart'; +import '../../business_logic/system_info/system_info_cubit.dart'; +import '../../business_logic/system_info/system_info_state.dart'; +import '../../business_logic/system_info/system_info_cubit.dart'; +import '../../business_logic/system_info/system_info_state.dart'; import '../../data/models/application_model.dart'; import '../../helpers/id_utils.dart'; import '../../app_router.dart'; @@ -73,11 +77,13 @@ class _InstalledScreenState extends State void _loadData() { widget.appStatusCubit.loadAppStatus(); widget.discoveryCubit.loadAllApps(); + context.read().loadSystemInfo(); } void _refreshData() { widget.appStatusCubit.refresh(); widget.discoveryCubit.loadAllApps(); + context.read().refresh(); } @override @@ -384,127 +390,148 @@ class _InstalledScreenState extends State } Widget _buildStorageSection(BuildContext context, List apps) { - final totalBytes = apps.fold(0, (sum, app) => sum + app.installedSize); - final usedGB = (totalBytes / (1024 * 1024 * 1024)).toStringAsFixed(1); - const totalSpace = 200.0; - final used = double.parse(usedGB); - final available = totalSpace - used; - final percentage = ((used / totalSpace) * 100).toInt(); + return BlocBuilder( + builder: (context, systemInfoState) { + double totalSpace = 200.0; + double available = 0.0; + double used = 0.0; + + final appsTotalBytes = apps.fold(0, (sum, app) => sum + app.installedSize); + final appsUsedGB = appsTotalBytes / (1024 * 1024 * 1024); - return GlassContainer( - borderRadius: 24, - blur: 20, - color: Colors.white.withValues(alpha: 0.4), - borderOpacity: 0.4, - child: Padding( - padding: const EdgeInsets.all(24), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( + if (systemInfoState is SystemInfoLoaded && systemInfoState.systemStorage != null) { + final storage = systemInfoState.systemStorage!; + final totalBytesSys = storage['total'] as int? ?? 1; + final availableBytesSys = storage['available'] as int? ?? 0; + + totalSpace = totalBytesSys / (1024.0 * 1024.0 * 1024.0); + available = availableBytesSys / (1024.0 * 1024.0 * 1024.0); + used = totalSpace - available; + } else { + // Fallback if native system info isn't available + used = appsUsedGB; + available = totalSpace - used; + } + + final percentage = totalSpace > 0 ? ((used / totalSpace) * 100).toInt().clamp(0, 100) : 0; + final usedGBStr = used.toStringAsFixed(1); + final availableGBStr = available.toStringAsFixed(1); + + return GlassContainer( + borderRadius: 24, + blur: 20, + color: Colors.white.withValues(alpha: 0.4), + borderOpacity: 0.4, + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Icon(Icons.storage_rounded, size: 20, color: Colors.grey[700]), - const SizedBox(width: 8), - const Text( - 'Storage Usage', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - fontFamily: 'general-sans', - ), - ), - ], - ), - const SizedBox(height: 20), - Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - height: 12, - decoration: BoxDecoration( - color: Colors.grey.withValues(alpha: 0.15), - borderRadius: BorderRadius.circular(6), - boxShadow: [ - BoxShadow( - color: Colors.white.withValues(alpha: 0.5), - offset: const Offset(0, 1), - blurRadius: 0, - spreadRadius: 0, - ) - ] - ), - child: FractionallySizedBox( - alignment: Alignment.centerLeft, - widthFactor: (used / totalSpace).clamp(0.0, 1.0), - child: Container( - decoration: BoxDecoration( - gradient: const LinearGradient( - colors: [Color(0xFF60A5FA), Color(0xFF2563EB)], - ), - borderRadius: BorderRadius.circular(6), - boxShadow: [ - BoxShadow( - color: Colors.blue.withValues(alpha: 0.4), - blurRadius: 6, - offset: const Offset(0, 2), - ) - ], - ), - ), - ), + Row( + children: [ + Icon(Icons.storage_rounded, size: 20, color: Colors.grey[700]), + const SizedBox(width: 8), + const Text( + 'System Storage', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + fontFamily: 'general-sans', ), - const SizedBox(height: 12), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + ), + ], + ), + const SizedBox(height: 20), + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - 'Used: $usedGB GB', - style: TextStyle( - fontSize: 13, - color: Colors.grey[700], - fontWeight: FontWeight.w500, + Container( + height: 12, + decoration: BoxDecoration( + color: Colors.grey.withValues(alpha: 0.15), + borderRadius: BorderRadius.circular(6), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.05), + blurRadius: 4, + offset: const Offset(0, 2), + ) + ] + ), + child: FractionallySizedBox( + alignment: Alignment.centerLeft, + widthFactor: totalSpace > 0 ? (used / totalSpace).clamp(0.0, 1.0) : 0.0, + child: Container( + decoration: BoxDecoration( + gradient: const LinearGradient( + colors: [Color(0xFF60A5FA), Color(0xFF2563EB)], + ), + borderRadius: BorderRadius.circular(6), + boxShadow: [ + BoxShadow( + color: Colors.blue.withValues(alpha: 0.3), + blurRadius: 6, + offset: const Offset(0, 2), + ) + ], + ), + ), ), ), - Text( - 'Free: ${available.toStringAsFixed(1)} GB', - style: TextStyle( - fontSize: 13, - color: Colors.grey[600], - ), + const SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Used: $usedGBStr GB', + style: TextStyle( + color: Colors.grey[800], + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + Text( + 'Free: $availableGBStr GB', + style: const TextStyle( + color: Color(0xFF2563EB), + fontSize: 12, + fontWeight: FontWeight.w600, + ), + ), + ], ), ], ), - ], - ), - ), - const SizedBox(width: 24), - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.white.withValues(alpha: 0.3), - border: Border.all(color: Colors.white.withValues(alpha: 0.5)), - ), - child: Text( - '$percentage%', - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - color: Color(0xFF2563EB), ), - ), + const SizedBox(width: 24), + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.white.withValues(alpha: 0.3), + border: Border.all(color: Colors.white.withValues(alpha: 0.5)), + ), + child: Text( + '${percentage}%', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Color(0xFF2563EB), + ), + ), + ), + ], ), ], ), - ], - ), - ), + ), + ); + } ); } - Widget _buildAppsList(BuildContext context, List apps, {required bool showUpdateButton}) { return Column( children: apps.map((app) => _buildAppItem(context, app, showUpdateButton)).toList(), diff --git a/packages/flatpak/example/lib/presentation/widgets/snackbar_listener.dart b/packages/flatpak/example/lib/presentation/widgets/snackbar_listener.dart index 070f01a..c91d5cd 100644 --- a/packages/flatpak/example/lib/presentation/widgets/snackbar_listener.dart +++ b/packages/flatpak/example/lib/presentation/widgets/snackbar_listener.dart @@ -4,6 +4,8 @@ import '../../business_logic/app_launch/app_launch_cubit.dart'; import '../../business_logic/app_launch/app_launch_state.dart'; import '../../business_logic/installation/installation_cubit.dart'; import '../../business_logic/installation/installation_state.dart'; +import 'storge_error.dart'; +import 'package:flatpak_flutter_example/app_router.dart'; class SnackbarListener extends StatelessWidget { final Widget child; @@ -17,21 +19,44 @@ class SnackbarListener extends StatelessWidget { // Installation events BlocListener( listener: (context, state) { + final navContext = AppRouter.router.routerDelegate.navigatorKey.currentContext ?? context; if (state is InstallationSuccess) { - ScaffoldMessenger.of(context).showSnackBar( + ScaffoldMessenger.of(navContext).showSnackBar( SnackBar( content: Text('${state.operation} completed successfully'), backgroundColor: Colors.green, ), ); } else if (state is InstallationFailure) { - ScaffoldMessenger.of(context).showSnackBar( + ScaffoldMessenger.of(navContext).showSnackBar( SnackBar( content: Text('${state.operation} failed: ${state.error}'), backgroundColor: Colors.red, action: SnackBarAction(label: 'Dismiss', onPressed: () {}), ), ); + } else if (state is InstallationInsufficientSpace) { + final availableGB = state.availableMb / 1024.0; + final requiredGB = state.requiredMb / 1024.0; + + final mockTotalGB = availableGB + requiredGB + 20.0; // 20GB padding + final mockUsedGB = mockTotalGB - availableGB; + + showDialog( + context: navContext, + builder: (dialogContext) { + return StorageErrorDialog( + appName: state.appId, + usedGB: (state.requiredMb - state.availableMb) / 1024, + totalGB: mockTotalGB, + onDismiss: Navigator.of(navContext).pop, + onSettings: () { + // TODO: Handle settings navigation + Navigator.of(navContext).pop(); + }, + ); + } + ); } }, ), diff --git a/packages/flatpak/example/lib/presentation/widgets/storge_error.dart b/packages/flatpak/example/lib/presentation/widgets/storge_error.dart index 7e96714..126fcb5 100644 --- a/packages/flatpak/example/lib/presentation/widgets/storge_error.dart +++ b/packages/flatpak/example/lib/presentation/widgets/storge_error.dart @@ -1,7 +1,6 @@ -import 'dart:ui'; import 'package:flutter/material.dart'; -class StorageErrorDialog extends StatefulWidget { +class StorageErrorDialog extends StatelessWidget { final String appName; final double usedGB; final double totalGB; @@ -16,438 +15,120 @@ class StorageErrorDialog extends StatefulWidget { required this.onDismiss, required this.onSettings, }); - - @override - State createState() => _StorageErrorDialogState(); -} -class _StorageErrorDialogState extends State - with SingleTickerProviderStateMixin { - late final AnimationController _ctrl; - late final Animation _scale; - late final Animation _fade; - - @override - void initState() { - super.initState(); - _ctrl = AnimationController( - vsync: this, - duration: const Duration(milliseconds: 520), - )..forward(); - - _scale = CurvedAnimation(parent: _ctrl, curve: Curves.elasticOut); - _fade = CurvedAnimation(parent: _ctrl, curve: Curves.easeIn); - } - - @override - void dispose() { - _ctrl.dispose(); - super.dispose(); - } - @override Widget build(BuildContext context) { - final screen = MediaQuery.of(context).size; - final dialogWidth = screen.width.clamp(280.0, 400.0); - - return FadeTransition( - opacity: _fade, - child: ScaleTransition( - scale: _scale, - child: SizedBox( - width: dialogWidth, - child: ClipRRect( - borderRadius: BorderRadius.circular(28), - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 28, sigmaY: 28), - child: Container( - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.14), - borderRadius: BorderRadius.circular(28), - border: Border.all( - color: Colors.white.withOpacity(0.28), - width: 1.2, - ), - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - Colors.white.withOpacity(0.22), - Colors.white.withOpacity(0.06), - ], - ), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.35), - blurRadius: 40, - offset: const Offset(0, 20), - ), - ], - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - _buildHeader(), - _buildBody(dialogWidth), - _buildDivider(), - _buildActions(), - ], - ), - ), - ), - ), - ), - ), - ); - } + // Responsive width limiting + final screenWidth = MediaQuery.of(context).size.width; + final dialogWidth = screenWidth > 400 ? 320.0 : screenWidth * 0.85; - Widget _buildHeader() { - return Padding( - padding: const EdgeInsets.fromLTRB(24, 28, 24, 0), - child: Column( - children: [ - // Glowing warning icon - Container( - width: 64, - height: 64, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: const Color(0xFFFF4444).withOpacity(0.18), - border: Border.all( - color: const Color(0xFFFF6B6B).withOpacity(0.5), - width: 1.5, + return Dialog( + backgroundColor: Colors.transparent, + elevation: 0, + insetPadding: const EdgeInsets.symmetric(horizontal: 16), + child: Container( + width: dialogWidth, + padding: const EdgeInsets.fromLTRB(20, 24, 20, 20), + decoration: BoxDecoration( + color: const Color(0xFFF2F2F7), + borderRadius: BorderRadius.circular(24), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.1), + blurRadius: 20, + spreadRadius: 5, + ) + ] + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Cannot Download App', + style: TextStyle( + fontSize: 17, + fontWeight: FontWeight.w600, + color: Colors.black, + letterSpacing: -0.4, ), - boxShadow: [ - BoxShadow( - color: const Color(0xFFFF4444).withOpacity(0.4), - blurRadius: 20, - spreadRadius: 2, + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + Text( + 'There is not enough available storage to download $appName. You can manage your storage in Settings.', + style: const TextStyle( + fontSize: 13, + fontWeight: FontWeight.w400, + color: Colors.black87, + height: 1.3, + letterSpacing: -0.1, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 24), + Row( + children: [ + Expanded( + child: _PillButton( + label: 'Close', + backgroundColor: const Color(0xFFE5E5EA), + textColor: Colors.black, + onTap: onDismiss, + ), + ), + const SizedBox(width: 12), + Expanded( + child: _PillButton( + label: 'Storage', + backgroundColor: const Color(0xFF007AFF), + textColor: Colors.white, + onTap: onSettings, + ), ), ], ), - child: const Icon( - Icons.storage_rounded, - color: Color(0xFFFF6B6B), - size: 30, - ), - ), - const SizedBox(height: 16), - - // Title - Text( - 'Unable to Install', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w700, - color: Colors.white.withOpacity(0.95), - letterSpacing: -0.3, - height: 1.2, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: 4), - Text( - '"${widget.appName}"', - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.w700, - color: Color(0xFFFF8C8C), - letterSpacing: -0.3, - height: 1.2, - ), - textAlign: TextAlign.center, - ), - ], - ), - ); - } - - Widget _buildBody(double dialogWidth) { - final freeGB = widget.totalGB - widget.usedGB; - final usedFraction = widget.usedGB / widget.totalGB; - - return Padding( - padding: const EdgeInsets.fromLTRB(24, 16, 24, 24), - child: Column( - children: [ - Text( - 'Your device does not have enough available storage to install this app.', - style: TextStyle( - fontSize: 14, - color: Colors.white.withOpacity(0.75), - height: 1.5, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: 20), - - // Storage bar card - ClipRRect( - borderRadius: BorderRadius.circular(16), - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), - child: Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.08), - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: Colors.white.withOpacity(0.15), - width: 1, - ), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'iPhone Storage', - style: TextStyle( - fontSize: 13, - fontWeight: FontWeight.w600, - color: Colors.white.withOpacity(0.85), - ), - ), - Text( - '${widget.usedGB} / ${widget.totalGB.toInt()} GB', - style: TextStyle( - fontSize: 12, - color: Colors.white.withOpacity(0.55), - ), - ), - ], - ), - const SizedBox(height: 10), - _StorageBar(usedFraction: usedFraction), - const SizedBox(height: 10), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - _StorageLegend( - color: const Color(0xFFFF6B6B), - label: 'Used', - value: '${widget.usedGB} GB', - ), - _StorageLegend( - color: Colors.white.withOpacity(0.25), - label: 'Free', - value: - '${freeGB.toStringAsFixed(1)} GB', - ), - ], - ), - ], - ), - ), - ), - ), - ], - ), - ); - } - - Widget _buildDivider() { - return Container( - height: 1, - color: Colors.white.withOpacity(0.12), - ); - } - - Widget _buildActions() { - return IntrinsicHeight( - child: Row( - children: [ - Expanded( - child: _GlassButton( - label: 'OK', - onTap: widget.onDismiss, - isPrimary: false, - ), - ), - VerticalDivider( - width: 1, - color: Colors.white.withOpacity(0.12), - ), - Expanded( - child: _GlassButton( - label: 'Settings', - onTap: widget.onSettings, - isPrimary: true, - ), - ), - ], - ), - ); - } -} - -class _StorageBar extends StatefulWidget { - final double usedFraction; - const _StorageBar({required this.usedFraction}); - - @override - State<_StorageBar> createState() => _StorageBarState(); -} - -class _StorageBarState extends State<_StorageBar> - with SingleTickerProviderStateMixin { - late final AnimationController _ctrl; - late final Animation _anim; - - @override - void initState() { - super.initState(); - _ctrl = AnimationController( - vsync: this, - duration: const Duration(milliseconds: 900), - )..forward(); - _anim = CurvedAnimation(parent: _ctrl, curve: Curves.easeOutCubic); - } - - @override - void dispose() { - _ctrl.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return AnimatedBuilder( - animation: _anim, - builder: (_, __) { - return LayoutBuilder( - builder: (ctx, constraints) { - return Container( - height: 8, - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.1), - borderRadius: BorderRadius.circular(4), - ), - child: Stack( - children: [ - FractionallySizedBox( - widthFactor: _anim.value * widget.usedFraction, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - gradient: const LinearGradient( - colors: [Color(0xFFFF4444), Color(0xFFFF8C00)], - ), - boxShadow: [ - BoxShadow( - color: const Color(0xFFFF4444).withOpacity(0.6), - blurRadius: 6, - ), - ], - ), - ), - ), - ], - ), - ); - }, - ); - }, - ); - } -} - -class _StorageLegend extends StatelessWidget { - final Color color; - final String label; - final String value; - - const _StorageLegend({ - required this.color, - required this.label, - required this.value, - }); - - @override - Widget build(BuildContext context) { - return Row( - children: [ - Container( - width: 8, - height: 8, - decoration: BoxDecoration(color: color, shape: BoxShape.circle), - ), - const SizedBox(width: 6), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - label, - style: TextStyle( - fontSize: 11, - color: Colors.white.withOpacity(0.45), - ), - ), - Text( - value, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Colors.white.withOpacity(0.8), - ), - ), ], ), - ], + ), ); } } -class _GlassButton extends StatefulWidget { +class _PillButton extends StatelessWidget { final String label; + final Color backgroundColor; + final Color textColor; final VoidCallback onTap; - final bool isPrimary; - - const _GlassButton({ + + const _PillButton({ required this.label, + required this.backgroundColor, + required this.textColor, required this.onTap, - required this.isPrimary, }); - - @override - State<_GlassButton> createState() => _GlassButtonState(); -} - -class _GlassButtonState extends State<_GlassButton> { - bool _pressed = false; - + @override Widget build(BuildContext context) { - return GestureDetector( - onTapDown: (_) => setState(() => _pressed = true), - onTapUp: (_) { - setState(() => _pressed = false); - widget.onTap(); - }, - onTapCancel: () => setState(() => _pressed = false), - child: AnimatedContainer( - duration: const Duration(milliseconds: 80), - padding: const EdgeInsets.symmetric(vertical: 16), - decoration: BoxDecoration( - color: _pressed - ? Colors.white.withOpacity(0.12) - : Colors.transparent, - ), - alignment: Alignment.center, - child: Text( - widget.label, - style: TextStyle( - fontSize: 17, - fontWeight: - widget.isPrimary ? FontWeight.w600 : FontWeight.w400, - color: widget.isPrimary - ? const Color(0xFF64B5FF) - : Colors.white.withOpacity(0.75), + return Material( + color: backgroundColor, + borderRadius: BorderRadius.circular(24), + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(24), + child: Container( + padding: const EdgeInsets.symmetric(vertical: 14), + alignment: Alignment.center, + child: Text( + label, + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w600, + color: textColor, + letterSpacing: -0.2, + ), ), ), ), ); } -} \ No newline at end of file +} From 7ac3b147c681db0b7eafa65ad03a4cfe99c2f47a Mon Sep 17 00:00:00 2001 From: AhmedAdelWafdy7 Date: Tue, 7 Apr 2026 23:47:52 +0000 Subject: [PATCH 12/12] feat: enhance responsiveness in AppscreenContent, Screenshot, and StorageErrorDialog widgets --- .../widgets/appscreen_content.dart | 8 +- .../widgets/screenshot_widget.dart | 104 +++++++++--------- .../presentation/widgets/storge_error.dart | 47 +++++--- 3 files changed, 87 insertions(+), 72 deletions(-) diff --git a/packages/flatpak/example/lib/presentation/widgets/appscreen_content.dart b/packages/flatpak/example/lib/presentation/widgets/appscreen_content.dart index 58478f4..7f6bf39 100644 --- a/packages/flatpak/example/lib/presentation/widgets/appscreen_content.dart +++ b/packages/flatpak/example/lib/presentation/widgets/appscreen_content.dart @@ -364,10 +364,10 @@ class AppscreenContent extends StatelessWidget { width: double.infinity, height: Responsive.responsiveValue( context, - small: Responsive.height(context) * 0.3, - medium: Responsive.height(context) * 0.4, - large: Responsive.height(context) * 0.5, - ).clamp(300.0, 600.0), + small: Responsive.height(context) * 0.4, + medium: Responsive.height(context) * 0.5, + large: Responsive.height(context) * 0.7, + ).clamp(300.0, 900.0), padding: Responsive.paddingSymmetric( context, horizontal: 32.0, diff --git a/packages/flatpak/example/lib/presentation/widgets/screenshot_widget.dart b/packages/flatpak/example/lib/presentation/widgets/screenshot_widget.dart index 911974d..214fd53 100644 --- a/packages/flatpak/example/lib/presentation/widgets/screenshot_widget.dart +++ b/packages/flatpak/example/lib/presentation/widgets/screenshot_widget.dart @@ -22,59 +22,63 @@ class _screenshotstate extends State { return Column( children: [ Expanded( - child: CarouselSlider( - items: widget.images.map((imagePath) { - return Builder( - builder: (BuildContext context) { - return Container( - width: MediaQuery.of(context).size.width, - margin: Responsive.paddingSymmetric( - context, - horizontal: 6.0, - ), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular( - Responsive.scale(context, 12.0), - ), - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.1), - blurRadius: 8, - offset: Offset(0, 4), + child: LayoutBuilder( + builder: (context, constraints) { + return CarouselSlider( + items: widget.images.map((imagePath) { + return Builder( + builder: (BuildContext context) { + return Container( + width: MediaQuery.of(context).size.width, + margin: Responsive.paddingSymmetric( + context, + horizontal: 6.0, ), - ], - ), - child: ClipRRect( - borderRadius: BorderRadius.circular( - Responsive.scale(context, 12.0), - ), - child: Image.network( - imagePath, - fit: BoxFit.contain, - width: double.infinity, - height: double.infinity, - filterQuality: FilterQuality.high, - ), - ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + Responsive.scale(context, 12.0), + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.1), + blurRadius: 8, + offset: Offset(0, 4), + ), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular( + Responsive.scale(context, 12.0), + ), + child: Image.network( + imagePath, + fit: BoxFit.contain, + width: double.infinity, + height: double.infinity, + filterQuality: FilterQuality.high, + ), + ), + ); + }, ); - }, + }).toList(), + carouselController: _controller, + options: CarouselOptions( + height: constraints.maxHeight, + autoPlay: true, + enlargeCenterPage: true, + viewportFraction: Responsive.isMobile(context) ? 0.9 : 0.8, + autoPlayInterval: Duration(seconds: 4), + autoPlayAnimationDuration: Duration(milliseconds: 800), + autoPlayCurve: Curves.fastOutSlowIn, + onPageChanged: (index, reason) { + setState(() { + _current = index; + }); + }, + ), ); - }).toList(), - carouselController: _controller, - options: CarouselOptions( - autoPlay: true, - enlargeCenterPage: true, - aspectRatio: 16 / 9, - viewportFraction: Responsive.isMobile(context) ? 0.9 : 0.8, - autoPlayInterval: Duration(seconds: 4), - autoPlayAnimationDuration: Duration(milliseconds: 800), - autoPlayCurve: Curves.fastOutSlowIn, - onPageChanged: (index, reason) { - setState(() { - _current = index; - }); - }, - ), + } ), ), if (widget.captions != null && diff --git a/packages/flatpak/example/lib/presentation/widgets/storge_error.dart b/packages/flatpak/example/lib/presentation/widgets/storge_error.dart index 126fcb5..0a8fe7d 100644 --- a/packages/flatpak/example/lib/presentation/widgets/storge_error.dart +++ b/packages/flatpak/example/lib/presentation/widgets/storge_error.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import '../../responsive.dart'; class StorageErrorDialog extends StatelessWidget { final String appName; @@ -19,45 +20,54 @@ class StorageErrorDialog extends StatelessWidget { @override Widget build(BuildContext context) { // Responsive width limiting - final screenWidth = MediaQuery.of(context).size.width; - final dialogWidth = screenWidth > 400 ? 320.0 : screenWidth * 0.85; + final dialogWidth = Responsive.responsiveValue( + context, + small: Responsive.width(context) * 0.85, + medium: Responsive.scale(context, 320.0), + large: Responsive.scale(context, 400.0), + ).clamp(320.0, 800.0); return Dialog( backgroundColor: Colors.transparent, elevation: 0, - insetPadding: const EdgeInsets.symmetric(horizontal: 16), + insetPadding: Responsive.paddingSymmetric(context, horizontal: 16), child: Container( width: dialogWidth, - padding: const EdgeInsets.fromLTRB(20, 24, 20, 20), + padding: EdgeInsets.fromLTRB( + Responsive.scale(context, 20), + Responsive.scale(context, 24), + Responsive.scale(context, 20), + Responsive.scale(context, 20) + ), decoration: BoxDecoration( color: const Color(0xFFF2F2F7), - borderRadius: BorderRadius.circular(24), + borderRadius: BorderRadius.circular(Responsive.scale(context, 24)), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.1), - blurRadius: 20, - spreadRadius: 5, + blurRadius: Responsive.scale(context, 20), + spreadRadius: Responsive.scale(context, 5), ) ] ), child: Column( mainAxisSize: MainAxisSize.min, children: [ - const Text( + Text( 'Cannot Download App', style: TextStyle( - fontSize: 17, + fontSize: Responsive.fontSize(context, 17), fontWeight: FontWeight.w600, color: Colors.black, letterSpacing: -0.4, ), textAlign: TextAlign.center, ), - const SizedBox(height: 8), + SizedBox(height: Responsive.scale(context, 8)), Text( 'There is not enough available storage to download $appName. You can manage your storage in Settings.', - style: const TextStyle( - fontSize: 13, + style: TextStyle( + fontSize: Responsive.fontSize(context, 13), fontWeight: FontWeight.w400, color: Colors.black87, height: 1.3, @@ -65,7 +75,7 @@ class StorageErrorDialog extends StatelessWidget { ), textAlign: TextAlign.center, ), - const SizedBox(height: 24), + SizedBox(height: Responsive.scale(context, 24)), Row( children: [ Expanded( @@ -76,7 +86,7 @@ class StorageErrorDialog extends StatelessWidget { onTap: onDismiss, ), ), - const SizedBox(width: 12), + SizedBox(width: Responsive.scale(context, 12)), Expanded( child: _PillButton( label: 'Storage', @@ -101,6 +111,7 @@ class _PillButton extends StatelessWidget { final VoidCallback onTap; const _PillButton({ + super.key, required this.label, required this.backgroundColor, required this.textColor, @@ -111,17 +122,17 @@ class _PillButton extends StatelessWidget { Widget build(BuildContext context) { return Material( color: backgroundColor, - borderRadius: BorderRadius.circular(24), + borderRadius: BorderRadius.circular(Responsive.scale(context, 24)), child: InkWell( onTap: onTap, - borderRadius: BorderRadius.circular(24), + borderRadius: BorderRadius.circular(Responsive.scale(context, 24)), child: Container( - padding: const EdgeInsets.symmetric(vertical: 14), + padding: EdgeInsets.symmetric(vertical: Responsive.scale(context, 14)), alignment: Alignment.center, child: Text( label, style: TextStyle( - fontSize: 15, + fontSize: Responsive.fontSize(context, 15), fontWeight: FontWeight.w600, color: textColor, letterSpacing: -0.2,