diff --git a/README.md b/README.md index 26d69e2..c22d3e5 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,5 @@ Hotelyn logo

The most incredible 🏨 app in the 🌎

+Build Status

⚠️ I'm reimagining and restructuring various aspects of the Hotelyn app. This includes improvements to the user interface, performance optimizations, and the incorporation of exciting new functionalities ⚠️

diff --git a/assets/images/messages/empty.png b/assets/images/messages/empty.png new file mode 100644 index 0000000..f4abeb4 Binary files /dev/null and b/assets/images/messages/empty.png differ diff --git a/ios/Flutter/flutter_export_environment.sh b/ios/Flutter/flutter_export_environment.sh index b5decaf..a0c3889 100755 --- a/ios/Flutter/flutter_export_environment.sh +++ b/ios/Flutter/flutter_export_environment.sh @@ -3,11 +3,12 @@ export "FLUTTER_ROOT=/Users/enzolizama/Development/flutter" export "FLUTTER_APPLICATION_PATH=/Users/enzolizama/Projects/hotelyn" export "COCOAPODS_PARALLEL_CODE_SIGN=true" -export "FLUTTER_TARGET=lib/main.dart" +export "FLUTTER_TARGET=/Users/enzolizama/Projects/hotelyn/lib/main.dart" export "FLUTTER_BUILD_DIR=build" export "FLUTTER_BUILD_NAME=1.0.0" export "FLUTTER_BUILD_NUMBER=1" +export "DART_DEFINES=RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ==,RkxVVFRFUl9XRUJfQ0FOVkFTS0lUX1VSTD1odHRwczovL3d3dy5nc3RhdGljLmNvbS9mbHV0dGVyLWNhbnZhc2tpdC8zZjNlNTYwMjM2NTM5YjdlMjcwMmY1YWM3OTBiMmE0NjkxYjMyZDQ5Lw==" export "DART_OBFUSCATION=false" export "TRACK_WIDGET_CREATION=true" export "TREE_SHAKE_ICONS=false" -export "PACKAGE_CONFIG=.dart_tool/package_config.json" +export "PACKAGE_CONFIG=/Users/enzolizama/Projects/hotelyn/.dart_tool/package_config.json" diff --git a/lib/components/app_bar.dart b/lib/components/app_bar.dart new file mode 100644 index 0000000..cf80704 --- /dev/null +++ b/lib/components/app_bar.dart @@ -0,0 +1,35 @@ +import 'package:flutter/widgets.dart'; +import 'package:hotelyn/components/text_style/hotelyn_text_style.dart'; + +class HotelynHomeAppBar extends StatelessWidget implements PreferredSizeWidget { + const HotelynHomeAppBar({ + super.key, + required this.title, + required this.iconData, + this.onIconPressed, + }); + + final String title; + final IconData iconData; + final VoidCallback? onIconPressed; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(top: 60, left: 20, right: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: HotelynTextStyle.h1, + ), + Icon(iconData, size: 24), + ], + ), + ); + } + + @override + Size get preferredSize => const Size(double.infinity, 120); +} diff --git a/lib/components/text_input/hotelyn_search_input.dart b/lib/components/text_input/hotelyn_search_input.dart new file mode 100644 index 0000000..abb0f56 --- /dev/null +++ b/lib/components/text_input/hotelyn_search_input.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:hotelyn/components/theme/hotelyn_colors.dart'; + +class HotelynSearchInput extends StatelessWidget { + const HotelynSearchInput({ + super.key, + required this.hintText, + }); + + final String hintText; + + final _radius = 30.0; + + @override + Widget build(BuildContext context) { + return TextField( + autofocus: false, + decoration: InputDecoration( + filled: true, + fillColor: HotelynAppColors.lightGrey, + prefixIcon: const Icon(Icons.search), + border: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(_radius)), + borderSide: BorderSide.none, + ), + hintText: hintText, + ), + ); + } +} diff --git a/lib/features/home/home_tab.dart b/lib/features/home/home_tab.dart index 1793e6d..3dfec41 100644 --- a/lib/features/home/home_tab.dart +++ b/lib/features/home/home_tab.dart @@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hotelyn/components/navigation_bar/navigation_bar.dart'; import 'package:hotelyn/components/navigation_bar/navigation_bar_cubit.dart'; import 'package:hotelyn/components/navigation_bar/navigation_bar_state.dart'; +import 'package:hotelyn/features/messages/messages_cubit.dart'; import 'package:hotelyn/features/messages/messages_tab.dart'; import 'package:hotelyn/features/profile/profile_cubit.dart'; import 'package:hotelyn/features/profile/profile_tab.dart'; @@ -25,6 +26,9 @@ class HomePage extends StatelessWidget { BlocProvider( create: (_) => ProfileCubit(), ), + BlocProvider( + create: (_) => MessagesCubit(), + ), ], child: BlocBuilder( builder: (context, state) { @@ -34,7 +38,7 @@ class HomePage extends StatelessWidget { body: [ const HomeTab(), const SearchTab(), - const MesssagesTab(), + const MessagesTab(), const ProfileTab() ].elementAt(index), ); diff --git a/lib/features/home/widgets/home_header.dart b/lib/features/home/widgets/home_header.dart index f2c84a8..211246f 100644 --- a/lib/features/home/widgets/home_header.dart +++ b/lib/features/home/widgets/home_header.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:hotelyn/components/text_input/hotelyn_search_input.dart'; import 'package:hotelyn/components/text_style/hotelyn_text_style.dart'; import 'package:hotelyn/components/theme/hotelyn_colors.dart'; @@ -44,19 +45,7 @@ class HotelynHeader extends SliverPersistentHeaderDelegate { ), SizedBox(height: 32), // TODO: Split in different widget and pass controller - TextField( - autofocus: false, - decoration: InputDecoration( - filled: true, - fillColor: HotelynAppColors.lightGrey, - prefixIcon: Icon(Icons.search), - border: OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(30)), - borderSide: BorderSide.none, - ), - hintText: 'Search hotel', - ), - ), + HotelynSearchInput(hintText: 'Search hotel'), ], ), ), diff --git a/lib/features/messages/messages_cubit.dart b/lib/features/messages/messages_cubit.dart new file mode 100644 index 0000000..39807b8 --- /dev/null +++ b/lib/features/messages/messages_cubit.dart @@ -0,0 +1,10 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:hotelyn/features/messages/messages_state.dart'; + +class MessagesCubit extends Cubit { + MessagesCubit() : super(MessagesEmpty()) { + _load(); + } + + void _load() {} +} diff --git a/lib/features/messages/messages_state.dart b/lib/features/messages/messages_state.dart new file mode 100644 index 0000000..c072f26 --- /dev/null +++ b/lib/features/messages/messages_state.dart @@ -0,0 +1,9 @@ +sealed class MessagesState {} + +class MessagesLoading extends MessagesState {} + +class MessagesEmpty extends MessagesState {} + +class MessagesLoadSuccess extends MessagesState {} + +class MessagesError extends MessagesState {} diff --git a/lib/features/messages/messages_tab.dart b/lib/features/messages/messages_tab.dart index 41c7310..24929b7 100644 --- a/lib/features/messages/messages_tab.dart +++ b/lib/features/messages/messages_tab.dart @@ -1,10 +1,104 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:hotelyn/components/app_bar.dart'; +import 'package:hotelyn/components/text_style/hotelyn_text_style.dart'; +import 'package:hotelyn/features/messages/messages_cubit.dart'; +import 'package:hotelyn/features/messages/messages_state.dart'; -class MesssagesTab extends StatelessWidget { - const MesssagesTab({super.key}); +class MessagesTab extends StatelessWidget { + const MessagesTab({super.key}); + + @override + Widget build(BuildContext context) { + return BlocConsumer( + listener: (context, state) { + // TODO: implement listener + }, + builder: (context, state) { + return Scaffold( + appBar: HotelynHomeAppBar( + title: 'Messages', + iconData: Icons.notifications, + onIconPressed: () {}, + ), + body: Builder( + builder: (context) { + return switch (state) { + MessagesLoading() => const MessagesLoadingScreen(), + MessagesEmpty() => const MessagesEmptyScreen(), + MessagesError() => const MessagesErrorScreen(), + MessagesLoadSuccess() => const MessagesLoadSuccessScreen(), + }; + }, + ), + floatingActionButton: state is MessagesEmpty + ? FloatingActionButton( + onPressed: () {}, + child: const Icon(Icons.message_rounded), + ) + : null, + ); + }, + ); + } +} + +class MessagesLoadSuccessScreen extends StatelessWidget { + const MessagesLoadSuccessScreen({super.key}); + + @override + Widget build(BuildContext context) { + return const Placeholder(); + } +} + +class MessagesErrorScreen extends StatelessWidget { + const MessagesErrorScreen({super.key}); @override Widget build(BuildContext context) { return const Placeholder(); } } + +class MessagesLoadingScreen extends StatelessWidget { + const MessagesLoadingScreen({super.key}); + + @override + Widget build(BuildContext context) { + return const Center( + child: CircularProgressIndicator(), + ); + } +} + +class MessagesEmptyScreen extends StatelessWidget { + const MessagesEmptyScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + 'assets/images/messages/empty.png', + width: 200, + height: 200, + ), + const SizedBox(height: 30), + const Text( + 'No Messages Here', + style: HotelynTextStyle.h1, + ), + const SizedBox(height: 12), + const Text( + 'Lets start messaging with others or with seller', + style: HotelynTextStyle.description, + ), + ], + ), + ); + } +} diff --git a/lib/features/profile/profile_tab.dart b/lib/features/profile/profile_tab.dart index 784f3c4..37182f5 100644 --- a/lib/features/profile/profile_tab.dart +++ b/lib/features/profile/profile_tab.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:hotelyn/components/app_bar.dart'; import 'package:hotelyn/components/avatar/hotelyn_avatar.dart'; import 'package:hotelyn/components/text_style/hotelyn_text_style.dart'; import 'package:hotelyn/features/profile/profile_cubit.dart'; @@ -16,7 +17,11 @@ class ProfileTab extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(), + appBar: HotelynHomeAppBar( + title: 'Profile', + iconData: Icons.settings, + onIconPressed: () {}, + ), body: BlocBuilder( builder: (context, state) { return switch (state) { @@ -54,7 +59,6 @@ class ProfileDataScreen extends StatelessWidget { child: SingleChildScrollView( child: Column( children: [ - ProfileTabAppBar(), SizedBox(height: 32), ProfileUserInformationSection(), SizedBox(height: 24), @@ -85,18 +89,3 @@ class ProfileUserInformationSection extends StatelessWidget { ); } } - -class ProfileTabAppBar extends StatelessWidget { - const ProfileTabAppBar({super.key}); - - @override - Widget build(BuildContext context) { - return const Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text('Profile', style: HotelynTextStyle.h1), - Icon(Icons.settings_outlined), - ], - ); - } -} diff --git a/pubspec.yaml b/pubspec.yaml index 43fdd51..14e1a3a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -41,6 +41,7 @@ flutter: - assets/images/ - assets/images/hotelyn/ - assets/images/onboarding/ + - assets/images/messages/ - assets/icons/ fonts: diff --git a/test/features/messages/messages_cubit_test.dart b/test/features/messages/messages_cubit_test.dart new file mode 100644 index 0000000..d8ce63d --- /dev/null +++ b/test/features/messages/messages_cubit_test.dart @@ -0,0 +1,12 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:hotelyn/features/messages/messages_cubit.dart'; +import 'package:hotelyn/features/messages/messages_state.dart'; + +void main() { + group('MessagesCubit', () { + test('constructor', () { + final cubit = MessagesCubit(); + expect(cubit.state, isA()); + }); + }); +} diff --git a/test/features/messages/messages_tab_test.dart b/test/features/messages/messages_tab_test.dart new file mode 100644 index 0000000..3a1a175 --- /dev/null +++ b/test/features/messages/messages_tab_test.dart @@ -0,0 +1,84 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:hotelyn/features/messages/messages_cubit.dart'; +import 'package:hotelyn/features/messages/messages_state.dart'; +import 'package:hotelyn/features/messages/messages_tab.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockMessagesCubit extends Mock implements MessagesCubit {} + +void main() { + group('MessagesTab::', () { + late MessagesCubit cubit; + + setUp(() { + cubit = MockMessagesCubit(); + }); + + Future buildWidget(WidgetTester tester) async { + await tester.pumpWidget( + BlocProvider.value( + value: cubit, + child: const MaterialApp( + home: MessagesTab(), + ), + ), + ); + } + + testWidgets('Messages tab when state is MessagesEmpty', (tester) async { + final state = MessagesEmpty(); + whenListen( + cubit, + Stream.fromIterable([]), + initialState: state, + ); + await buildWidget(tester); + final screenType = find.byType(MessagesEmptyScreen); + expect(cubit.state, isA()); + expect(screenType, findsOneWidget); + }); + + testWidgets('Messages tab when state is MessagesLoading', (tester) async { + final state = MessagesLoading(); + whenListen( + cubit, + Stream.fromIterable([]), + initialState: state, + ); + await buildWidget(tester); + final screenType = find.byType(MessagesLoadingScreen); + expect(cubit.state, isA()); + expect(screenType, findsOneWidget); + }); + + testWidgets('Messages tab when state is MessagesError', (tester) async { + final state = MessagesError(); + whenListen( + cubit, + Stream.fromIterable([]), + initialState: state, + ); + await buildWidget(tester); + final screenType = find.byType(MessagesErrorScreen); + expect(cubit.state, isA()); + expect(screenType, findsOneWidget); + }); + + testWidgets('Messages tab when state is MessagesLoadSuccess', + (tester) async { + final state = MessagesLoadSuccess(); + whenListen( + cubit, + Stream.fromIterable([]), + initialState: state, + ); + await buildWidget(tester); + final screenType = find.byType(MessagesLoadSuccessScreen); + expect(cubit.state, isA()); + expect(screenType, findsOneWidget); + }); + }); +}