Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
1790d3a
Separates change_email_verification.dart into page, controller and wi…
JasonYoder2026 Mar 18, 2026
618447a
Adds tests for change_email_verification.dart
JasonYoder2026 Mar 20, 2026
867a7e0
Separates edit_profile.dart
JasonYoder2026 Mar 20, 2026
3787749
Correct edit_profile feature
JasonYoder2026 Mar 20, 2026
bd49a11
Adds tests for edit_profile feature
JasonYoder2026 Mar 20, 2026
c9e9f17
Cleans tests
JasonYoder2026 Mar 20, 2026
141e742
Separates email_verification.dart
JasonYoder2026 Mar 20, 2026
6cdabb6
Adds tests for email_verification feature
JasonYoder2026 Mar 20, 2026
a9a7b71
Separates home page feature
JasonYoder2026 Mar 20, 2026
0a0a38e
Adds tests for home feature
JasonYoder2026 Mar 20, 2026
02c2560
Separates loading feature
JasonYoder2026 Mar 20, 2026
9eaa0b4
Fix controller parameters
JasonYoder2026 Mar 20, 2026
f36e95e
Refactor loading
JasonYoder2026 Mar 20, 2026
a39683d
Adds tests for loading
JasonYoder2026 Mar 20, 2026
3ebcd9e
Cleans tests
JasonYoder2026 Mar 20, 2026
47ce4d8
Separates login into features/login
JasonYoder2026 Mar 20, 2026
9ba130f
Refactors controller to use DI parameters
JasonYoder2026 Mar 20, 2026
682f001
Adds tests for login
JasonYoder2026 Mar 20, 2026
656572d
Separates loyalty_card_page.dart into loyalty feature
JasonYoder2026 Mar 21, 2026
ff9d6f5
Separates loyalty_card_page.dart into loyalty feature
JasonYoder2026 Mar 21, 2026
9e00dca
Removes unneeded DI
JasonYoder2026 Mar 21, 2026
1c40599
Adds tests for loyalty feature
JasonYoder2026 Mar 21, 2026
4aabc22
Separates monthly report
JasonYoder2026 Mar 21, 2026
6bdda7c
Adds tests for monthly report
JasonYoder2026 Mar 21, 2026
3cf788e
Moves not found page to features
JasonYoder2026 Mar 21, 2026
12aee0c
Separates reset password
JasonYoder2026 Mar 21, 2026
4cb3ca1
Fixes controller by adding trimming
JasonYoder2026 Mar 21, 2026
4881cef
Adds tests for password_reset
JasonYoder2026 Mar 21, 2026
6732781
Separates payment page into feature
JasonYoder2026 Mar 21, 2026
a1920cd
Renames payment_page to machine_payment
JasonYoder2026 Mar 21, 2026
94c6909
Adds tests for machine_payment
JasonYoder2026 Mar 21, 2026
f1684e9
Separates request refund
JasonYoder2026 Mar 22, 2026
b95d258
Adds tests for refund
JasonYoder2026 Mar 22, 2026
efa18fe
Separates reset_protected
JasonYoder2026 Mar 22, 2026
9fe4fdb
Adds tests for reset_protected
JasonYoder2026 Mar 22, 2026
79e395b
Moves root_app to lib/
JasonYoder2026 Mar 22, 2026
b7850ac
Separates scanner
JasonYoder2026 Mar 22, 2026
002a6a4
Adds tests for scanner
JasonYoder2026 Mar 22, 2026
90e2b76
Separates settings
JasonYoder2026 Mar 22, 2026
2dfe5d6
Adds tests for settings
JasonYoder2026 Mar 23, 2026
fde8cab
Separates signup
JasonYoder2026 Mar 23, 2026
e06a678
Adds tests for sign_up
JasonYoder2026 Mar 23, 2026
e9b11fc
Separates start_machine
JasonYoder2026 Mar 23, 2026
07d2f66
Adds tests for start_machine
JasonYoder2026 Mar 23, 2026
4aeb438
Separates verification code
JasonYoder2026 Mar 23, 2026
c73ace2
Adds tests for code verification
JasonYoder2026 Mar 23, 2026
d00de02
Fixes routes in tests
JasonYoder2026 Mar 23, 2026
85896c6
Replaces pages in router with features
JasonYoder2026 Mar 23, 2026
8ef0bce
Fixes missing colon
JasonYoder2026 Mar 23, 2026
7682ef1
Fixes qr scanner
JasonYoder2026 Mar 23, 2026
0d0a7bd
Removes pages/ replaces pages with features
JasonYoder2026 Mar 23, 2026
5e111f4
Moves widget
JasonYoder2026 Mar 23, 2026
2f5f087
Cleans up entry point
JasonYoder2026 Mar 23, 2026
bbb8f26
Fixes DI in tests
JasonYoder2026 Mar 23, 2026
4820534
Moves router to core/
JasonYoder2026 Mar 23, 2026
1fb461f
Moves theme to core/
JasonYoder2026 Mar 23, 2026
02a77a9
Adjusts imports and tests
JasonYoder2026 Mar 23, 2026
2e7ffa9
Adjusts imports
JasonYoder2026 Mar 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions lib/core/di/di.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:get_it/get_it.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:flutter_stripe/flutter_stripe.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:go_router/go_router.dart';

// services
import 'package:clean_stream_laundry_app/logic/services/auth_service.dart';
import 'package:clean_stream_laundry_app/logic/services/edge_function_service.dart';
import 'package:clean_stream_laundry_app/logic/services/location_service.dart';
import 'package:clean_stream_laundry_app/logic/services/machine_communication_service.dart';
import 'package:clean_stream_laundry_app/logic/services/machine_service.dart';
import 'package:clean_stream_laundry_app/logic/services/payment_service.dart';
import 'package:clean_stream_laundry_app/logic/services/profile_service.dart';
import 'package:clean_stream_laundry_app/logic/services/transaction_service.dart';

// implementations
import 'package:clean_stream_laundry_app/services/supabase/supabase_auth_service.dart';
import 'package:clean_stream_laundry_app/services/supabase/supabase_edge_function_service.dart';
import 'package:clean_stream_laundry_app/services/supabase/supabase_location_service.dart';
import 'package:clean_stream_laundry_app/services/supabase/supabase_machine_service.dart';
import 'package:clean_stream_laundry_app/services/supabase/supabase_profile_service.dart';
import 'package:clean_stream_laundry_app/services/supabase/supabase_transaction_service.dart';
import 'package:clean_stream_laundry_app/services/stripe/stripe_service.dart';
import 'package:clean_stream_laundry_app/services/nayax/machine_communicator.dart';

// misc
import 'package:clean_stream_laundry_app/core/router/app_router.dart';
import 'package:clean_stream_laundry_app/services/notification_service.dart';
import 'package:clean_stream_laundry_app/logic/payment/process_payment.dart';

final getIt = GetIt.instance;

Future<void> setupDependencies() async {
await dotenv.load(fileName: '.env');

await Supabase.initialize(
url: dotenv.env['SUPABASE_URL']!,
anonKey: dotenv.env['ANON_KEY']!,
);

final supabase = Supabase.instance.client;

Stripe.publishableKey = dotenv.env['STRIPE_PUBLISHABLE_KEY']!;

getIt.registerLazySingleton<TransactionService>(
() => SupabaseTransactionService(client: supabase),
);

getIt.registerLazySingleton<ProfileService>(
() => SupabaseProfileService(client: supabase),
);

getIt.registerLazySingleton<MachineService>(
() => SupabaseMachineService(client: supabase),
);

getIt.registerLazySingleton<LocationService>(
() => SupabaseLocationHandler(client: supabase),
);

getIt.registerLazySingleton<EdgeFunctionService>(
() => SupabaseEdgeFunctionService(client: supabase),
);

getIt.registerLazySingleton<AuthService>(
() => SupabaseAuthService(client: supabase),
);

getIt.registerLazySingleton<PaymentService>(() => StripeService());

getIt.registerLazySingleton<Stripe>(() => Stripe.instance);

getIt.registerLazySingleton<MachineCommunicationService>(
() => MachineCommunicator(
edgeFunctionService: getIt<EdgeFunctionService>(),
),
);

getIt.registerLazySingleton<RouterService>(() => RouterService());

getIt.registerLazySingleton<NotificationService>(
() => NotificationService(),
);

getIt.registerSingleton<FlutterLocalNotificationsPlugin>(
FlutterLocalNotificationsPlugin(),
);

getIt.registerLazySingleton<PaymentProcessor>(
() => PaymentProcessor(),
);

getIt.registerLazySingleton<GoRouter>(() {
final authService = getIt<AuthService>();
final routerService = getIt<RouterService>();

return routerService.createRouter(authService);
});
}
50 changes: 25 additions & 25 deletions lib/middleware/app_router.dart → lib/core/router/app_router.dart
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import 'package:app_links/app_links.dart';
import 'package:clean_stream_laundry_app/pages/change_email_verification.dart';
import 'package:clean_stream_laundry_app/pages/edit_profile_page.dart';
import 'package:clean_stream_laundry_app/pages/verify_code_page.dart';
import 'package:go_router/go_router.dart';
import 'package:clean_stream_laundry_app/features/change_email_verification/change_email_verification.dart';
import 'package:clean_stream_laundry_app/features/edit_profile/edit_profile.dart';
import 'package:clean_stream_laundry_app/features/verify_code/verify_code.dart';
import 'package:clean_stream_laundry_app/features/email_verification/email_verification.dart';
import 'package:clean_stream_laundry_app/features/home/home.dart';
import 'package:clean_stream_laundry_app/features/loading/loading.dart';
import 'package:clean_stream_laundry_app/features/loyalty/loyalty.dart';
import 'package:clean_stream_laundry_app/features/scanner/scanner.dart';
import 'package:clean_stream_laundry_app/features/sign_up/sign_up.dart';
import 'package:clean_stream_laundry_app/features/login/login.dart';
import 'package:clean_stream_laundry_app/features/not_found/not_found.dart';
import 'package:clean_stream_laundry_app/features/settings/settings.dart';
import 'package:clean_stream_laundry_app/features/start_machine/start_machine.dart';
import 'package:clean_stream_laundry_app/features/machine_payment/machine_payment.dart';
import 'package:clean_stream_laundry_app/features/monthly_report/monthly_report.dart';
import 'package:clean_stream_laundry_app/features/refund_request/refund_request.dart';
import 'package:clean_stream_laundry_app/features/password_reset/password_reset.dart';
import 'package:clean_stream_laundry_app/features/reset_protected/reset_protected.dart';
import 'package:clean_stream_laundry_app/logic/services/auth_service.dart';
import 'package:clean_stream_laundry_app/pages/email_verification_page.dart';
import 'package:clean_stream_laundry_app/pages/home_page.dart';
import 'package:clean_stream_laundry_app/pages/loading_page.dart';
import 'package:clean_stream_laundry_app/pages/loyalty_card_page.dart';
import 'package:clean_stream_laundry_app/pages/scanner_widget.dart';
import 'package:clean_stream_laundry_app/pages/sign_up_screen.dart';
import 'package:clean_stream_laundry_app/pages/login_page.dart';
import 'package:clean_stream_laundry_app/pages/not_found_page.dart';
import 'package:clean_stream_laundry_app/pages/settings.dart';
import 'package:clean_stream_laundry_app/pages/start_machine_page.dart';
import 'package:clean_stream_laundry_app/pages/payment_page.dart';
import 'package:clean_stream_laundry_app/pages/monthly_transaction_history.dart';
import 'package:clean_stream_laundry_app/pages/refund_page.dart';
import 'package:clean_stream_laundry_app/pages/password_reset.dart';
import 'package:clean_stream_laundry_app/pages/reset_protected_page.dart';
import 'package:go_router/go_router.dart';

class RouterService {
GoRouter createRouter(AuthService authenticator) => GoRouter(
Expand All @@ -28,7 +28,7 @@ class RouterService {
path: '/login',
pageBuilder: (context, state) => CustomTransitionPage(
key: state.pageKey,
child: LoginScreen(appLinks: AppLinks()),
child: Login(appLinks: AppLinks()),
transitionDuration: Duration.zero,
reverseTransitionDuration: Duration.zero,
transitionsBuilder: (_, _, _, child) => child,
Expand All @@ -38,7 +38,7 @@ class RouterService {
path: '/signup',
pageBuilder: (context, state) => CustomTransitionPage(
key: state.pageKey,
child: SignUpScreen(),
child: SignUpPage(),
transitionDuration: Duration.zero,
reverseTransitionDuration: Duration.zero,
transitionsBuilder: (_, _, _, child) => child,
Expand All @@ -48,7 +48,7 @@ class RouterService {
path: '/scanner',
pageBuilder: (context, state) => CustomTransitionPage(
key: state.pageKey,
child: ScannerWidget(),
child: ScannerPage(),
transitionDuration: Duration.zero,
reverseTransitionDuration: Duration.zero,
transitionsBuilder: (_, _, _, child) => child,
Expand Down Expand Up @@ -81,7 +81,7 @@ class RouterService {
final machineId = state.uri.queryParameters['machineId'] ?? '';
return CustomTransitionPage(
key: state.pageKey,
child: PaymentPage(machineId: machineId),
child: MachinePayment(machineId: machineId),
transitionDuration: Duration.zero,
reverseTransitionDuration: Duration.zero,
transitionsBuilder: (_, _, _, child) => child,
Expand Down Expand Up @@ -134,7 +134,7 @@ class RouterService {
final transactions = state.extra as List<Map<String, dynamic>>? ?? [];
return CustomTransitionPage(
key: state.pageKey,
child: MonthlyTransactionHistory(transactions: transactions),
child: MonthlyReport(transactions: transactions),
transitionDuration: Duration.zero,
reverseTransitionDuration: Duration.zero,
transitionsBuilder: (_, _, _, child) => child,
Expand Down Expand Up @@ -202,7 +202,7 @@ class RouterService {
if (uri.scheme == 'clean-stream' && uri.host == 'reset-protected') {
return ResetProtectedPage();
}
return const NotFoundScreen();
return const NotFound();
},
redirect: (context, state) {
final uri = state.uri;
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:clean_stream_laundry_app/logic/theme/theme.dart';
import 'package:clean_stream_laundry_app/middleware/storage_service.dart';
import 'package:clean_stream_laundry_app/core/theme/theme.dart';
import 'package:clean_stream_laundry_app/core/storage/storage_service.dart';

class ThemeManager with ChangeNotifier {
ThemeData _themeData = lightMode;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import 'package:clean_stream_laundry_app/core/theme/theme.dart';
import 'package:flutter/material.dart';
import 'package:app_links/app_links.dart';
import 'controller.dart';
import 'widgets/resend_verification.dart';

class ChangeEmailVerificationPage extends StatefulWidget {
final AppLinks appLinks;

const ChangeEmailVerificationPage({super.key, required this.appLinks});

@override
State<ChangeEmailVerificationPage> createState() =>
_ChangeEmailVerificationPageState();
}

class _ChangeEmailVerificationPageState
extends State<ChangeEmailVerificationPage> {
late final ChangeEmailVerificationController _controller;

@override
void initState() {
super.initState();
_controller = ChangeEmailVerificationController(
appLinks: widget.appLinks,
context: context,
);
_controller.init();
}

@override
void dispose() {
_controller.dispose();
super.dispose();
}

void _refresh() {
setState(() {});
}

@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).colorScheme.surface,
body: Center(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.email, size: 80, color: Colors.blueAccent),
const SizedBox(height: 24),
Text(
'Please verify your new email address',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18,
color: Theme.of(context).colorScheme.fontInverted,
),
),
const SizedBox(height: 16),
Text(
'Check your new email\'s inbox and click the verification link.',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).colorScheme.fontSecondary,
),
),
const SizedBox(height: 24),
ResendVerificationWidget(
controller: _controller,
onStateChange: _refresh,
),
],
),
),
),
);
}
}
60 changes: 60 additions & 0 deletions lib/features/change_email_verification/controller.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:clean_stream_laundry_app/logic/enums/authentication_response_enum.dart';
import 'package:clean_stream_laundry_app/logic/services/auth_service.dart';
import 'package:get_it/get_it.dart';
import 'package:go_router/go_router.dart';
import 'package:app_links/app_links.dart';

class ChangeEmailVerificationController {
final AuthService _authService = GetIt.instance<AuthService>();
final AppLinks appLinks;
final BuildContext context;

StreamSubscription? _linkSub;

bool resent = false;
bool isLoading = false;
AuthenticationResponses? lastResponse;

ChangeEmailVerificationController({
required this.appLinks,
required this.context,
});

void init() {
_linkSub = appLinks.uriLinkStream.listen(_handleUri);
}

void dispose() {
_linkSub?.cancel();
}

/// Handles deeplink from email
Future<void> _handleUri(Uri? uri) async {
if (uri != null &&
uri.scheme == 'clean-stream' &&
uri.host == 'change-email') {
await _authService.refreshSession();
await _authService.getCurrentUser();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (context.mounted) {
context.go('/editProfile');
}
});
}
}

/// Resends verification email
Future<void> resendVerification() async {
if (resent) return;

isLoading = true;
lastResponse = await _authService.resendVerification();
isLoading = false;

if (lastResponse == AuthenticationResponses.success) {
resent = true;
}
}
}
Loading
Loading