Add initial mobile app prototypes and staff payments feature

Introduces the first versions of client and staff mobile application prototypes, including platform-specific assets, screens, and configuration for Android, iOS, macOS, Linux, and Windows. Adds documentation, AI prompt guides, and a new staff payments feature module with repository, use cases, and presentation logic. Also includes generated localization files and supporting resources for both client and staff apps.
This commit is contained in:
Achintha Isuru
2026-01-25 17:01:18 -05:00
parent 0b043ed91e
commit 19959a2b3f
468 changed files with 71399 additions and 0 deletions

View File

@@ -0,0 +1,183 @@
/// Generated file. Do not edit.
///
/// Source: lib/src/l10n
/// To regenerate, run: `dart run slang`
///
/// Locales: 2
/// Strings: 1004 (502 per locale)
///
/// Built on 2026-01-25 at 22:00 UTC
// coverage:ignore-file
// ignore_for_file: type=lint, unused_import
// dart format off
import 'package:flutter/widgets.dart';
import 'package:intl/intl.dart';
import 'package:slang/generated.dart';
import 'package:slang_flutter/slang_flutter.dart';
export 'package:slang_flutter/slang_flutter.dart';
import 'strings_es.g.dart' deferred as l_es;
part 'strings_en.g.dart';
/// Supported locales.
///
/// Usage:
/// - LocaleSettings.setLocale(AppLocale.en) // set locale
/// - Locale locale = AppLocale.en.flutterLocale // get flutter locale from enum
/// - if (LocaleSettings.currentLocale == AppLocale.en) // locale check
enum AppLocale with BaseAppLocale<AppLocale, Translations> {
en(languageCode: 'en'),
es(languageCode: 'es');
const AppLocale({
required this.languageCode,
this.scriptCode, // ignore: unused_element, unused_element_parameter
this.countryCode, // ignore: unused_element, unused_element_parameter
});
@override final String languageCode;
@override final String? scriptCode;
@override final String? countryCode;
@override
Future<Translations> build({
Map<String, Node>? overrides,
PluralResolver? cardinalResolver,
PluralResolver? ordinalResolver,
}) async {
switch (this) {
case AppLocale.en:
return TranslationsEn(
overrides: overrides,
cardinalResolver: cardinalResolver,
ordinalResolver: ordinalResolver,
);
case AppLocale.es:
await l_es.loadLibrary();
return l_es.TranslationsEs(
overrides: overrides,
cardinalResolver: cardinalResolver,
ordinalResolver: ordinalResolver,
);
}
}
@override
Translations buildSync({
Map<String, Node>? overrides,
PluralResolver? cardinalResolver,
PluralResolver? ordinalResolver,
}) {
switch (this) {
case AppLocale.en:
return TranslationsEn(
overrides: overrides,
cardinalResolver: cardinalResolver,
ordinalResolver: ordinalResolver,
);
case AppLocale.es:
return l_es.TranslationsEs(
overrides: overrides,
cardinalResolver: cardinalResolver,
ordinalResolver: ordinalResolver,
);
}
}
/// Gets current instance managed by [LocaleSettings].
Translations get translations => LocaleSettings.instance.getTranslations(this);
}
/// Method A: Simple
///
/// No rebuild after locale change.
/// Translation happens during initialization of the widget (call of t).
/// Configurable via 'translate_var'.
///
/// Usage:
/// String a = t.someKey.anotherKey;
/// String b = t['someKey.anotherKey']; // Only for edge cases!
Translations get t => LocaleSettings.instance.currentTranslations;
/// Method B: Advanced
///
/// All widgets using this method will trigger a rebuild when locale changes.
/// Use this if you have e.g. a settings page where the user can select the locale during runtime.
///
/// Step 1:
/// wrap your App with
/// TranslationProvider(
/// child: MyApp()
/// );
///
/// Step 2:
/// final t = Translations.of(context); // Get t variable.
/// String a = t.someKey.anotherKey; // Use t variable.
/// String b = t['someKey.anotherKey']; // Only for edge cases!
class TranslationProvider extends BaseTranslationProvider<AppLocale, Translations> {
TranslationProvider({required super.child}) : super(settings: LocaleSettings.instance);
static InheritedLocaleData<AppLocale, Translations> of(BuildContext context) => InheritedLocaleData.of<AppLocale, Translations>(context);
}
/// Method B shorthand via [BuildContext] extension method.
/// Configurable via 'translate_var'.
///
/// Usage (e.g. in a widget's build method):
/// context.t.someKey.anotherKey
extension BuildContextTranslationsExtension on BuildContext {
Translations get t => TranslationProvider.of(this).translations;
}
/// Manages all translation instances and the current locale
class LocaleSettings extends BaseFlutterLocaleSettings<AppLocale, Translations> {
LocaleSettings._() : super(
utils: AppLocaleUtils.instance,
lazy: true,
);
static final instance = LocaleSettings._();
// static aliases (checkout base methods for documentation)
static AppLocale get currentLocale => instance.currentLocale;
static Stream<AppLocale> getLocaleStream() => instance.getLocaleStream();
static Future<AppLocale> setLocale(AppLocale locale, {bool? listenToDeviceLocale = false}) => instance.setLocale(locale, listenToDeviceLocale: listenToDeviceLocale);
static Future<AppLocale> setLocaleRaw(String rawLocale, {bool? listenToDeviceLocale = false}) => instance.setLocaleRaw(rawLocale, listenToDeviceLocale: listenToDeviceLocale);
static Future<AppLocale> useDeviceLocale() => instance.useDeviceLocale();
static Future<void> setPluralResolver({String? language, AppLocale? locale, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver}) => instance.setPluralResolver(
language: language,
locale: locale,
cardinalResolver: cardinalResolver,
ordinalResolver: ordinalResolver,
);
// synchronous versions
static AppLocale setLocaleSync(AppLocale locale, {bool? listenToDeviceLocale = false}) => instance.setLocaleSync(locale, listenToDeviceLocale: listenToDeviceLocale);
static AppLocale setLocaleRawSync(String rawLocale, {bool? listenToDeviceLocale = false}) => instance.setLocaleRawSync(rawLocale, listenToDeviceLocale: listenToDeviceLocale);
static AppLocale useDeviceLocaleSync() => instance.useDeviceLocaleSync();
static void setPluralResolverSync({String? language, AppLocale? locale, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver}) => instance.setPluralResolverSync(
language: language,
locale: locale,
cardinalResolver: cardinalResolver,
ordinalResolver: ordinalResolver,
);
}
/// Provides utility functions without any side effects.
class AppLocaleUtils extends BaseAppLocaleUtils<AppLocale, Translations> {
AppLocaleUtils._() : super(
baseLocale: AppLocale.en,
locales: AppLocale.values,
);
static final instance = AppLocaleUtils._();
// static aliases (checkout base methods for documentation)
static AppLocale parse(String rawLocale) => instance.parse(rawLocale);
static AppLocale parseLocaleParts({required String languageCode, String? scriptCode, String? countryCode}) => instance.parseLocaleParts(languageCode: languageCode, scriptCode: scriptCode, countryCode: countryCode);
static AppLocale findDeviceLocale() => instance.findDeviceLocale();
static List<Locale> get supportedLocales => instance.supportedLocales;
static List<String> get supportedLocalesRaw => instance.supportedLocalesRaw;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,15 @@
import 'package:krow_data_connect/krow_data_connect.dart';
import 'package:krow_domain/krow_domain.dart';
import '../../domain/repositories/payments_repository.dart';
class PaymentsRepositoryImpl implements PaymentsRepository {
final FinancialRepositoryMock financialRepository;
PaymentsRepositoryImpl({required this.financialRepository});
@override
Future<List<StaffPayment>> getPayments() async {
// TODO: Get actual logged in staff ID
return await financialRepository.getStaffPayments('staff_1');
}
}

View File

@@ -1,3 +1,4 @@
<<<<<<< Updated upstream
import '../entities/payment_summary.dart';
import '../entities/payment_transaction.dart';
@@ -7,4 +8,11 @@ abstract class PaymentsRepository {
/// Fetches the list of recent payment transactions (history).
Future<List<PaymentTransaction>> getPaymentHistory(String period);
=======
import 'package:krow_domain/krow_domain.dart';
abstract class PaymentsRepository {
/// Fetches the list of payments.
Future<List<StaffPayment>> getPayments();
>>>>>>> Stashed changes
}

View File

@@ -1,12 +1,27 @@
<<<<<<< Updated upstream
import '../entities/payment_transaction.dart';
import '../repositories/payments_repository.dart';
class GetPaymentHistoryUseCase {
=======
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import '../repositories/payments_repository.dart';
class GetPaymentHistoryUseCase extends UseCase<String, List<StaffPayment>> {
>>>>>>> Stashed changes
final PaymentsRepository repository;
GetPaymentHistoryUseCase(this.repository);
<<<<<<< Updated upstream
Future<List<PaymentTransaction>> call({String period = 'week'}) async {
return await repository.getPaymentHistory(period);
=======
@override
Future<List<StaffPayment>> call(String period) async {
// TODO: Implement filtering by period
return await repository.getPayments();
>>>>>>> Stashed changes
}
}

View File

@@ -1,12 +1,26 @@
<<<<<<< Updated upstream
import '../entities/payment_summary.dart';
import '../repositories/payments_repository.dart';
class GetPaymentSummaryUseCase {
=======
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import '../repositories/payments_repository.dart';
class GetPaymentSummaryUseCase extends NoInputUseCase<List<StaffPayment>> {
>>>>>>> Stashed changes
final PaymentsRepository repository;
GetPaymentSummaryUseCase(this.repository);
<<<<<<< Updated upstream
Future<PaymentSummary> call() async {
return await repository.getPaymentSummary();
=======
@override
Future<List<StaffPayment>> call() async {
return await repository.getPayments();
>>>>>>> Stashed changes
}
}

View File

@@ -1,18 +1,31 @@
import 'package:flutter_modular/flutter_modular.dart';
<<<<<<< Updated upstream
import 'domain/repositories/payments_repository.dart';
import 'domain/usecases/get_payment_summary_usecase.dart';
import 'domain/usecases/get_payment_history_usecase.dart';
import 'data/datasources/payments_remote_datasource.dart';
import 'data/datasources/payments_mock_datasource.dart';
import 'data/repositories/payments_repository_impl.dart';
=======
import 'package:krow_data_connect/krow_data_connect.dart';
import 'domain/repositories/payments_repository.dart';
import 'domain/usecases/get_payment_summary_usecase.dart';
import 'domain/usecases/get_payment_history_usecase.dart';
import 'data/repositories_impl/payments_repository_impl.dart';
>>>>>>> Stashed changes
import 'presentation/blocs/payments/payments_bloc.dart';
import 'presentation/pages/payments_page.dart';
class StaffPaymentsModule extends Module {
@override
void binds(Injector i) {
<<<<<<< Updated upstream
// Data Sources
i.add<PaymentsRemoteDataSource>(PaymentsMockDataSource.new);
=======
// Data Connect Mocks
i.add<FinancialRepositoryMock>(FinancialRepositoryMock.new);
>>>>>>> Stashed changes
// Repositories
i.add<PaymentsRepository>(PaymentsRepositoryImpl.new);

View File

@@ -1,8 +1,15 @@
import 'package:flutter_bloc/flutter_bloc.dart';
<<<<<<< Updated upstream
import '../../../domain/entities/payment_summary.dart';
import '../../../domain/entities/payment_transaction.dart';
import '../../../domain/usecases/get_payment_summary_usecase.dart';
import '../../../domain/usecases/get_payment_history_usecase.dart';
=======
import 'package:krow_domain/krow_domain.dart';
import '../../../domain/usecases/get_payment_summary_usecase.dart';
import '../../../domain/usecases/get_payment_history_usecase.dart';
import '../../models/payment_stats.dart';
>>>>>>> Stashed changes
import 'payments_event.dart';
import 'payments_state.dart';
@@ -24,10 +31,19 @@ class PaymentsBloc extends Bloc<PaymentsEvent, PaymentsState> {
) async {
emit(PaymentsLoading());
try {
<<<<<<< Updated upstream
final PaymentSummary summary = await getPaymentSummary();
final List<PaymentTransaction> history = await getPaymentHistory(period: 'week');
emit(PaymentsLoaded(
summary: summary,
=======
final List<StaffPayment> allPayments = await getPaymentSummary();
final PaymentStats stats = _calculateStats(allPayments);
final List<StaffPayment> history = await getPaymentHistory('week');
emit(PaymentsLoaded(
summary: stats,
>>>>>>> Stashed changes
history: history,
activePeriod: 'week',
));
@@ -44,10 +60,15 @@ class PaymentsBloc extends Bloc<PaymentsEvent, PaymentsState> {
if (currentState is PaymentsLoaded) {
if (currentState.activePeriod == event.period) return;
<<<<<<< Updated upstream
// Optimistic update or set loading state if expecting delay
// For now, we'll keep the current data and fetch new history
try {
final List<PaymentTransaction> newHistory = await getPaymentHistory(period: event.period);
=======
try {
final List<StaffPayment> newHistory = await getPaymentHistory(event.period);
>>>>>>> Stashed changes
emit(currentState.copyWith(
history: newHistory,
activePeriod: event.period,
@@ -57,4 +78,41 @@ class PaymentsBloc extends Bloc<PaymentsEvent, PaymentsState> {
}
}
}
<<<<<<< Updated upstream
=======
PaymentStats _calculateStats(List<StaffPayment> payments) {
double total = 0;
double pending = 0;
double weekly = 0;
double monthly = 0;
final DateTime now = DateTime.now();
for (final StaffPayment p in payments) {
// Assuming all payments count towards total history
total += p.amount;
if (p.status == PaymentStatus.pending) {
pending += p.amount;
}
if (p.paidAt != null) {
if (now.difference(p.paidAt!).inDays < 7) {
weekly += p.amount;
}
if (now.month == p.paidAt!.month && now.year == p.paidAt!.year) {
monthly += p.amount;
}
}
}
return PaymentStats(
totalEarnings: total,
pendingEarnings: pending,
weeklyEarnings: weekly,
monthlyEarnings: monthly,
);
}
>>>>>>> Stashed changes
}

View File

@@ -1,6 +1,11 @@
import 'package:equatable/equatable.dart';
<<<<<<< Updated upstream
import '../../../domain/entities/payment_summary.dart';
import '../../../domain/entities/payment_transaction.dart';
=======
import 'package:krow_domain/krow_domain.dart';
import '../../models/payment_stats.dart';
>>>>>>> Stashed changes
abstract class PaymentsState extends Equatable {
const PaymentsState();
@@ -14,8 +19,13 @@ class PaymentsInitial extends PaymentsState {}
class PaymentsLoading extends PaymentsState {}
class PaymentsLoaded extends PaymentsState {
<<<<<<< Updated upstream
final PaymentSummary summary;
final List<PaymentTransaction> history;
=======
final PaymentStats summary;
final List<StaffPayment> history;
>>>>>>> Stashed changes
final String activePeriod;
const PaymentsLoaded({
@@ -25,8 +35,13 @@ class PaymentsLoaded extends PaymentsState {
});
PaymentsLoaded copyWith({
<<<<<<< Updated upstream
PaymentSummary? summary,
List<PaymentTransaction>? history,
=======
PaymentStats? summary,
List<StaffPayment>? history,
>>>>>>> Stashed changes
String? activePeriod,
}) {
return PaymentsLoaded(

View File

@@ -0,0 +1,23 @@
import 'package:equatable/equatable.dart';
class PaymentStats extends Equatable {
final double weeklyEarnings;
final double monthlyEarnings;
final double pendingEarnings;
final double totalEarnings;
const PaymentStats({
this.weeklyEarnings = 0.0,
this.monthlyEarnings = 0.0,
this.pendingEarnings = 0.0,
this.totalEarnings = 0.0,
});
@override
List<Object?> get props => [
weeklyEarnings,
monthlyEarnings,
pendingEarnings,
totalEarnings,
];
}

View File

@@ -3,7 +3,11 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:intl/intl.dart';
<<<<<<< Updated upstream
import '../../domain/entities/payment_transaction.dart';
=======
import 'package:krow_domain/krow_domain.dart';
>>>>>>> Stashed changes
import '../blocs/payments/payments_bloc.dart';
import '../blocs/payments/payments_event.dart';
import '../blocs/payments/payments_state.dart';
@@ -177,11 +181,16 @@ class _PaymentsPageState extends State<PaymentsPage> {
),
const SizedBox(height: 12),
Column(
<<<<<<< Updated upstream
children: state.history.map((PaymentTransaction payment) {
=======
children: state.history.map((StaffPayment payment) {
>>>>>>> Stashed changes
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: PaymentHistoryItem(
amount: payment.amount,
<<<<<<< Updated upstream
title: payment.title,
location: payment.location,
address: payment.address,
@@ -190,6 +199,16 @@ class _PaymentsPageState extends State<PaymentsPage> {
hours: payment.hours,
rate: payment.rate,
status: payment.status,
=======
title: 'Assignment ${payment.assignmentId}',
location: 'Location', // TODO: Fetch from assignment
address: '',
date: payment.paidAt != null ? DateFormat('E, MMM d').format(payment.paidAt!) : 'Pending',
workedTime: '00:00 - 00:00', // TODO: Fetch from assignment
hours: 0,
rate: 0,
status: payment.status.toString().split('.').last,
>>>>>>> Stashed changes
),
);
}).toList(),

View File

@@ -2,9 +2,16 @@ name: staff_payments
description: Staff Payments feature
version: 0.0.1
publish_to: 'none'
<<<<<<< Updated upstream
environment:
sdk: '>=3.0.0 <4.0.0'
=======
resolution: workspace
environment:
sdk: '>=3.10.0 <4.0.0'
>>>>>>> Stashed changes
flutter: ">=3.0.0"
dependencies:
@@ -21,6 +28,11 @@ dependencies:
path: ../../../core_localization
krow_domain:
path: ../../../domain
<<<<<<< Updated upstream
=======
krow_core:
path: ../../../core
>>>>>>> Stashed changes
dev_dependencies:
flutter_test: