Move apps to mobile directory structure
Relocated all app directories (client, design_system_viewer, staff) and their contents under the new 'apps/mobile' path. This change improves project organization and prepares for future platform-specific structuring.
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||
import 'src/data/repositories_impl/settings_repository_impl.dart';
|
||||
import 'src/domain/repositories/settings_repository_interface.dart';
|
||||
import 'src/domain/usecases/sign_out_usecase.dart';
|
||||
import 'src/presentation/blocs/client_settings_bloc.dart';
|
||||
import 'src/presentation/pages/client_settings_page.dart';
|
||||
|
||||
/// A [Module] for the client settings feature.
|
||||
class ClientSettingsModule extends Module {
|
||||
@override
|
||||
List<Module> get imports => [DataConnectModule()];
|
||||
|
||||
@override
|
||||
void binds(Injector i) {
|
||||
// Repositories
|
||||
i.addLazySingleton<SettingsRepositoryInterface>(
|
||||
() => SettingsRepositoryImpl(mock: i.get<AuthRepositoryMock>()),
|
||||
);
|
||||
|
||||
// UseCases
|
||||
i.addLazySingleton(SignOutUseCase.new);
|
||||
|
||||
// BLoCs
|
||||
i.add<ClientSettingsBloc>(
|
||||
() => ClientSettingsBloc(signOutUseCase: i.get<SignOutUseCase>()),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void routes(r) {
|
||||
r.child('/', child: (_) => const ClientSettingsPage());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||
import '../../domain/repositories/settings_repository_interface.dart';
|
||||
|
||||
/// Implementation of [SettingsRepositoryInterface].
|
||||
///
|
||||
/// This implementation delegates data access to the [AuthRepositoryMock]
|
||||
/// from the `data_connect` package.
|
||||
class SettingsRepositoryImpl implements SettingsRepositoryInterface {
|
||||
/// The auth mock from data connect.
|
||||
final AuthRepositoryMock mock;
|
||||
|
||||
/// Creates a [SettingsRepositoryImpl] with the required [mock].
|
||||
SettingsRepositoryImpl({required this.mock});
|
||||
|
||||
@override
|
||||
Future<void> signOut() {
|
||||
return mock.signOut();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/// Interface for the Client Settings repository.
|
||||
///
|
||||
/// This repository handles settings-related operations such as user sign out.
|
||||
abstract interface class SettingsRepositoryInterface {
|
||||
/// Signs out the current user from the application.
|
||||
Future<void> signOut();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
import '../repositories/settings_repository_interface.dart';
|
||||
|
||||
/// Use case handles the user sign out process.
|
||||
///
|
||||
/// This use case delegates the sign out logic to the [SettingsRepositoryInterface].
|
||||
class SignOutUseCase implements NoInputUseCase<void> {
|
||||
final SettingsRepositoryInterface _repository;
|
||||
|
||||
/// Creates a [SignOutUseCase].
|
||||
///
|
||||
/// Requires a [SettingsRepositoryInterface] to perform the sign out operation.
|
||||
SignOutUseCase(this._repository);
|
||||
|
||||
@override
|
||||
Future<void> call() {
|
||||
return _repository.signOut();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../domain/usecases/sign_out_usecase.dart';
|
||||
|
||||
part 'client_settings_event.dart';
|
||||
part 'client_settings_state.dart';
|
||||
|
||||
/// BLoC to manage client settings and profile state.
|
||||
class ClientSettingsBloc
|
||||
extends Bloc<ClientSettingsEvent, ClientSettingsState> {
|
||||
final SignOutUseCase _signOutUseCase;
|
||||
|
||||
ClientSettingsBloc({required SignOutUseCase signOutUseCase})
|
||||
: _signOutUseCase = signOutUseCase,
|
||||
super(const ClientSettingsInitial()) {
|
||||
on<ClientSettingsSignOutRequested>(_onSignOutRequested);
|
||||
}
|
||||
|
||||
Future<void> _onSignOutRequested(
|
||||
ClientSettingsSignOutRequested event,
|
||||
Emitter<ClientSettingsState> emit,
|
||||
) async {
|
||||
emit(const ClientSettingsLoading());
|
||||
try {
|
||||
await _signOutUseCase();
|
||||
emit(const ClientSettingsSignOutSuccess());
|
||||
} catch (e) {
|
||||
emit(ClientSettingsError(e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
part of 'client_settings_bloc.dart';
|
||||
|
||||
abstract class ClientSettingsEvent extends Equatable {
|
||||
const ClientSettingsEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class ClientSettingsSignOutRequested extends ClientSettingsEvent {
|
||||
const ClientSettingsSignOutRequested();
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
part of 'client_settings_bloc.dart';
|
||||
|
||||
abstract class ClientSettingsState extends Equatable {
|
||||
const ClientSettingsState();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class ClientSettingsInitial extends ClientSettingsState {
|
||||
const ClientSettingsInitial();
|
||||
}
|
||||
|
||||
class ClientSettingsLoading extends ClientSettingsState {
|
||||
const ClientSettingsLoading();
|
||||
}
|
||||
|
||||
class ClientSettingsSignOutSuccess extends ClientSettingsState {
|
||||
const ClientSettingsSignOutSuccess();
|
||||
}
|
||||
|
||||
class ClientSettingsError extends ClientSettingsState {
|
||||
final String message;
|
||||
|
||||
const ClientSettingsError(this.message);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
|
||||
/// Extension on [IModularNavigator] to provide strongly-typed navigation
|
||||
/// for the client settings feature.
|
||||
extension ClientSettingsNavigator on IModularNavigator {
|
||||
/// Navigates to the client settings page.
|
||||
void pushClientSettings() {
|
||||
pushNamed('/client/settings/');
|
||||
}
|
||||
|
||||
/// Navigates to the hubs page.
|
||||
void pushHubs() {
|
||||
pushNamed('/client/hubs');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import '../blocs/client_settings_bloc.dart';
|
||||
import '../widgets/client_settings_page/settings_actions.dart';
|
||||
import '../widgets/client_settings_page/settings_profile_header.dart';
|
||||
import '../widgets/client_settings_page/settings_quick_links.dart';
|
||||
|
||||
/// Page for client settings and profile management.
|
||||
///
|
||||
/// This page follows the KROW architecture by being a [StatelessWidget]
|
||||
/// and delegating its state management to [ClientSettingsBloc] and its
|
||||
/// UI sections to specialized sub-widgets.
|
||||
class ClientSettingsPage extends StatelessWidget {
|
||||
/// Creates a [ClientSettingsPage].
|
||||
const ClientSettingsPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<ClientSettingsBloc>(
|
||||
create: (context) => Modular.get<ClientSettingsBloc>(),
|
||||
child: BlocListener<ClientSettingsBloc, ClientSettingsState>(
|
||||
listener: (context, state) {
|
||||
if (state is ClientSettingsSignOutSuccess) {
|
||||
Modular.to.navigate('/');
|
||||
}
|
||||
if (state is ClientSettingsError) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(state.message)));
|
||||
}
|
||||
},
|
||||
child: const Scaffold(
|
||||
backgroundColor: UiColors.background,
|
||||
body: CustomScrollView(
|
||||
slivers: [
|
||||
SettingsProfileHeader(),
|
||||
SettingsActions(),
|
||||
SettingsQuickLinks(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../blocs/client_settings_bloc.dart';
|
||||
|
||||
/// A widget that displays the primary actions for the settings page.
|
||||
class SettingsActions extends StatelessWidget {
|
||||
/// Creates a [SettingsActions].
|
||||
const SettingsActions({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final labels = t.client_settings.profile;
|
||||
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: UiConstants.space5),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildListDelegate([
|
||||
const SizedBox(height: UiConstants.space5),
|
||||
UiButton.primary(text: labels.edit_profile, onPressed: () {}),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
UiButton.primary(text: labels.hubs, onPressed: () {}),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
BlocBuilder<ClientSettingsBloc, ClientSettingsState>(
|
||||
builder: (context, state) {
|
||||
return UiButton.secondary(
|
||||
text: labels.log_out,
|
||||
onPressed: state is ClientSettingsLoading
|
||||
? null
|
||||
: () => BlocProvider.of<ClientSettingsBloc>(
|
||||
context,
|
||||
).add(const ClientSettingsSignOutRequested()),
|
||||
);
|
||||
},
|
||||
),
|
||||
]),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
|
||||
/// A widget that displays the profile header with avatar and company info.
|
||||
class SettingsProfileHeader extends StatelessWidget {
|
||||
/// Creates a [SettingsProfileHeader].
|
||||
const SettingsProfileHeader({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final labels = t.client_settings.profile;
|
||||
|
||||
return SliverAppBar(
|
||||
backgroundColor: UiColors.primary,
|
||||
expandedHeight: 200,
|
||||
pinned: true,
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
background: Container(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [UiColors.primary, Color(0xFF0047FF)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(height: UiConstants.space5 * 2),
|
||||
Container(
|
||||
width: 80,
|
||||
height: 80,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: UiColors.white.withValues(alpha: 0.24),
|
||||
width: 4,
|
||||
),
|
||||
color: UiColors.white,
|
||||
),
|
||||
child: const Center(
|
||||
child: Text(
|
||||
'C',
|
||||
style: TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: UiColors.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
Text(
|
||||
'Your Company',
|
||||
style: UiTypography.body1b.copyWith(color: UiColors.white),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space1),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
UiIcons.mail,
|
||||
size: 14,
|
||||
color: UiColors.white.withValues(alpha: 0.7),
|
||||
),
|
||||
const SizedBox(width: UiConstants.space2),
|
||||
Text(
|
||||
'client@example.com',
|
||||
style: UiTypography.footnote1r.copyWith(
|
||||
color: UiColors.white.withValues(alpha: 0.7),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
leading: IconButton(
|
||||
icon: const Icon(UiIcons.arrowLeft, color: UiColors.white),
|
||||
onPressed: () => Modular.to.pop(),
|
||||
),
|
||||
title: Text(
|
||||
labels.title,
|
||||
style: UiTypography.body1b.copyWith(color: UiColors.white),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import '../../navigation/client_settings_navigator.dart';
|
||||
|
||||
/// A widget that displays a list of quick links in a card.
|
||||
class SettingsQuickLinks extends StatelessWidget {
|
||||
/// Creates a [SettingsQuickLinks].
|
||||
const SettingsQuickLinks({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final labels = t.client_settings.profile;
|
||||
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: Card(
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
side: const BorderSide(color: UiColors.border),
|
||||
),
|
||||
color: UiColors.white,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(UiConstants.space4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
labels.quick_links,
|
||||
style: UiTypography.footnote1b.textPrimary,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
_QuickLinkItem(
|
||||
icon: UiIcons.nfc,
|
||||
title: labels.clock_in_hubs,
|
||||
onTap: () => Modular.to.pushHubs(),
|
||||
),
|
||||
_QuickLinkItem(
|
||||
icon: UiIcons.building,
|
||||
title: labels.billing_payments,
|
||||
onTap: () {},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal widget for a single quick link item.
|
||||
class _QuickLinkItem extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _QuickLinkItem({
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: UiConstants.radiusMd,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: UiConstants.space3,
|
||||
horizontal: UiConstants.space2,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(icon, size: 20, color: UiColors.iconSecondary),
|
||||
const SizedBox(width: UiConstants.space3),
|
||||
Text(title, style: UiTypography.footnote1m.textPrimary),
|
||||
],
|
||||
),
|
||||
const Icon(
|
||||
UiIcons.chevronRight,
|
||||
size: 20,
|
||||
color: UiColors.iconThird,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
37
apps/mobile/packages/features/client/settings/pubspec.yaml
Normal file
37
apps/mobile/packages/features/client/settings/pubspec.yaml
Normal file
@@ -0,0 +1,37 @@
|
||||
name: client_settings
|
||||
description: Settings and profile screen for the client application.
|
||||
version: 0.0.1
|
||||
publish_to: none
|
||||
resolution: workspace
|
||||
|
||||
environment:
|
||||
sdk: '>=3.10.0 <4.0.0'
|
||||
flutter: ">=3.0.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_bloc: ^8.1.0
|
||||
flutter_modular: ^6.3.0
|
||||
equatable: ^2.0.5
|
||||
lucide_icons: ^0.257.0
|
||||
|
||||
design_system:
|
||||
path: ../../../design_system
|
||||
core_localization:
|
||||
path: ../../../core_localization
|
||||
krow_core:
|
||||
path: ../../../core
|
||||
krow_domain:
|
||||
path: ../../../domain
|
||||
krow_data_connect:
|
||||
path: ../../../data_connect
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
bloc_test: ^9.1.0
|
||||
mocktail: ^1.0.0
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
Reference in New Issue
Block a user