diff --git a/apps/mobile/packages/core/lib/src/routing/staff/navigator.dart b/apps/mobile/packages/core/lib/src/routing/staff/navigator.dart index 1269484c..1f63eb9a 100644 --- a/apps/mobile/packages/core/lib/src/routing/staff/navigator.dart +++ b/apps/mobile/packages/core/lib/src/routing/staff/navigator.dart @@ -284,13 +284,24 @@ extension StaffNavigator on IModularNavigator { pushNamed(StaffPaths.faqs); } - /// Pushes the privacy and security settings page. + // ========================================================================== + // PRIVACY & SECURITY + // ========================================================================== + + /// Navigates to the privacy and security settings page. /// - /// Manage privacy preferences and security settings. - void toPrivacy() { - pushNamed(StaffPaths.privacy); + /// Manage privacy preferences including: + /// * Location sharing settings + /// * View terms of service + /// * View privacy policy + void toPrivacySecurity() { + pushNamed(StaffPaths.privacySecurity); } + // ========================================================================== + // MESSAGING & COMMUNICATION + // ========================================================================== + /// Pushes the messages page (placeholder). /// /// Access internal messaging system. diff --git a/apps/mobile/packages/core/lib/src/routing/staff/route_paths.dart b/apps/mobile/packages/core/lib/src/routing/staff/route_paths.dart index 1b49991c..52014858 100644 --- a/apps/mobile/packages/core/lib/src/routing/staff/route_paths.dart +++ b/apps/mobile/packages/core/lib/src/routing/staff/route_paths.dart @@ -205,8 +205,19 @@ class StaffPaths { /// FAQs - frequently asked questions. static const String faqs = '/faqs'; + // ========================================================================== + // PRIVACY & SECURITY + // ========================================================================== + /// Privacy and security settings. - static const String privacy = '/privacy'; + /// + /// Manage privacy preferences, location sharing, terms of service, + /// and privacy policy. + static const String privacySecurity = '/worker-main/privacy-security/'; + + // ========================================================================== + // MESSAGING & COMMUNICATION (Placeholders) + // ========================================================================== /// Messages - internal messaging system (placeholder). static const String messages = '/messages'; diff --git a/apps/mobile/packages/core_localization/lib/src/l10n/en.i18n.json b/apps/mobile/packages/core_localization/lib/src/l10n/en.i18n.json index 0241ab37..ab54d771 100644 --- a/apps/mobile/packages/core_localization/lib/src/l10n/en.i18n.json +++ b/apps/mobile/packages/core_localization/lib/src/l10n/en.i18n.json @@ -1125,6 +1125,21 @@ "service_unavailable": "Service is currently unavailable." } }, + "staff_privacy_security": { + "title": "Privacy & Security", + "privacy_section": "Privacy", + "legal_section": "Legal", + "location_sharing": { + "title": "Location Sharing", + "subtitle": "Share location during shifts" + }, + "terms_of_service": { + "title": "Terms of Service" + }, + "privacy_policy": { + "title": "Privacy Policy" + } + }, "success": { "hub": { "created": "Hub created successfully!", diff --git a/apps/mobile/packages/core_localization/lib/src/l10n/es.i18n.json b/apps/mobile/packages/core_localization/lib/src/l10n/es.i18n.json index ee54965e..e537d3da 100644 --- a/apps/mobile/packages/core_localization/lib/src/l10n/es.i18n.json +++ b/apps/mobile/packages/core_localization/lib/src/l10n/es.i18n.json @@ -1125,6 +1125,21 @@ "service_unavailable": "El servicio no está disponible actualmente." } }, + "staff_privacy_security": { + "title": "Privacidad y Seguridad", + "privacy_section": "Privacidad", + "legal_section": "Legal", + "location_sharing": { + "title": "Compartir Ubicación", + "subtitle": "Compartir ubicación durante turnos" + }, + "terms_of_service": { + "title": "Términos de Servicio" + }, + "privacy_policy": { + "title": "Política de Privacidad" + } + }, "success": { "hub": { "created": "¡Hub creado exitosamente!", diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/data/repositories_impl/privacy_settings_repository_impl.dart b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/data/repositories_impl/privacy_settings_repository_impl.dart new file mode 100644 index 00000000..de24e493 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/data/repositories_impl/privacy_settings_repository_impl.dart @@ -0,0 +1,65 @@ +import 'package:krow_data_connect/krow_data_connect.dart'; + +import '../../domain/entities/privacy_settings_entity.dart'; +import '../../domain/repositories/privacy_settings_repository_interface.dart'; + +/// Data layer implementation of privacy settings repository +/// +/// Handles all backend communication for privacy settings, +/// using DataConnectService for automatic auth and token refresh +class PrivacySettingsRepositoryImpl + implements PrivacySettingsRepositoryInterface { + final DataConnectService _service; + + PrivacySettingsRepositoryImpl(this._service); + + @override + Future getPrivacySettings() async { + return _service.run( + () async { + // TODO: Call Data Connect query to fetch privacy settings + // For now, return default settings + return PrivacySettingsEntity( + locationSharing: true, + updatedAt: DateTime.now(), + ); + }, + ); + } + + @override + Future updateLocationSharing(bool enabled) async { + return _service.run( + () async { + // TODO: Call Data Connect mutation to update location sharing preference + // For now, return updated settings + return PrivacySettingsEntity( + locationSharing: enabled, + updatedAt: DateTime.now(), + ); + }, + ); + } + + @override + Future getTermsOfService() async { + return _service.run( + () async { + // TODO: Call Data Connect query to fetch terms of service content + // For now, return placeholder + return 'Terms of Service Content'; + }, + ); + } + + @override + Future getPrivacyPolicy() async { + return _service.run( + () async { + // TODO: Call Data Connect query to fetch privacy policy content + // For now, return placeholder + return 'Privacy Policy Content'; + }, + ); + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/domain/entities/privacy_settings_entity.dart b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/domain/entities/privacy_settings_entity.dart new file mode 100644 index 00000000..aad50058 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/domain/entities/privacy_settings_entity.dart @@ -0,0 +1,29 @@ +import 'package:equatable/equatable.dart'; + +/// Privacy settings entity representing user privacy preferences +class PrivacySettingsEntity extends Equatable { + /// Whether location sharing during shifts is enabled + final bool locationSharing; + + /// The timestamp when these settings were last updated + final DateTime? updatedAt; + + const PrivacySettingsEntity({ + required this.locationSharing, + this.updatedAt, + }); + + /// Create a copy with optional field overrides + PrivacySettingsEntity copyWith({ + bool? locationSharing, + DateTime? updatedAt, + }) { + return PrivacySettingsEntity( + locationSharing: locationSharing ?? this.locationSharing, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + List get props => [locationSharing, updatedAt]; +} diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/domain/repositories/privacy_settings_repository_interface.dart b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/domain/repositories/privacy_settings_repository_interface.dart new file mode 100644 index 00000000..666cc0b9 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/domain/repositories/privacy_settings_repository_interface.dart @@ -0,0 +1,18 @@ +import '../entities/privacy_settings_entity.dart'; + +/// Interface for privacy settings repository operations +abstract class PrivacySettingsRepositoryInterface { + /// Fetch the current user's privacy settings + Future getPrivacySettings(); + + /// Update location sharing preference + /// + /// Returns the updated privacy settings + Future updateLocationSharing(bool enabled); + + /// Fetch terms of service content + Future getTermsOfService(); + + /// Fetch privacy policy content + Future getPrivacyPolicy(); +} diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/domain/usecases/get_privacy_policy_usecase.dart b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/domain/usecases/get_privacy_policy_usecase.dart new file mode 100644 index 00000000..f7d5fae4 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/domain/usecases/get_privacy_policy_usecase.dart @@ -0,0 +1,17 @@ +import '../repositories/privacy_settings_repository_interface.dart'; + +/// Use case to retrieve privacy policy +class GetPrivacyPolicyUseCase { + final PrivacySettingsRepositoryInterface _repository; + + GetPrivacyPolicyUseCase(this._repository); + + /// Execute the use case to get privacy policy + Future call() async { + try { + return await _repository.getPrivacyPolicy(); + } catch (e) { + return 'Privacy Policy is currently unavailable.'; + } + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/domain/usecases/get_privacy_settings_usecase.dart b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/domain/usecases/get_privacy_settings_usecase.dart new file mode 100644 index 00000000..f3066bcb --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/domain/usecases/get_privacy_settings_usecase.dart @@ -0,0 +1,22 @@ +import '../entities/privacy_settings_entity.dart'; +import '../repositories/privacy_settings_repository_interface.dart'; + +/// Use case to retrieve the current user's privacy settings +class GetPrivacySettingsUseCase { + final PrivacySettingsRepositoryInterface _repository; + + GetPrivacySettingsUseCase(this._repository); + + /// Execute the use case to get privacy settings + Future call() async { + try { + return await _repository.getPrivacySettings(); + } catch (e) { + // Return default settings on error + return PrivacySettingsEntity( + locationSharing: true, + updatedAt: DateTime.now(), + ); + } + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/domain/usecases/get_terms_usecase.dart b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/domain/usecases/get_terms_usecase.dart new file mode 100644 index 00000000..5a68b8b3 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/domain/usecases/get_terms_usecase.dart @@ -0,0 +1,17 @@ +import '../repositories/privacy_settings_repository_interface.dart'; + +/// Use case to retrieve terms of service +class GetTermsUseCase { + final PrivacySettingsRepositoryInterface _repository; + + GetTermsUseCase(this._repository); + + /// Execute the use case to get terms of service + Future call() async { + try { + return await _repository.getTermsOfService(); + } catch (e) { + return 'Terms of Service is currently unavailable.'; + } + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/domain/usecases/update_location_sharing_usecase.dart b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/domain/usecases/update_location_sharing_usecase.dart new file mode 100644 index 00000000..2ee00d33 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/domain/usecases/update_location_sharing_usecase.dart @@ -0,0 +1,35 @@ +import 'package:equatable/equatable.dart'; + +import '../entities/privacy_settings_entity.dart'; +import '../repositories/privacy_settings_repository_interface.dart'; + +/// Parameters for updating location sharing +class UpdateLocationSharingParams extends Equatable { + /// Whether to enable or disable location sharing + final bool enabled; + + const UpdateLocationSharingParams({required this.enabled}); + + @override + List get props => [enabled]; +} + +/// Use case to update location sharing preference +class UpdateLocationSharingUseCase { + final PrivacySettingsRepositoryInterface _repository; + + UpdateLocationSharingUseCase(this._repository); + + /// Execute the use case to update location sharing + Future call(UpdateLocationSharingParams params) async { + try { + return await _repository.updateLocationSharing(params.enabled); + } catch (e) { + // Return current settings on error + return PrivacySettingsEntity( + locationSharing: params.enabled, + updatedAt: DateTime.now(), + ); + } + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/blocs/privacy_security_bloc.dart b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/blocs/privacy_security_bloc.dart new file mode 100644 index 00000000..70b51944 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/blocs/privacy_security_bloc.dart @@ -0,0 +1,135 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:equatable/equatable.dart'; + +import '../../domain/entities/privacy_settings_entity.dart'; +import '../../domain/repositories/privacy_settings_repository_interface.dart'; +import '../../domain/usecases/get_privacy_settings_usecase.dart'; +import '../../domain/usecases/update_location_sharing_usecase.dart'; +import '../../domain/usecases/get_terms_usecase.dart'; +import '../../domain/usecases/get_privacy_policy_usecase.dart'; + +part 'privacy_security_event.dart'; +part 'privacy_security_state.dart'; + +/// BLoC managing privacy and security settings state +class PrivacySecurityBloc + extends Bloc { + final GetPrivacySettingsUseCase _getPrivacySettingsUseCase; + final UpdateLocationSharingUseCase _updateLocationSharingUseCase; + final GetTermsUseCase _getTermsUseCase; + final GetPrivacyPolicyUseCase _getPrivacyPolicyUseCase; + + PrivacySecurityBloc({ + required GetPrivacySettingsUseCase getPrivacySettingsUseCase, + required UpdateLocationSharingUseCase updateLocationSharingUseCase, + required GetTermsUseCase getTermsUseCase, + required GetPrivacyPolicyUseCase getPrivacyPolicyUseCase, + }) : _getPrivacySettingsUseCase = getPrivacySettingsUseCase, + _updateLocationSharingUseCase = updateLocationSharingUseCase, + _getTermsUseCase = getTermsUseCase, + _getPrivacyPolicyUseCase = getPrivacyPolicyUseCase, + super(const PrivacySecurityState()) { + on(_onFetchPrivacySettings); + on(_onUpdateLocationSharing); + on(_onFetchTerms); + on(_onFetchPrivacyPolicy); + } + + Future _onFetchPrivacySettings( + FetchPrivacySettingsEvent event, + Emitter emit, + ) async { + emit(state.copyWith(isLoading: true, error: null)); + + try { + final settings = await _getPrivacySettingsUseCase.call(); + emit( + state.copyWith( + isLoading: false, + privacySettings: settings, + ), + ); + } catch (e) { + emit( + state.copyWith( + isLoading: false, + error: 'Failed to fetch privacy settings', + ), + ); + } + } + + Future _onUpdateLocationSharing( + UpdateLocationSharingEvent event, + Emitter emit, + ) async { + emit(state.copyWith(isUpdating: true, error: null)); + + try { + final settings = await _updateLocationSharingUseCase.call( + UpdateLocationSharingParams(enabled: event.enabled), + ); + emit( + state.copyWith( + isUpdating: false, + privacySettings: settings, + ), + ); + } catch (e) { + emit( + state.copyWith( + isUpdating: false, + error: 'Failed to update location sharing', + ), + ); + } + } + + Future _onFetchTerms( + FetchTermsEvent event, + Emitter emit, + ) async { + emit(state.copyWith(isLoadingTerms: true, error: null)); + + try { + final content = await _getTermsUseCase.call(); + emit( + state.copyWith( + isLoadingTerms: false, + termsContent: content, + ), + ); + } catch (e) { + emit( + state.copyWith( + isLoadingTerms: false, + error: 'Failed to fetch terms of service', + ), + ); + } + } + + Future _onFetchPrivacyPolicy( + FetchPrivacyPolicyEvent event, + Emitter emit, + ) async { + emit(state.copyWith(isLoadingPrivacyPolicy: true, error: null)); + + try { + final content = await _getPrivacyPolicyUseCase.call(); + emit( + state.copyWith( + isLoadingPrivacyPolicy: false, + privacyPolicyContent: content, + ), + ); + } catch (e) { + emit( + state.copyWith( + isLoadingPrivacyPolicy: false, + error: 'Failed to fetch privacy policy', + ), + ); + } + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/blocs/privacy_security_event.dart b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/blocs/privacy_security_event.dart new file mode 100644 index 00000000..d1a9caac --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/blocs/privacy_security_event.dart @@ -0,0 +1,35 @@ +part of 'privacy_security_bloc.dart'; + +/// Base class for privacy security BLoC events +abstract class PrivacySecurityEvent extends Equatable { + const PrivacySecurityEvent(); + + @override + List get props => []; +} + +/// Event to fetch current privacy settings +class FetchPrivacySettingsEvent extends PrivacySecurityEvent { + const FetchPrivacySettingsEvent(); +} + +/// Event to update location sharing preference +class UpdateLocationSharingEvent extends PrivacySecurityEvent { + /// Whether to enable or disable location sharing + final bool enabled; + + const UpdateLocationSharingEvent({required this.enabled}); + + @override + List get props => [enabled]; +} + +/// Event to fetch terms of service +class FetchTermsEvent extends PrivacySecurityEvent { + const FetchTermsEvent(); +} + +/// Event to fetch privacy policy +class FetchPrivacyPolicyEvent extends PrivacySecurityEvent { + const FetchPrivacyPolicyEvent(); +} diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/blocs/privacy_security_state.dart b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/blocs/privacy_security_state.dart new file mode 100644 index 00000000..14a6c39d --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/blocs/privacy_security_state.dart @@ -0,0 +1,75 @@ +part of 'privacy_security_bloc.dart'; + +/// State for privacy security BLoC +class PrivacySecurityState extends Equatable { + /// Current privacy settings + final PrivacySettingsEntity? privacySettings; + + /// Whether settings are currently loading + final bool isLoading; + + /// Whether settings are currently being updated + final bool isUpdating; + + /// Terms of service content + final String? termsContent; + + /// Whether terms are currently loading + final bool isLoadingTerms; + + /// Privacy policy content + final String? privacyPolicyContent; + + /// Whether privacy policy is currently loading + final bool isLoadingPrivacyPolicy; + + /// Error message, if any + final String? error; + + const PrivacySecurityState({ + this.privacySettings, + this.isLoading = false, + this.isUpdating = false, + this.termsContent, + this.isLoadingTerms = false, + this.privacyPolicyContent, + this.isLoadingPrivacyPolicy = false, + this.error, + }); + + /// Create a copy with optional field overrides + PrivacySecurityState copyWith({ + PrivacySettingsEntity? privacySettings, + bool? isLoading, + bool? isUpdating, + String? termsContent, + bool? isLoadingTerms, + String? privacyPolicyContent, + bool? isLoadingPrivacyPolicy, + String? error, + }) { + return PrivacySecurityState( + privacySettings: privacySettings ?? this.privacySettings, + isLoading: isLoading ?? this.isLoading, + isUpdating: isUpdating ?? this.isUpdating, + termsContent: termsContent ?? this.termsContent, + isLoadingTerms: isLoadingTerms ?? this.isLoadingTerms, + privacyPolicyContent: privacyPolicyContent ?? this.privacyPolicyContent, + isLoadingPrivacyPolicy: + isLoadingPrivacyPolicy ?? this.isLoadingPrivacyPolicy, + error: error, + ); + } + + @override + List get props => [ + privacySettings, + isLoading, + isUpdating, + termsContent, + isLoadingTerms, + privacyPolicyContent, + isLoadingPrivacyPolicy, + error, + ]; +} diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/navigation/privacy_security_navigator.dart b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/navigation/privacy_security_navigator.dart new file mode 100644 index 00000000..a823b0fd --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/navigation/privacy_security_navigator.dart @@ -0,0 +1,9 @@ +import 'package:flutter_modular/flutter_modular.dart'; + +/// Extension on IModularNavigator for privacy security navigation +extension PrivacySecurityNavigator on IModularNavigator { + /// Navigate to privacy security page + Future toPrivacySecurityPage() { + return pushNamed('/privacy-security'); + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/navigation/privacy_security_paths.dart b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/navigation/privacy_security_paths.dart new file mode 100644 index 00000000..d922dc29 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/navigation/privacy_security_paths.dart @@ -0,0 +1,5 @@ +/// Navigation route paths for privacy security feature +class PrivacySecurityPaths { + /// Route to privacy security main page + static const String privacySecurity = '/privacy-security'; +} diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/pages/privacy_security_page.dart b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/pages/privacy_security_page.dart new file mode 100644 index 00000000..6041b6f3 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/pages/privacy_security_page.dart @@ -0,0 +1,190 @@ +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 'package:flutter_modular/flutter_modular.dart'; + +import '../blocs/privacy_security_bloc.dart'; +import '../widgets/settings_action_tile_widget.dart'; +import '../widgets/settings_divider_widget.dart'; +import '../widgets/settings_section_header_widget.dart'; +import '../widgets/settings_switch_tile_widget.dart'; + +/// Page displaying privacy & security settings for staff +class PrivacySecurityPage extends StatelessWidget { + const PrivacySecurityPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: UiAppBar( + title: t.staff_privacy_security.title, + showBackButton: true, + bottom: PreferredSize( + preferredSize: const Size.fromHeight(1), + child: Container(color: UiColors.border, height: 1), + ), + ), + body: BlocProvider.value( + value: Modular.get() + ..add(const FetchPrivacySettingsEvent()), + child: BlocBuilder( + builder: (BuildContext context, PrivacySecurityState state) { + if (state.isLoading) { + return const UiLoadingPage(); + } + + return SingleChildScrollView( + padding: const EdgeInsets.all(UiConstants.space6), + child: Column( + children: [ + // Privacy Section + SettingsSectionHeader( + title: t.staff_privacy_security.privacy_section, + icon: Icons.visibility, + ), + const SizedBox(height: 12.0), + Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8.0), + border: Border.all( + color: UiColors.border, + ), + ), + child: Column( + children: [ + SettingsSwitchTile( + title: + t.staff_privacy_security.location_sharing.title, + subtitle: t + .staff_privacy_security + .location_sharing + .subtitle, + value: + state.privacySettings?.locationSharing ?? false, + onChanged: (bool value) { + BlocProvider.of(context) + .add( + UpdateLocationSharingEvent(enabled: value), + ); + }, + ), + ], + ), + ), + + const SizedBox(height: 24.0), + + // Legal Section + SettingsSectionHeader( + title: t.staff_privacy_security.legal_section, + icon: Icons.shield, + ), + const SizedBox(height: 12.0), + Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8.0), + border: Border.all( + color: UiColors.border, + ), + ), + child: Column( + children: [ + SettingsActionTile( + title: + t.staff_privacy_security.terms_of_service.title, + onTap: () => _showTermsDialog(context), + ), + const SettingsDivider(), + SettingsActionTile( + title: t.staff_privacy_security.privacy_policy.title, + onTap: () => _showPrivacyPolicyDialog(context), + ), + ], + ), + ), + + const SizedBox(height: 24.0), + ], + ), + ); + }, + ), + ), + ); + } + + /// Show terms of service in a modal dialog + void _showTermsDialog(BuildContext context) { + BlocProvider.of(context) + .add(const FetchTermsEvent()); + + showDialog( + context: context, + builder: (BuildContext dialogContext) => + BlocBuilder( + builder: (BuildContext context, PrivacySecurityState state) { + return AlertDialog( + title: Text( + t.staff_privacy_security.terms_of_service.title, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + content: SingleChildScrollView( + child: Text( + state.termsContent ?? 'Loading...', + style: const TextStyle(fontSize: 14), + ), + ), + actions: [ + TextButton( + onPressed: () => Modular.to.pop(), + child: Text(t.common.ok), + ), + ], + ); + }, + ), + ); + } + + /// Show privacy policy in a modal dialog + void _showPrivacyPolicyDialog(BuildContext context) { + BlocProvider.of(context) + .add(const FetchPrivacyPolicyEvent()); + + showDialog( + context: context, + builder: (BuildContext dialogContext) => + BlocBuilder( + builder: (BuildContext context, PrivacySecurityState state) { + return AlertDialog( + title: Text( + t.staff_privacy_security.privacy_policy.title, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + content: SingleChildScrollView( + child: Text( + state.privacyPolicyContent ?? 'Loading...', + style: const TextStyle(fontSize: 14), + ), + ), + actions: [ + TextButton( + onPressed: () => Modular.to.pop(), + child: Text(t.common.ok), + ), + ], + ); + }, + ), + ); + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/widgets/settings_action_tile_widget.dart b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/widgets/settings_action_tile_widget.dart new file mode 100644 index 00000000..aee4dddc --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/widgets/settings_action_tile_widget.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:design_system/design_system.dart'; + +/// Reusable widget for action tile (tap to navigate) +class SettingsActionTile extends StatelessWidget { + /// The title of the action + final String title; + + /// Optional subtitle describing the action + final String? subtitle; + + /// Callback when tile is tapped + final VoidCallback onTap; + + const SettingsActionTile({ + Key? key, + required this.title, + this.subtitle, + required this.onTap, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onTap, + child: Padding( + padding: EdgeInsets.all(UiConstants.space4), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: UiTypography.body2r.copyWith( + fontWeight: FontWeight.w500, + ), + ), + if (subtitle != null) ...[ + SizedBox(height: UiConstants.space1), + Text( + subtitle!, + style: UiTypography.footnote1r.copyWith( + color: UiColors.muted, + ), + ), + ], + ], + ), + ), + Icon( + Icons.chevron_right, + size: 20, + color: UiColors.muted, + ), + ], + ), + ), + ); + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/widgets/settings_divider_widget.dart b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/widgets/settings_divider_widget.dart new file mode 100644 index 00000000..349ab271 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/widgets/settings_divider_widget.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; +import 'package:design_system/design_system.dart'; + +/// Divider widget for separating items within settings sections +class SettingsDivider extends StatelessWidget { + const SettingsDivider({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Divider( + height: 1, + color: UiColors.border, + ); + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/widgets/settings_section_header_widget.dart b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/widgets/settings_section_header_widget.dart new file mode 100644 index 00000000..aca1bf27 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/widgets/settings_section_header_widget.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:design_system/design_system.dart'; + +/// Reusable widget for settings section header with icon +class SettingsSectionHeader extends StatelessWidget { + /// The title of the section + final String title; + + /// The icon to display next to the title + final IconData icon; + + const SettingsSectionHeader({ + Key? key, + required this.title, + required this.icon, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Icon( + icon, + size: 20, + color: UiColors.primary, + ), + SizedBox(width: UiConstants.space2), + Text( + title, + style: UiTypography.body1r.copyWith( + fontWeight: FontWeight.w600, + ), + ), + ], + ); + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/widgets/settings_switch_tile_widget.dart b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/widgets/settings_switch_tile_widget.dart new file mode 100644 index 00000000..7a03d070 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/src/presentation/widgets/settings_switch_tile_widget.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:design_system/design_system.dart'; + +/// Reusable widget for toggle tile in privacy settings +class SettingsSwitchTile extends StatelessWidget { + /// The title of the setting + final String title; + + /// The subtitle describing the setting + final String subtitle; + + /// Current toggle value + final bool value; + + /// Callback when toggle is changed + final ValueChanged onChanged; + + const SettingsSwitchTile({ + Key? key, + required this.title, + required this.subtitle, + required this.value, + required this.onChanged, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.all(UiConstants.space4), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: UiTypography.body2r.copyWith( + fontWeight: FontWeight.w500, + ), + ), + SizedBox(height: UiConstants.space1), + Text( + subtitle, + style: UiTypography.footnote1r.copyWith( + color: UiColors.muted, + ), + ), + ], + ), + ), + Switch( + value: value, + onChanged: onChanged, + activeColor: UiColors.primary, + ), + ], + ), + ); + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/staff_privacy_security.dart b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/staff_privacy_security.dart new file mode 100644 index 00000000..92c7c856 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/staff_privacy_security.dart @@ -0,0 +1,14 @@ +export 'src/domain/entities/privacy_settings_entity.dart'; +export 'src/domain/repositories/privacy_settings_repository_interface.dart'; +export 'src/domain/usecases/get_privacy_settings_usecase.dart'; +export 'src/domain/usecases/update_location_sharing_usecase.dart'; +export 'src/domain/usecases/get_terms_usecase.dart'; +export 'src/domain/usecases/get_privacy_policy_usecase.dart'; +export 'src/data/repositories_impl/privacy_settings_repository_impl.dart'; +export 'src/presentation/blocs/privacy_security_bloc.dart'; +export 'src/presentation/pages/privacy_security_page.dart'; +export 'src/presentation/widgets/settings_switch_tile_widget.dart'; +export 'src/presentation/widgets/settings_action_tile_widget.dart'; +export 'src/presentation/widgets/settings_section_header_widget.dart'; +export 'src/presentation/widgets/settings_divider_widget.dart'; +export 'staff_privacy_security_module.dart'; diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/staff_privacy_security_module.dart b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/staff_privacy_security_module.dart new file mode 100644 index 00000000..55d89e00 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/lib/staff_privacy_security_module.dart @@ -0,0 +1,70 @@ +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:krow_data_connect/krow_data_connect.dart'; + +import 'src/data/repositories_impl/privacy_settings_repository_impl.dart'; +import 'src/domain/repositories/privacy_settings_repository_interface.dart'; +import 'src/domain/usecases/get_privacy_policy_usecase.dart'; +import 'src/domain/usecases/get_privacy_settings_usecase.dart'; +import 'src/domain/usecases/get_terms_usecase.dart'; +import 'src/domain/usecases/update_location_sharing_usecase.dart'; +import 'src/presentation/blocs/privacy_security_bloc.dart'; +import 'src/presentation/pages/privacy_security_page.dart'; + +/// Module for privacy security feature +/// +/// Provides: +/// - Dependency injection for repositories, use cases, and BLoCs +/// - Route definitions delegated to core routing +class PrivacySecurityModule extends Module { + @override + void binds(i) { + // Repository + i.addSingleton( + () => PrivacySettingsRepositoryImpl( + Modular.get(), + ), + ); + + // Use Cases + i.addSingleton( + () => GetPrivacySettingsUseCase( + i(), + ), + ); + i.addSingleton( + () => UpdateLocationSharingUseCase( + i(), + ), + ); + i.addSingleton( + () => GetTermsUseCase( + i(), + ), + ); + i.addSingleton( + () => GetPrivacyPolicyUseCase( + i(), + ), + ); + + // BLoC + i.addSingleton( + () => PrivacySecurityBloc( + getPrivacySettingsUseCase: i(), + updateLocationSharingUseCase: i(), + getTermsUseCase: i(), + getPrivacyPolicyUseCase: i(), + ), + ); + } + + @override + @override + void routes(r) { + // Route is handled by core routing (StaffPaths.privacySecurity) + r.child( + '/', + child: (context) => const PrivacySecurityPage(), + ); + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/pubspec.yaml b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/pubspec.yaml new file mode 100644 index 00000000..37644420 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/settings/privacy_security/pubspec.yaml @@ -0,0 +1,40 @@ +name: staff_privacy_security +description: Privacy & Security settings feature for staff 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 + firebase_data_connect: ^0.2.2+1 + url_launcher: ^6.2.0 + + # Architecture Packages + krow_domain: + path: ../../../../../domain + krow_data_connect: + path: ../../../../../data_connect + krow_core: + path: ../../../../../core + design_system: + path: ../../../../../design_system + core_localization: + path: ../../../../../core_localization + + +dev_dependencies: + flutter_test: + sdk: flutter + bloc_test: ^9.1.0 + mocktail: ^1.0.0 + +flutter: + uses-material-design: true