diff --git a/apps/mobile/NEXT_SPRINT_TASKS.md b/apps/mobile/NEXT_SPRINT_TASKS.md new file mode 100644 index 00000000..32bea20f --- /dev/null +++ b/apps/mobile/NEXT_SPRINT_TASKS.md @@ -0,0 +1,9 @@ +## Recommended tasks for the next sprint + + +* In the mobile applications, since the structure is now finalized (at least for the existing features), we need to **strictly follow best practices while coding**: + + * Break down large widgets into **smaller, reusable widgets** + * Add **doc comments** where necessary to improve readability and maintainability + * **Remove overly complicated or unnecessary logic** introduced by AI and simplify where possible + * **Adhere to the design system** and remove all **hard-coded colors and typography**, using shared tokens instead diff --git a/apps/mobile/apps/client/lib/main.dart b/apps/mobile/apps/client/lib/main.dart index 27f4d77d..8acdd045 100644 --- a/apps/mobile/apps/client/lib/main.dart +++ b/apps/mobile/apps/client/lib/main.dart @@ -12,6 +12,7 @@ import 'package:client_hubs/client_hubs.dart' as client_hubs; import 'package:client_create_order/client_create_order.dart' as client_create_order; import 'package:firebase_core/firebase_core.dart'; +import 'package:krow_core/core.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -54,34 +55,38 @@ class AppWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocProvider( - create: (BuildContext context) => - Modular.get(), - child: - BlocBuilder< - core_localization.LocaleBloc, - core_localization.LocaleState - >( - builder: - (BuildContext context, core_localization.LocaleState state) { - return core_localization.TranslationProvider( - child: MaterialApp.router( - debugShowCheckedModeBanner: false, - title: "Krow Client", - theme: UiTheme.light, - routerConfig: Modular.routerConfig, - locale: state.locale, - supportedLocales: state.supportedLocales, - localizationsDelegates: - const >[ - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ], - ), - ); - }, - ), + return WebMobileFrame( + appName: 'KROW Client\nApplication', + logo: Image.asset('assets/logo.png'), + child: BlocProvider( + create: (BuildContext context) => + Modular.get(), + child: + BlocBuilder< + core_localization.LocaleBloc, + core_localization.LocaleState + >( + builder: + (BuildContext context, core_localization.LocaleState state) { + return core_localization.TranslationProvider( + child: MaterialApp.router( + debugShowCheckedModeBanner: false, + title: "Krow Client", + theme: UiTheme.light, + routerConfig: Modular.routerConfig, + locale: state.locale, + supportedLocales: state.supportedLocales, + localizationsDelegates: + const >[ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + ), + ); + }, + ), + ), ); } } diff --git a/apps/mobile/apps/staff/lib/main.dart b/apps/mobile/apps/staff/lib/main.dart index 4852b03d..f4c215c6 100644 --- a/apps/mobile/apps/staff/lib/main.dart +++ b/apps/mobile/apps/staff/lib/main.dart @@ -8,6 +8,7 @@ import 'package:flutter_modular/flutter_modular.dart'; import 'package:staff_authentication/staff_authentication.dart' as staff_authentication; import 'package:staff_main/staff_main.dart' as staff_main; +import 'package:krow_core/core.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -34,33 +35,37 @@ class AppWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocProvider( - create: (BuildContext context) => - Modular.get(), - child: - BlocBuilder< - core_localization.LocaleBloc, - core_localization.LocaleState - >( - builder: - (BuildContext context, core_localization.LocaleState state) { - return core_localization.TranslationProvider( - child: MaterialApp.router( - title: "KROW Staff", - theme: UiTheme.light, - routerConfig: Modular.routerConfig, - locale: state.locale, - supportedLocales: state.supportedLocales, - localizationsDelegates: - const >[ - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ], - ), - ); - }, - ), + return WebMobileFrame( + appName: 'KROW Staff\nApplication', + logo: Image.asset('assets/logo.png'), + child: BlocProvider( + create: (BuildContext context) => + Modular.get(), + child: + BlocBuilder< + core_localization.LocaleBloc, + core_localization.LocaleState + >( + builder: + (BuildContext context, core_localization.LocaleState state) { + return core_localization.TranslationProvider( + child: MaterialApp.router( + title: "KROW Staff", + theme: UiTheme.light, + routerConfig: Modular.routerConfig, + locale: state.locale, + supportedLocales: state.supportedLocales, + localizationsDelegates: + const >[ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + ), + ); + }, + ), + ), ); } } diff --git a/apps/mobile/packages/core/lib/core.dart b/apps/mobile/packages/core/lib/core.dart index c6536ad4..0b9e5ccf 100644 --- a/apps/mobile/packages/core/lib/core.dart +++ b/apps/mobile/packages/core/lib/core.dart @@ -3,3 +3,4 @@ library core; export 'src/domain/arguments/usecase_argument.dart'; export 'src/domain/usecases/usecase.dart'; export 'src/utils/date_time_utils.dart'; +export 'src/presentation/widgets/web_mobile_frame.dart'; diff --git a/apps/mobile/packages/core/lib/src/presentation/widgets/web_mobile_frame.dart b/apps/mobile/packages/core/lib/src/presentation/widgets/web_mobile_frame.dart new file mode 100644 index 00000000..9c636294 --- /dev/null +++ b/apps/mobile/packages/core/lib/src/presentation/widgets/web_mobile_frame.dart @@ -0,0 +1,249 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +/// A wrapper widget that renders the application inside an iPhone-like frame +/// specifically for Flutter Web. On other platforms, it simply returns the child. +class WebMobileFrame extends StatelessWidget { + final Widget child; + final Widget logo; + final String appName; + + const WebMobileFrame({ + super.key, + required this.child, + required this.logo, + required this.appName, + }); + + @override + Widget build(BuildContext context) { + if (!kIsWeb) return child; + + return MaterialApp( + debugShowCheckedModeBanner: false, + theme: ThemeData.dark(), + home: _WebFrameContent( + logo: logo, + appName: appName, + child: child, + ), + ); + } +} + +class _WebFrameContent extends StatefulWidget { + final Widget child; + final Widget logo; + final String appName; + + const _WebFrameContent({ + required this.child, + required this.logo, + required this.appName, + }); + + @override + State<_WebFrameContent> createState() => _WebFrameContentState(); +} + +class _WebFrameContentState extends State<_WebFrameContent> { + // ignore: unused_field + Offset _cursorPosition = Offset.zero; + // ignore: unused_field + bool _isHovering = false; + + @override + Widget build(BuildContext context) { + // iPhone 14 Pro Max-ish dimensions (scaled for frame look) + const double frameWidth = 390 * 1.2; + const double frameHeight = 844 * 1.3; + const double borderRadius = 54.0; + const double borderThickness = 12.0; + + return Scaffold( + backgroundColor: const Color(0xFF121212), + body: MouseRegion( + cursor: SystemMouseCursors.none, + onHover: (event) { + setState(() { + _cursorPosition = event.position; + _isHovering = true; + }); + }, + onExit: (_) => setState(() => _isHovering = false), + child: Stack( + children: [ + // Logo and Title on the left (Web only) + Positioned( + left: 60, + top: 0, + bottom: 0, + child: Center( + child: Opacity( + opacity: 0.5, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 140, + child: widget.logo, + ), + const SizedBox(height: 12), + Text( + widget.appName, + textAlign: TextAlign.left, + style: const TextStyle( + color: Colors.white, + fontSize: 28, + fontWeight: FontWeight.bold, + letterSpacing: -0.5, + fontFamily: 'Instrument Sans', // Fallback if available or system + ), + ), + const SizedBox(height: 4), + Container( + height: 2, + width: 40, + color: Colors.white.withOpacity(0.3), + ), + ], + ), + ), + ), + ), + + // Frame and Content + Center( + child: LayoutBuilder( + builder: (context, constraints) { + // Scale down if screen is too small + double scaleX = constraints.maxWidth / (frameWidth + 80); + double scaleY = constraints.maxHeight / (frameHeight + 80); + double scale = (scaleX < 1 || scaleY < 1) + ? (scaleX < scaleY ? scaleX : scaleY) + : 1.0; + + return Transform.scale( + scale: scale, + child: Container( + width: frameWidth, + height: frameHeight, + decoration: BoxDecoration( + color: Colors.black, + borderRadius: BorderRadius.circular(borderRadius), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.6), + blurRadius: 40, + spreadRadius: 10, + ), + ], + border: Border.all( + color: const Color(0xFF2C2C2C), + width: borderThickness, + ), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular( + borderRadius - borderThickness, + ), + child: Stack( + children: [ + // The actual app + status bar + Column( + children: [ + // Mock iOS Status Bar + Container( + height: 48, + padding: const EdgeInsets.symmetric( + horizontal: 24, + ), + decoration: const BoxDecoration( + color: Color(0xFFF9F6EE), + border: Border( + bottom: BorderSide( + color: Color(0xFFEEEEEE), + width: 0.5, + ), + ), + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + // Time side + const SizedBox( + width: 80, + child: Text( + '9:41 PM', + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.black54, + fontWeight: FontWeight.w700, + fontSize: 14, + letterSpacing: -0.2, + ), + ), + ), + // Status Icons side + const SizedBox( + width: 80, + child: Row( + mainAxisAlignment: + MainAxisAlignment.end, + spacing: 12, + children: [ + Icon( + Icons.signal_cellular_alt, + size: 14, + color: Colors.black54, + ), + Icon( + Icons.wifi, + size: 14, + color: Colors.black54, + ), + Icon( + Icons.battery_full, + size: 14, + color: Colors.black54, + ), + ], + ), + ), + ], + ), + ), + // The main app content content + Expanded(child: widget.child), + ], + ), + + // Dynamic Island / Notch Mockup + Align( + alignment: Alignment.topCenter, + child: Container( + width: 120, + height: 35, + margin: const EdgeInsets.only(top: 10), + decoration: BoxDecoration( + color: Colors.black, + borderRadius: BorderRadius.circular(20), + ), + ), + ), + ], + ), + ), + ), + ); + }, + ), + ), + ], + ), + ), + ); + } +}