-
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add login screen, fix guards, check initial state
- Loading branch information
Showing
10 changed files
with
749 additions
and
44 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
// ignore_for_file: avoid_classes_with_only_static_members | ||
|
||
/// Config for app. | ||
abstract final class Config { | ||
// --- ENVIRONMENT --- // | ||
|
||
/// Environment flavor. | ||
/// e.g. development, staging, production | ||
static final EnvironmentFlavor environment = EnvironmentFlavor.from( | ||
const String.fromEnvironment('ENVIRONMENT', defaultValue: 'development')); | ||
|
||
// --- API --- // | ||
|
||
/// Base url for api. | ||
/// e.g. https://api.vexus.io | ||
static const String apiBaseUrl = String.fromEnvironment('API_BASE_URL', | ||
defaultValue: 'https://api.domain.tld'); | ||
|
||
/// Timeout in milliseconds for opening url. | ||
/// [Dio] will throw the [DioException] with [DioExceptionType.connectTimeout] type when time out. | ||
/// e.g. 15000 | ||
static const Duration apiConnectTimeout = Duration( | ||
milliseconds: | ||
int.fromEnvironment('API_CONNECT_TIMEOUT', defaultValue: 15000)); | ||
|
||
/// Timeout in milliseconds for receiving data from url. | ||
/// [Dio] will throw the [DioException] with [DioExceptionType.receiveTimeout] type when time out. | ||
/// e.g. 10000 | ||
static const Duration apiReceiveTimeout = Duration( | ||
milliseconds: | ||
int.fromEnvironment('API_RECEIVE_TIMEOUT', defaultValue: 10000)); | ||
|
||
/// Cache lifetime. | ||
/// Refetch data from url when cache is expired. | ||
/// e.g. 1 hour | ||
static const Duration cacheLifetime = Duration(hours: 1); | ||
|
||
// --- DATABASE --- // | ||
|
||
/// Whether to drop database on start. | ||
/// e.g. true | ||
static const bool dropDatabase = | ||
bool.fromEnvironment('DROP_DATABASE', defaultValue: false); | ||
|
||
/// Database file name by default. | ||
/// e.g. sqlite means "sqlite.db" for native platforms and "sqlite" for web platform. | ||
static const String databaseName = | ||
String.fromEnvironment('DATABASE_NAME', defaultValue: 'sqlite'); | ||
|
||
// --- AUTHENTICATION --- // | ||
|
||
/// Minimum length of password. | ||
/// e.g. 8 | ||
static const int passwordMinLength = | ||
int.fromEnvironment('PASSWORD_MIN_LENGTH', defaultValue: 8); | ||
|
||
/// Maximum length of password. | ||
/// e.g. 32 | ||
static const int passwordMaxLength = | ||
int.fromEnvironment('PASSWORD_MAX_LENGTH', defaultValue: 32); | ||
|
||
// --- LAYOUT --- // | ||
|
||
/// Maximum screen layout width for screen with list view. | ||
static const int maxScreenLayoutWidth = | ||
int.fromEnvironment('MAX_LAYOUT_WIDTH', defaultValue: 768); | ||
} | ||
|
||
/// Environment flavor. | ||
/// e.g. development, staging, production | ||
enum EnvironmentFlavor { | ||
/// Development | ||
development('development'), | ||
|
||
/// Staging | ||
staging('staging'), | ||
|
||
/// Production | ||
production('production'); | ||
|
||
/// {@nodoc} | ||
const EnvironmentFlavor(this.value); | ||
|
||
/// {@nodoc} | ||
factory EnvironmentFlavor.from(String? value) => | ||
switch (value?.trim().toLowerCase()) { | ||
'development' || 'debug' || 'develop' || 'dev' => development, | ||
'staging' || 'profile' || 'stage' || 'stg' => staging, | ||
'production' || 'release' || 'prod' || 'prd' => production, | ||
_ => const bool.fromEnvironment('dart.vm.product') | ||
? production | ||
: development, | ||
}; | ||
|
||
/// development, staging, production | ||
final String value; | ||
|
||
/// Whether the environment is development. | ||
bool get isDevelopment => this == development; | ||
|
||
/// Whether the environment is staging. | ||
bool get isStaging => this == staging; | ||
|
||
/// Whether the environment is production. | ||
bool get isProduction => this == production; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import 'dart:async'; | ||
|
||
import 'package:example/src/feature/authentication/model/user.dart'; | ||
import 'package:octopus/octopus.dart'; | ||
|
||
/// A router guard that checks if the user is authenticated. | ||
class AuthenticationGuard extends OctopusGuard { | ||
AuthenticationGuard({ | ||
required FutureOr<User> Function() getUser, | ||
required Set<String> routes, | ||
required OctopusState signinNavigation, | ||
required OctopusState homeNavigation, | ||
super.refresh, | ||
}) : _getUser = getUser, | ||
_routes = routes, | ||
_lastNavigation = homeNavigation, | ||
_signinNavigation = signinNavigation; | ||
|
||
/// Get the current user. | ||
final FutureOr<User> Function() _getUser; | ||
|
||
/// Routes names that stand for the authentication routes. | ||
final Set<String> _routes; | ||
|
||
/// The navigation to use when the user is not authenticated. | ||
final OctopusState _signinNavigation; | ||
|
||
/// The navigation to use when the user is authenticated. | ||
OctopusState _lastNavigation; | ||
|
||
@override | ||
FutureOr<OctopusState?> call( | ||
List<OctopusState> history, | ||
OctopusState state, | ||
) async { | ||
final user = await _getUser(); // Get the current user. | ||
final isAuthNav = | ||
state.children.any((child) => _routes.contains(child.name)); | ||
if (isAuthNav) { | ||
// New state is an authentication navigation. | ||
if (user.isAuthenticated) { | ||
// User authenticated. | ||
// Remove any navigation that is an authentication navigation. | ||
state.removeWhere((child) => _routes.contains(child.name)); | ||
// Restore the last navigation when the user is authenticated | ||
// if the state contains only the authentication routes. | ||
return state.isEmpty ? _lastNavigation : state; | ||
} else { | ||
// User not authenticated. | ||
// Remove any navigation that is not an authentication navigation. | ||
state.removeWhere((child) => !_routes.contains(child.name)); | ||
// Add the signin navigation if the state is empty. | ||
// Or return the state if it contains the signin navigation. | ||
return state.isEmpty ? _signinNavigation : state; | ||
} | ||
} else { | ||
// New state is not an authentication navigation. | ||
if (user.isAuthenticated) { | ||
// User authenticated. | ||
// Save the current navigation as the last navigation. | ||
_lastNavigation = state; | ||
return super.call(history, state); | ||
} else { | ||
// User not authenticated. | ||
// Replace the current navigation with the signin navigation. | ||
return _signinNavigation; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.