Merge remote-tracking branch 'origin/408-feature-implement-paidunpaid-breaks---client-app-frontend-development' into staff_recurring_permanent_order
This commit is contained in:
@@ -284,13 +284,38 @@ 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);
|
||||
}
|
||||
|
||||
/// Navigates to the Terms of Service page.
|
||||
///
|
||||
/// Display the full terms of service document in a dedicated page view.
|
||||
void toTermsOfService() {
|
||||
pushNamed(StaffPaths.termsOfService);
|
||||
}
|
||||
|
||||
/// Navigates to the Privacy Policy page.
|
||||
///
|
||||
/// Display the full privacy policy document in a dedicated page view.
|
||||
void toPrivacyPolicy() {
|
||||
pushNamed(StaffPaths.privacyPolicy);
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// MESSAGING & COMMUNICATION
|
||||
// ==========================================================================
|
||||
|
||||
/// Pushes the messages page (placeholder).
|
||||
///
|
||||
/// Access internal messaging system.
|
||||
|
||||
@@ -205,8 +205,29 @@ 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/';
|
||||
|
||||
/// Terms of Service page.
|
||||
///
|
||||
/// Display the full terms of service document.
|
||||
static const String termsOfService = '/worker-main/privacy-security/terms/';
|
||||
|
||||
/// Privacy Policy page.
|
||||
///
|
||||
/// Display the full privacy policy document.
|
||||
static const String privacyPolicy = '/worker-main/privacy-security/policy/';
|
||||
|
||||
// ==========================================================================
|
||||
// MESSAGING & COMMUNICATION (Placeholders)
|
||||
// ==========================================================================
|
||||
|
||||
/// Messages - internal messaging system (placeholder).
|
||||
static const String messages = '/messages';
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -71,7 +71,9 @@ class UiTheme {
|
||||
),
|
||||
maximumSize: const Size(double.infinity, 54),
|
||||
).copyWith(
|
||||
side: WidgetStateProperty.resolveWith<BorderSide?>((Set<WidgetState> states) {
|
||||
side: WidgetStateProperty.resolveWith<BorderSide?>((
|
||||
Set<WidgetState> states,
|
||||
) {
|
||||
if (states.contains(WidgetState.disabled)) {
|
||||
return const BorderSide(
|
||||
color: UiColors.borderPrimary,
|
||||
@@ -80,7 +82,9 @@ class UiTheme {
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
overlayColor: WidgetStateProperty.resolveWith((Set<WidgetState> states) {
|
||||
overlayColor: WidgetStateProperty.resolveWith((
|
||||
Set<WidgetState> states,
|
||||
) {
|
||||
if (states.contains(WidgetState.hovered))
|
||||
return UiColors.buttonPrimaryHover;
|
||||
return null;
|
||||
@@ -239,7 +243,9 @@ class UiTheme {
|
||||
navigationBarTheme: NavigationBarThemeData(
|
||||
backgroundColor: UiColors.white,
|
||||
indicatorColor: UiColors.primaryInverse.withAlpha(51), // 20% of 255
|
||||
labelTextStyle: WidgetStateProperty.resolveWith((Set<WidgetState> states) {
|
||||
labelTextStyle: WidgetStateProperty.resolveWith((
|
||||
Set<WidgetState> states,
|
||||
) {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
return UiTypography.footnote2m.textPrimary;
|
||||
}
|
||||
@@ -249,13 +255,38 @@ class UiTheme {
|
||||
|
||||
// Switch Theme
|
||||
switchTheme: SwitchThemeData(
|
||||
trackColor: WidgetStateProperty.resolveWith<Color>((Set<WidgetState> states) {
|
||||
trackColor: WidgetStateProperty.resolveWith<Color>((
|
||||
Set<WidgetState> states,
|
||||
) {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
return UiColors.switchActive;
|
||||
return UiColors.primary.withAlpha(60);
|
||||
}
|
||||
return UiColors.switchInactive;
|
||||
}),
|
||||
thumbColor: const WidgetStatePropertyAll<Color>(UiColors.white),
|
||||
thumbColor: WidgetStateProperty.resolveWith<Color>((
|
||||
Set<WidgetState> states,
|
||||
) {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
return UiColors.primary;
|
||||
}
|
||||
return UiColors.white;
|
||||
}),
|
||||
trackOutlineColor: WidgetStateProperty.resolveWith<Color?>((
|
||||
Set<WidgetState> states,
|
||||
) {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
return UiColors.primary;
|
||||
}
|
||||
return UiColors.transparent;
|
||||
}),
|
||||
trackOutlineWidth: WidgetStateProperty.resolveWith<double?>((
|
||||
Set<WidgetState> states,
|
||||
) {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
return 1.0;
|
||||
}
|
||||
return 0.0;
|
||||
}),
|
||||
),
|
||||
|
||||
// Checkbox Theme
|
||||
|
||||
@@ -15,8 +15,6 @@ dependencies:
|
||||
google_fonts: ^7.0.2
|
||||
lucide_icons: ^0.257.0
|
||||
font_awesome_flutter: ^10.7.0
|
||||
core_localization:
|
||||
path: ../core_localization
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
@@ -105,7 +105,7 @@ class HomeRepositoryImpl
|
||||
address: staff.addres,
|
||||
avatar: staff.photoUrl,
|
||||
),
|
||||
ownerId: session?.ownerId,
|
||||
ownerId: staff.ownerId,
|
||||
);
|
||||
StaffSessionStore.instance.setSession(updatedSession);
|
||||
|
||||
|
||||
@@ -192,6 +192,20 @@ class StaffProfilePage extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
SectionTitle(
|
||||
i18n.header.title.contains("Perfil") ? "Soporte" : "Support",
|
||||
),
|
||||
ProfileMenuGrid(
|
||||
crossAxisCount: 3,
|
||||
children: [
|
||||
ProfileMenuItem(
|
||||
icon: UiIcons.shield,
|
||||
label: i18n.header.title.contains("Perfil") ? "Privacidad" : "Privacy & Security",
|
||||
onTap: () => Modular.to.toPrivacySecurity(),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
SectionTitle(
|
||||
i18n.header.title.contains("Perfil") ? "Ajustes" : "Settings",
|
||||
),
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
PRIVACY POLICY
|
||||
|
||||
Effective Date: February 18, 2026
|
||||
|
||||
1. INTRODUCTION
|
||||
|
||||
Krow Workforce ("we," "us," "our," or "the App") is committed to protecting your privacy. This Privacy Policy explains how we collect, use, disclose, and otherwise process your personal information through our mobile application and related services.
|
||||
|
||||
2. INFORMATION WE COLLECT
|
||||
|
||||
2.1 Information You Provide Directly:
|
||||
- Account information: name, email address, phone number, password
|
||||
- Profile information: photo, bio, skills, experience, certifications
|
||||
- Location data: work location preferences and current location (when enabled)
|
||||
- Payment information: bank account details, tax identification numbers
|
||||
- Communication data: messages, support inquiries, feedback
|
||||
|
||||
2.2 Information Collected Automatically:
|
||||
- Device information: device type, operating system, device identifiers
|
||||
- Usage data: features accessed, actions taken, time and duration of activities
|
||||
- Log data: IP address, browser type, pages visited, errors encountered
|
||||
- Location data: approximate location based on IP address (always)
|
||||
- Precise location: only when Location Sharing is enabled
|
||||
|
||||
2.3 Information from Third Parties:
|
||||
- Background check services: verification results
|
||||
- Banking partners: account verification information
|
||||
- Payment processors: transaction information
|
||||
|
||||
3. HOW WE USE YOUR INFORMATION
|
||||
|
||||
We use your information to:
|
||||
- Create and maintain your account
|
||||
- Process payments and verify employment eligibility
|
||||
- Improve and optimize our services
|
||||
- Send you important notifications and updates
|
||||
- Provide customer support
|
||||
- Prevent fraud and ensure security
|
||||
- Comply with legal obligations
|
||||
- Conduct analytics and research
|
||||
- Match you with appropriate work opportunities
|
||||
- Communicate promotional offers (with your consent)
|
||||
|
||||
4. LOCATION DATA & PRIVACY SETTINGS
|
||||
|
||||
4.1 Location Sharing:
|
||||
You can control location sharing through Privacy Settings:
|
||||
- Disabled (default): Your approximate location is based on IP address only
|
||||
- Enabled: Precise location data is collected for better job matching
|
||||
|
||||
4.2 Your Control:
|
||||
You may enable or disable precise location sharing at any time in the Privacy & Security section of your profile.
|
||||
|
||||
5. DATA RETENTION
|
||||
|
||||
We retain your personal information for as long as:
|
||||
- Your account is active, plus
|
||||
- An additional period as required by law or for business purposes
|
||||
|
||||
You may request deletion of your account and associated data by contacting support@krow.com.
|
||||
|
||||
6. DATA SECURITY
|
||||
|
||||
We implement appropriate technical and organizational measures to protect your personal information from unauthorized access, alteration, disclosure, or destruction. However, no method of transmission over the internet is 100% secure.
|
||||
|
||||
7. SHARING OF INFORMATION
|
||||
|
||||
We do not sell your personal information. We may share information with:
|
||||
- Service providers and contractors: who process data on our behalf
|
||||
- Employers and clients: limited information needed for job matching
|
||||
- Legal authorities: when required by law
|
||||
- Business partners: with your explicit consent
|
||||
- Other users: your name, skills, and ratings (as needed for job matching)
|
||||
|
||||
8. YOUR PRIVACY RIGHTS
|
||||
|
||||
8.1 Access and Correction:
|
||||
You have the right to access, review, and request correction of your personal information.
|
||||
|
||||
8.2 Data Portability:
|
||||
You may request a copy of your personal data in a portable format.
|
||||
|
||||
8.3 Deletion:
|
||||
You may request deletion of your account and personal information, subject to legal obligations.
|
||||
|
||||
8.4 Opt-Out:
|
||||
You may opt out of marketing communications and certain data processing activities.
|
||||
|
||||
9. CHILDREN'S PRIVACY
|
||||
|
||||
Our App is not intended for individuals under 18 years of age. We do not knowingly collect personal information from children. If we become aware that we have collected information from a child, we will take steps to delete such information immediately.
|
||||
|
||||
10. THIRD-PARTY LINKS
|
||||
|
||||
Our App may contain links to third-party websites. We are not responsible for the privacy practices of these external sites. We encourage you to review their privacy policies.
|
||||
|
||||
11. INTERNATIONAL DATA TRANSFERS
|
||||
|
||||
Your information may be transferred to, stored in, and processed in countries other than your country of residence. These countries may have data protection laws different from your home country.
|
||||
|
||||
12. CHANGES TO THIS POLICY
|
||||
|
||||
We may update this Privacy Policy from time to time. We will notify you of significant changes via email or through the App. Your continued use of the App constitutes your acceptance of the updated Privacy Policy.
|
||||
|
||||
13. CONTACT US
|
||||
|
||||
If you have questions about this Privacy Policy or your personal information, please contact us at:
|
||||
|
||||
Email: privacy@krow.com
|
||||
Address: Krow Workforce, [Company Address]
|
||||
Phone: [Support Phone Number]
|
||||
|
||||
14. CALIFORNIA PRIVACY RIGHTS (CCPA)
|
||||
|
||||
If you are a California resident, you have additional rights under the California Consumer Privacy Act (CCPA). Please visit our CCPA Rights page or contact privacy@krow.com for more information.
|
||||
|
||||
15. EUROPEAN PRIVACY RIGHTS (GDPR)
|
||||
|
||||
If you are in the European Union, you have rights under the General Data Protection Regulation (GDPR). These include the right to access, rectification, erasure, and data portability. Contact privacy@krow.com to exercise these rights.
|
||||
@@ -0,0 +1,61 @@
|
||||
TERMS OF SERVICE
|
||||
|
||||
Effective Date: February 18, 2026
|
||||
|
||||
1. ACCEPTANCE OF TERMS
|
||||
|
||||
By accessing and using the Krow Workforce application ("the App"), you accept and agree to be bound by the terms and provisions of this agreement. If you do not agree to abide by the above, please do not use this service.
|
||||
|
||||
2. USE LICENSE
|
||||
|
||||
Permission is granted to temporarily download one copy of the materials (information or software) on Krow Workforce's App for personal, non-commercial transitory viewing only. This is the grant of a license, not a transfer of title, and under this license you may not:
|
||||
|
||||
a) Modifying or copying the materials
|
||||
b) Using the materials for any commercial purpose or for any public display
|
||||
c) Attempting to reverse engineer, disassemble, or decompile any software contained on the App
|
||||
d) Removing any copyright or other proprietary notations from the materials
|
||||
e) Transferring the materials to another person or "mirroring" the materials on any other server
|
||||
|
||||
3. DISCLAIMER
|
||||
|
||||
The materials on Krow Workforce's App are provided on an "as is" basis. Krow Workforce makes no warranties, expressed or implied, and hereby disclaims and negates all other warranties including, without limitation, implied warranties or conditions of merchantability, fitness for a particular purpose, or non-infringement of intellectual property or other violation of rights.
|
||||
|
||||
4. LIMITATIONS
|
||||
|
||||
In no event shall Krow Workforce or its suppliers be liable for any damages (including, without limitation, damages for loss of data or profit, or due to business interruption) arising out of the use or inability to use the materials on Krow Workforce's App, even if Krow Workforce or a Krow Workforce authorized representative has been notified orally or in writing of the possibility of such damage.
|
||||
|
||||
5. ACCURACY OF MATERIALS
|
||||
|
||||
The materials appearing on Krow Workforce's App could include technical, typographical, or photographic errors. Krow Workforce does not warrant that any of the materials on its App are accurate, complete, or current. Krow Workforce may make changes to the materials contained on its App at any time without notice.
|
||||
|
||||
6. MATERIALS DISCLAIMER
|
||||
|
||||
Krow Workforce has not reviewed all of the sites linked to its App and is not responsible for the contents of any such linked site. The inclusion of any link does not imply endorsement by Krow Workforce of the site. Use of any such linked website is at the user's own risk.
|
||||
|
||||
7. MODIFICATIONS
|
||||
|
||||
Krow Workforce may revise these terms of service for its App at any time without notice. By using this App, you are agreeing to be bound by the then current version of these terms of service.
|
||||
|
||||
8. GOVERNING LAW
|
||||
|
||||
These terms and conditions are governed by and construed in accordance with the laws of the jurisdiction in which Krow Workforce is located, and you irrevocably submit to the exclusive jurisdiction of the courts in that location.
|
||||
|
||||
9. LIMITATION OF LIABILITY
|
||||
|
||||
In no case shall Krow Workforce, its staff, or other contributors be liable for any indirect, incidental, consequential, special, or punitive damages arising out of or relating to the use of the App.
|
||||
|
||||
10. USER CONTENT
|
||||
|
||||
You grant Krow Workforce a non-exclusive, royalty-free, perpetual, and irrevocable right to use any content you provide to us, including but not limited to text, images, and information, in any media or format and for any purpose consistent with our business.
|
||||
|
||||
11. INDEMNIFICATION
|
||||
|
||||
You agree to indemnify and hold harmless Krow Workforce and its staff from any and all claims, damages, losses, costs, and expenses, including attorney's fees, arising out of or resulting from your use of the App or violation of these terms.
|
||||
|
||||
12. TERMINATION
|
||||
|
||||
Krow Workforce reserves the right to terminate your account and access to the App at any time, in its sole discretion, for any reason or no reason, with or without notice.
|
||||
|
||||
13. CONTACT INFORMATION
|
||||
|
||||
If you have any questions about these Terms of Service, please contact us at support@krow.com.
|
||||
@@ -0,0 +1,79 @@
|
||||
import 'package:flutter/services.dart';
|
||||
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,
|
||||
/// and loads legal documents from app assets
|
||||
class PrivacySettingsRepositoryImpl
|
||||
implements PrivacySettingsRepositoryInterface {
|
||||
final DataConnectService _service;
|
||||
|
||||
PrivacySettingsRepositoryImpl(this._service);
|
||||
|
||||
@override
|
||||
Future<PrivacySettingsEntity> getPrivacySettings() async {
|
||||
return _service.run<PrivacySettingsEntity>(
|
||||
() async {
|
||||
// TODO: Call Data Connect query to fetch privacy settings
|
||||
// For now, return default settings
|
||||
return PrivacySettingsEntity(
|
||||
locationSharing: true,
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PrivacySettingsEntity> updateLocationSharing(bool enabled) async {
|
||||
return _service.run<PrivacySettingsEntity>(
|
||||
() 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<String> getTermsOfService() async {
|
||||
return _service.run<String>(
|
||||
() async {
|
||||
try {
|
||||
// Load from package asset path
|
||||
return await rootBundle.loadString(
|
||||
'packages/staff_privacy_security/lib/src/assets/legal/terms_of_service.txt',
|
||||
);
|
||||
} catch (e) {
|
||||
// Final fallback if asset not found
|
||||
return 'Terms of Service - Content unavailable. Please contact support@krow.com';
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> getPrivacyPolicy() async {
|
||||
return _service.run<String>(
|
||||
() async {
|
||||
try {
|
||||
// Load from package asset path
|
||||
return await rootBundle.loadString(
|
||||
'packages/staff_privacy_security/lib/src/assets/legal/privacy_policy.txt',
|
||||
);
|
||||
} catch (e) {
|
||||
// Final fallback if asset not found
|
||||
return 'Privacy Policy - Content unavailable. Please contact privacy@krow.com';
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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<Object?> get props => [locationSharing, updatedAt];
|
||||
}
|
||||
@@ -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<PrivacySettingsEntity> getPrivacySettings();
|
||||
|
||||
/// Update location sharing preference
|
||||
///
|
||||
/// Returns the updated privacy settings
|
||||
Future<PrivacySettingsEntity> updateLocationSharing(bool enabled);
|
||||
|
||||
/// Fetch terms of service content
|
||||
Future<String> getTermsOfService();
|
||||
|
||||
/// Fetch privacy policy content
|
||||
Future<String> getPrivacyPolicy();
|
||||
}
|
||||
@@ -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<String> call() async {
|
||||
try {
|
||||
return await _repository.getPrivacyPolicy();
|
||||
} catch (e) {
|
||||
return 'Privacy Policy is currently unavailable.';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<PrivacySettingsEntity> call() async {
|
||||
try {
|
||||
return await _repository.getPrivacySettings();
|
||||
} catch (e) {
|
||||
// Return default settings on error
|
||||
return PrivacySettingsEntity(
|
||||
locationSharing: true,
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<String> call() async {
|
||||
try {
|
||||
return await _repository.getTermsOfService();
|
||||
} catch (e) {
|
||||
return 'Terms of Service is currently unavailable.';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Object?> 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<PrivacySettingsEntity> 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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../domain/usecases/get_privacy_policy_usecase.dart';
|
||||
|
||||
/// State for Privacy Policy cubit
|
||||
class PrivacyPolicyState {
|
||||
final String? content;
|
||||
final bool isLoading;
|
||||
final String? error;
|
||||
|
||||
const PrivacyPolicyState({
|
||||
this.content,
|
||||
this.isLoading = false,
|
||||
this.error,
|
||||
});
|
||||
|
||||
PrivacyPolicyState copyWith({
|
||||
String? content,
|
||||
bool? isLoading,
|
||||
String? error,
|
||||
}) {
|
||||
return PrivacyPolicyState(
|
||||
content: content ?? this.content,
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
error: error ?? this.error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Cubit for managing Privacy Policy content
|
||||
class PrivacyPolicyCubit extends Cubit<PrivacyPolicyState> {
|
||||
final GetPrivacyPolicyUseCase _getPrivacyPolicyUseCase;
|
||||
|
||||
PrivacyPolicyCubit({
|
||||
required GetPrivacyPolicyUseCase getPrivacyPolicyUseCase,
|
||||
}) : _getPrivacyPolicyUseCase = getPrivacyPolicyUseCase,
|
||||
super(const PrivacyPolicyState());
|
||||
|
||||
/// Fetch privacy policy content
|
||||
Future<void> fetchPrivacyPolicy() async {
|
||||
emit(state.copyWith(isLoading: true, error: null));
|
||||
try {
|
||||
final String content = await _getPrivacyPolicyUseCase();
|
||||
emit(state.copyWith(
|
||||
content: content,
|
||||
isLoading: false,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
isLoading: false,
|
||||
error: e.toString(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../domain/usecases/get_terms_usecase.dart';
|
||||
|
||||
/// State for Terms of Service cubit
|
||||
class TermsState {
|
||||
final String? content;
|
||||
final bool isLoading;
|
||||
final String? error;
|
||||
|
||||
const TermsState({
|
||||
this.content,
|
||||
this.isLoading = false,
|
||||
this.error,
|
||||
});
|
||||
|
||||
TermsState copyWith({
|
||||
String? content,
|
||||
bool? isLoading,
|
||||
String? error,
|
||||
}) {
|
||||
return TermsState(
|
||||
content: content ?? this.content,
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
error: error ?? this.error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Cubit for managing Terms of Service content
|
||||
class TermsCubit extends Cubit<TermsState> {
|
||||
final GetTermsUseCase _getTermsUseCase;
|
||||
|
||||
TermsCubit({
|
||||
required GetTermsUseCase getTermsUseCase,
|
||||
}) : _getTermsUseCase = getTermsUseCase,
|
||||
super(const TermsState());
|
||||
|
||||
/// Fetch terms of service content
|
||||
Future<void> fetchTerms() async {
|
||||
emit(state.copyWith(isLoading: true, error: null));
|
||||
try {
|
||||
final String content = await _getTermsUseCase();
|
||||
emit(state.copyWith(
|
||||
content: content,
|
||||
isLoading: false,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
isLoading: false,
|
||||
error: e.toString(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<PrivacySecurityEvent, PrivacySecurityState> {
|
||||
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<FetchPrivacySettingsEvent>(_onFetchPrivacySettings);
|
||||
on<UpdateLocationSharingEvent>(_onUpdateLocationSharing);
|
||||
on<FetchTermsEvent>(_onFetchTerms);
|
||||
on<FetchPrivacyPolicyEvent>(_onFetchPrivacyPolicy);
|
||||
}
|
||||
|
||||
Future<void> _onFetchPrivacySettings(
|
||||
FetchPrivacySettingsEvent event,
|
||||
Emitter<PrivacySecurityState> 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<void> _onUpdateLocationSharing(
|
||||
UpdateLocationSharingEvent event,
|
||||
Emitter<PrivacySecurityState> 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<void> _onFetchTerms(
|
||||
FetchTermsEvent event,
|
||||
Emitter<PrivacySecurityState> 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<void> _onFetchPrivacyPolicy(
|
||||
FetchPrivacyPolicyEvent event,
|
||||
Emitter<PrivacySecurityState> 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',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Object?> 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<Object?> 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();
|
||||
}
|
||||
@@ -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<Object?> get props => [
|
||||
privacySettings,
|
||||
isLoading,
|
||||
isUpdating,
|
||||
termsContent,
|
||||
isLoadingTerms,
|
||||
privacyPolicyContent,
|
||||
isLoadingPrivacyPolicy,
|
||||
error,
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
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/legal/privacy_policy_cubit.dart';
|
||||
|
||||
/// Page displaying the Privacy Policy document
|
||||
class PrivacyPolicyPage extends StatelessWidget {
|
||||
const PrivacyPolicyPage({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: UiAppBar(
|
||||
title: t.staff_privacy_security.privacy_policy.title,
|
||||
showBackButton: true,
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(1),
|
||||
child: Container(color: UiColors.border, height: 1),
|
||||
),
|
||||
),
|
||||
body: BlocProvider<PrivacyPolicyCubit>(
|
||||
create: (BuildContext context) => Modular.get<PrivacyPolicyCubit>()..fetchPrivacyPolicy(),
|
||||
child: BlocBuilder<PrivacyPolicyCubit, PrivacyPolicyState>(
|
||||
builder: (BuildContext context, PrivacyPolicyState state) {
|
||||
if (state.isLoading) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
if (state.error != null) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
child: Text(
|
||||
'Error loading Privacy Policy: ${state.error}',
|
||||
textAlign: TextAlign.center,
|
||||
style: UiTypography.body2r.copyWith(
|
||||
color: UiColors.textSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
child: Text(
|
||||
state.content ?? 'No content available',
|
||||
style: UiTypography.body2r.copyWith(
|
||||
height: 1.6,
|
||||
color: UiColors.textPrimary,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
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/legal/terms_cubit.dart';
|
||||
|
||||
/// Page displaying the Terms of Service document
|
||||
class TermsOfServicePage extends StatelessWidget {
|
||||
const TermsOfServicePage({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: UiAppBar(
|
||||
title: t.staff_privacy_security.terms_of_service.title,
|
||||
showBackButton: true,
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(1),
|
||||
child: Container(color: UiColors.border, height: 1),
|
||||
),
|
||||
),
|
||||
body: BlocProvider<TermsCubit>(
|
||||
create: (BuildContext context) => Modular.get<TermsCubit>()..fetchTerms(),
|
||||
child: BlocBuilder<TermsCubit, TermsState>(
|
||||
builder: (BuildContext context, TermsState state) {
|
||||
if (state.isLoading) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
if (state.error != null) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
child: Text(
|
||||
'Error loading Terms of Service: ${state.error}',
|
||||
textAlign: TextAlign.center,
|
||||
style: UiTypography.body2r.copyWith(
|
||||
color: UiColors.textSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
child: Text(
|
||||
state.content ?? 'No content available',
|
||||
style: UiTypography.body2r.copyWith(
|
||||
height: 1.6,
|
||||
color: UiColors.textPrimary,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
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/legal/legal_section_widget.dart';
|
||||
import '../widgets/privacy/privacy_section_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<PrivacySecurityBloc>.value(
|
||||
value: Modular.get<PrivacySecurityBloc>()
|
||||
..add(const FetchPrivacySettingsEvent()),
|
||||
child: BlocBuilder<PrivacySecurityBloc, PrivacySecurityState>(
|
||||
builder: (BuildContext context, PrivacySecurityState state) {
|
||||
if (state.isLoading) {
|
||||
return const UiLoadingPage();
|
||||
}
|
||||
|
||||
return const SingleChildScrollView(
|
||||
padding: EdgeInsets.all(UiConstants.space6),
|
||||
child: Column(
|
||||
spacing: UiConstants.space6,
|
||||
children: <Widget>[
|
||||
// Privacy Section
|
||||
PrivacySectionWidget(),
|
||||
|
||||
// Legal Section
|
||||
LegalSectionWidget(),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
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 'package:krow_core/core.dart';
|
||||
|
||||
import '../../blocs/privacy_security_bloc.dart';
|
||||
import '../settings_action_tile_widget.dart';
|
||||
import '../settings_divider_widget.dart';
|
||||
import '../settings_section_header_widget.dart';
|
||||
|
||||
/// Widget displaying legal documents (Terms of Service and Privacy Policy)
|
||||
class LegalSectionWidget extends StatelessWidget {
|
||||
const LegalSectionWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
spacing: UiConstants.space4,
|
||||
|
||||
children: <Widget>[
|
||||
// Legal Section Header
|
||||
SettingsSectionHeader(
|
||||
title: t.staff_privacy_security.legal_section,
|
||||
icon: Icons.shield,
|
||||
),
|
||||
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||
border: Border.all(color: UiColors.border),
|
||||
),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
SettingsActionTile(
|
||||
title: t.staff_privacy_security.terms_of_service.title,
|
||||
onTap: () => _navigateToTerms(context),
|
||||
),
|
||||
const SettingsDivider(),
|
||||
SettingsActionTile(
|
||||
title: t.staff_privacy_security.privacy_policy.title,
|
||||
onTap: () => _navigateToPrivacyPolicy(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// Navigate to terms of service page
|
||||
void _navigateToTerms(BuildContext context) {
|
||||
BlocProvider.of<PrivacySecurityBloc>(context).add(const FetchTermsEvent());
|
||||
|
||||
// Navigate using typed navigator
|
||||
Modular.to.toTermsOfService();
|
||||
}
|
||||
|
||||
/// Navigate to privacy policy page
|
||||
void _navigateToPrivacyPolicy(BuildContext context) {
|
||||
BlocProvider.of<PrivacySecurityBloc>(
|
||||
context,
|
||||
).add(const FetchPrivacyPolicyEvent());
|
||||
|
||||
// Navigate using typed navigator
|
||||
Modular.to.toPrivacyPolicy();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
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/privacy_security_bloc.dart';
|
||||
import '../settings_section_header_widget.dart';
|
||||
import '../settings_switch_tile_widget.dart';
|
||||
|
||||
/// Widget displaying privacy settings including location sharing preference
|
||||
class PrivacySectionWidget extends StatelessWidget {
|
||||
const PrivacySectionWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<PrivacySecurityBloc, PrivacySecurityState>(
|
||||
builder: (BuildContext context, PrivacySecurityState state) {
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
// Privacy Section Header
|
||||
SettingsSectionHeader(
|
||||
title: t.staff_privacy_security.privacy_section,
|
||||
icon: UiIcons.eye,
|
||||
),
|
||||
const SizedBox(height: 12.0),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||
border: Border.all(
|
||||
color: UiColors.border,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
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<PrivacySecurityBloc>(context).add(
|
||||
UpdateLocationSharingEvent(enabled: value),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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: const EdgeInsets.all(UiConstants.space4),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
title,
|
||||
style: UiTypography.body2r.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
if (subtitle != null) ...<Widget>[
|
||||
SizedBox(height: UiConstants.space1),
|
||||
Text(
|
||||
subtitle!,
|
||||
style: UiTypography.footnote1r.copyWith(
|
||||
color: UiColors.muted,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
const Icon(
|
||||
UiIcons.chevronRight,
|
||||
size: 16,
|
||||
color: UiColors.iconSecondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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: <Widget>[
|
||||
Icon(
|
||||
icon,
|
||||
size: 20,
|
||||
color: UiColors.primary,
|
||||
),
|
||||
SizedBox(width: UiConstants.space2),
|
||||
Text(
|
||||
title,
|
||||
style: UiTypography.body1r.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
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<bool> 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: const EdgeInsets.all(UiConstants.space4),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(title, style: UiTypography.body2r),
|
||||
Text(subtitle, style: UiTypography.footnote1r.textSecondary),
|
||||
],
|
||||
),
|
||||
),
|
||||
Switch(value: value, onChanged: onChanged),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||
|
||||
import 'data/repositories_impl/privacy_settings_repository_impl.dart';
|
||||
import 'domain/repositories/privacy_settings_repository_interface.dart';
|
||||
import 'domain/usecases/get_privacy_policy_usecase.dart';
|
||||
import 'domain/usecases/get_privacy_settings_usecase.dart';
|
||||
import 'domain/usecases/get_terms_usecase.dart';
|
||||
import 'domain/usecases/update_location_sharing_usecase.dart';
|
||||
import 'presentation/blocs/legal/privacy_policy_cubit.dart';
|
||||
import 'presentation/blocs/legal/terms_cubit.dart';
|
||||
import 'presentation/blocs/privacy_security_bloc.dart';
|
||||
import 'presentation/pages/legal/privacy_policy_page.dart';
|
||||
import 'presentation/pages/legal/terms_of_service_page.dart';
|
||||
import '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(Injector i) {
|
||||
// Repository
|
||||
i.addSingleton<PrivacySettingsRepositoryInterface>(
|
||||
() => PrivacySettingsRepositoryImpl(
|
||||
Modular.get<DataConnectService>(),
|
||||
),
|
||||
);
|
||||
|
||||
// Use Cases
|
||||
i.addSingleton(
|
||||
() => GetPrivacySettingsUseCase(
|
||||
i<PrivacySettingsRepositoryInterface>(),
|
||||
),
|
||||
);
|
||||
i.addSingleton(
|
||||
() => UpdateLocationSharingUseCase(
|
||||
i<PrivacySettingsRepositoryInterface>(),
|
||||
),
|
||||
);
|
||||
i.addSingleton(
|
||||
() => GetTermsUseCase(
|
||||
i<PrivacySettingsRepositoryInterface>(),
|
||||
),
|
||||
);
|
||||
i.addSingleton(
|
||||
() => GetPrivacyPolicyUseCase(
|
||||
i<PrivacySettingsRepositoryInterface>(),
|
||||
),
|
||||
);
|
||||
|
||||
// BLoC
|
||||
i.add(
|
||||
() => PrivacySecurityBloc(
|
||||
getPrivacySettingsUseCase: i(),
|
||||
updateLocationSharingUseCase: i(),
|
||||
getTermsUseCase: i(),
|
||||
getPrivacyPolicyUseCase: i(),
|
||||
),
|
||||
);
|
||||
|
||||
// Legal Cubits
|
||||
i.add(
|
||||
() => TermsCubit(
|
||||
getTermsUseCase: i<GetTermsUseCase>(),
|
||||
),
|
||||
);
|
||||
|
||||
i.add(
|
||||
() => PrivacyPolicyCubit(
|
||||
getPrivacyPolicyUseCase: i<GetPrivacyPolicyUseCase>(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void routes(RouteManager r) {
|
||||
// Main privacy security page
|
||||
r.child(
|
||||
StaffPaths.childRoute(
|
||||
StaffPaths.privacySecurity,
|
||||
StaffPaths.privacySecurity,
|
||||
),
|
||||
child: (BuildContext context) => const PrivacySecurityPage(),
|
||||
);
|
||||
|
||||
// Terms of Service page
|
||||
r.child(
|
||||
StaffPaths.childRoute(
|
||||
StaffPaths.privacySecurity,
|
||||
StaffPaths.termsOfService,
|
||||
),
|
||||
child: (BuildContext context) => const TermsOfServicePage(),
|
||||
);
|
||||
|
||||
// Privacy Policy page
|
||||
r.child(
|
||||
StaffPaths.childRoute(
|
||||
StaffPaths.privacySecurity,
|
||||
StaffPaths.privacyPolicy,
|
||||
),
|
||||
child: (BuildContext context) => const PrivacyPolicyPage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 'src/staff_privacy_security_module.dart';
|
||||
@@ -0,0 +1,42 @@
|
||||
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
|
||||
assets:
|
||||
- lib/src/assets/legal/
|
||||
@@ -187,8 +187,6 @@ class ShiftsRepositoryImpl
|
||||
.listShiftRolesByVendorId(vendorId: vendorId)
|
||||
.execute());
|
||||
|
||||
|
||||
|
||||
final allShiftRoles = result.data.shiftRoles;
|
||||
|
||||
// Fetch my applications to filter out already booked shifts
|
||||
|
||||
@@ -175,30 +175,30 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
||||
child: state is ShiftsLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: state is ShiftsError
|
||||
? Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
translateErrorKey(state.message),
|
||||
style: UiTypography.body2r.textSecondary,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
? Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
translateErrorKey(state.message),
|
||||
style: UiTypography.body2r.textSecondary,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
)
|
||||
: _buildTabContent(
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: _buildTabContent(
|
||||
myShifts,
|
||||
pendingAssignments,
|
||||
cancelledShifts,
|
||||
availableJobs,
|
||||
historyShifts,
|
||||
availableLoading,
|
||||
historyLoading,
|
||||
),
|
||||
pendingAssignments,
|
||||
cancelledShifts,
|
||||
availableJobs,
|
||||
historyShifts,
|
||||
availableLoading,
|
||||
historyLoading,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -254,14 +254,14 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
||||
onTap: !enabled
|
||||
? null
|
||||
: () {
|
||||
setState(() => _activeTab = id);
|
||||
if (id == 'history') {
|
||||
_bloc.add(LoadHistoryShiftsEvent());
|
||||
}
|
||||
if (id == 'find') {
|
||||
_bloc.add(LoadAvailableShiftsEvent());
|
||||
}
|
||||
},
|
||||
setState(() => _activeTab = id);
|
||||
if (id == 'history') {
|
||||
_bloc.add(LoadHistoryShiftsEvent());
|
||||
}
|
||||
if (id == 'find') {
|
||||
_bloc.add(LoadAvailableShiftsEvent());
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: UiConstants.space2,
|
||||
@@ -290,9 +290,17 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
||||
Flexible(
|
||||
child: Text(
|
||||
label,
|
||||
style: (isActive ? UiTypography.body3m.copyWith(color: UiColors.primary) : UiTypography.body3m.white).copyWith(
|
||||
color: !enabled ? UiColors.white.withValues(alpha: 0.5) : null,
|
||||
),
|
||||
style:
|
||||
(isActive
|
||||
? UiTypography.body3m.copyWith(
|
||||
color: UiColors.primary,
|
||||
)
|
||||
: UiTypography.body3m.white)
|
||||
.copyWith(
|
||||
color: !enabled
|
||||
? UiColors.white.withValues(alpha: 0.5)
|
||||
: null,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -32,6 +32,8 @@ dependencies:
|
||||
url_launcher: ^6.3.1
|
||||
firebase_auth: ^6.1.4
|
||||
firebase_data_connect: ^0.2.2+2
|
||||
meta: ^1.17.0
|
||||
bloc: ^8.1.4
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'package:staff_home/staff_home.dart';
|
||||
import 'package:staff_main/src/presentation/blocs/staff_main_cubit.dart';
|
||||
import 'package:staff_main/src/presentation/pages/staff_main_page.dart';
|
||||
import 'package:staff_payments/staff_payements.dart';
|
||||
import 'package:staff_privacy_security/staff_privacy_security.dart';
|
||||
import 'package:staff_profile/staff_profile.dart';
|
||||
import 'package:staff_profile_experience/staff_profile_experience.dart';
|
||||
import 'package:staff_profile_info/staff_profile_info.dart';
|
||||
@@ -93,6 +94,10 @@ class StaffMainModule extends Module {
|
||||
StaffPaths.childRoute(StaffPaths.main, StaffPaths.availability),
|
||||
module: StaffAvailabilityModule(),
|
||||
);
|
||||
r.module(
|
||||
StaffPaths.childRoute(StaffPaths.main, StaffPaths.privacySecurity),
|
||||
module: PrivacySecurityModule(),
|
||||
);
|
||||
r.module(
|
||||
StaffPaths.childRoute(StaffPaths.main, StaffPaths.shiftDetailsRoute),
|
||||
module: ShiftDetailsModule(),
|
||||
|
||||
@@ -20,6 +20,8 @@ dependencies:
|
||||
path: ../../../design_system
|
||||
core_localization:
|
||||
path: ../../../core_localization
|
||||
krow_core:
|
||||
path: ../../../krow_core
|
||||
|
||||
# Features
|
||||
staff_home:
|
||||
@@ -52,6 +54,8 @@ dependencies:
|
||||
path: ../availability
|
||||
staff_clock_in:
|
||||
path: ../clock_in
|
||||
staff_privacy_security:
|
||||
path: ../profile_sections/settings/privacy_security
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
@@ -1290,6 +1290,13 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
staff_privacy_security:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "packages/features/staff/profile_sections/settings/privacy_security"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
Reference in New Issue
Block a user