Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.3.3] - 25/10/2024

### Added

- Password changes
After logging in, users are prompted to update their password if shouldChangePassword is true.
Users can also change their password if they wish by navigating to the settings screen.

- Dynamic theming.
Album share now uses the system color scheme when available.

### Changed

- Activity and notification sidebars have some transparency removed, this makes content much easier to see.

## [0.3.2] - 21/10/2024

Expand Down Expand Up @@ -61,3 +75,5 @@ Now, when the user attempts to navigate back, the image scale is first reset the

[0.2.1]: https://github.com/ConcenTech/album_share/compare/main...0.2.1
[0.3.0]: https://github.com/ConcenTech/album_share/compare/0.2.1...0.3.0
[0.3.2]: https://github.com/ConcenTech/album_share/compare/0.3.0...0.3.2
[0.3.3]: https://github.com/ConcenTech/album_share/compare/0.3.2...0.3.3
2 changes: 1 addition & 1 deletion lib/core/components/sidebar/activity_sidebar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class ActivitySidebar extends AppSidebar {
return Drawer(
width: AppSidebar.width,
backgroundColor:
Theme.of(context).scaffoldBackgroundColor.withOpacity(0.5),
Theme.of(context).scaffoldBackgroundColor.withOpacity(0.9),
child: SafeArea(
top: false,
child: Consumer(
Expand Down
2 changes: 1 addition & 1 deletion lib/core/components/sidebar/notifications_sidebar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class NotificationSidebar extends AppSidebar {
final provider = ref.watch(ActivityProviders.notifications);
final theme = Theme.of(context);
return Drawer(
backgroundColor: theme.scaffoldBackgroundColor.withOpacity(0.5),
backgroundColor: theme.scaffoldBackgroundColor.withOpacity(0.9),
width: AppSidebar.width,
child: provider.when(
data: (a) {
Expand Down
33 changes: 15 additions & 18 deletions lib/core/theme/app_theme.dart
Original file line number Diff line number Diff line change
@@ -1,41 +1,38 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:system_theme/system_theme.dart';

class AppTheme {
static ThemeData light() {
final base = ThemeData.from(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.purple,
brightness: Brightness.light,
),
);
static Future<void> ensureInitialized() async {
SystemTheme.fallbackColor = Colors.purple;
await SystemTheme.accentColor.load();
}

return base.copyWith(
appBarTheme: base.appBarTheme.copyWith(
systemOverlayStyle: SystemUiOverlayStyle.dark.copyWith(
statusBarColor: Colors.transparent,
),
),
);
static Color systemThemeColour() {
return SystemTheme.accentColor.accent;
}

static ThemeData dark() {
static ThemeData base(Brightness brightness) {
final base = ThemeData.from(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.purple,
brightness: Brightness.dark,
seedColor: systemThemeColour(),
brightness: brightness,
),
);

return base.copyWith(
appBarTheme: base.appBarTheme.copyWith(
systemOverlayStyle: SystemUiOverlayStyle.light.copyWith(
systemOverlayStyle: SystemUiOverlayStyle.dark.copyWith(
statusBarColor: Colors.transparent,
),
),
);
}

static ThemeData light() => base(Brightness.light);

static ThemeData dark() => base(Brightness.dark);

ThemeData themeFromBrightness(Brightness brightness) {
return switch (brightness) {
Brightness.light => light(),
Expand Down
17 changes: 14 additions & 3 deletions lib/core/utils/validators.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,9 @@ class Validators {
[
RequiredValidator(errorText: AppLocale.instance.current.required),
MinLengthValidator(
6,
8,
errorText: AppLocale.instance.current.invalidPasswordLengthError,
),
PatternValidator(r'(?=.*?[#?!@$%^&*-=])',
errorText: AppLocale.instance.current.invalidPasswordError),
],
).call;

Expand All @@ -46,6 +44,19 @@ class Validators {
static final optionalEmail =
EmailValidator(errorText: AppLocale.instance.current.invalidEmailError);

static String? newPassword(String current, String? replacement) {
if (replacement == null) {
return AppLocale.instance.current.required;
}
if (replacement.length < 8) {
return AppLocale.instance.current.invalidPasswordLengthError;
}
if (current == replacement) {
return AppLocale.instance.current.passwordSameAsOldError;
}
return null;
}

static String? passwordMatch(
{required String? value1, required String? value2}) {
if (value1 != value2) {
Expand Down
13 changes: 7 additions & 6 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,24 @@
"@invalidValueError": {
"description": "The user passed an invalid value into an integer text field so it could not be parsed"
},
"invalidPasswordLengthError": "Password must be at least 6 digits",
"invalidPasswordLengthError": "Invalid password",
"@invalidPasswordLengthError": {
"description": "The user entered a password that was less than 6 digits long"
},
"invalidPasswordError": "Passwords must have at least one special character",
"@invalidPasswordError": {
"description": "The user entered a password without any special characters."
"description": "The user entered a password that was less than 8 digits long"
},
"invalidPasswordMatchError": "Passwords must match",
"@invalidPasswordMatchError": {
"description": "The user entered two passwords that were not the same"
},
"passwordSameAsOldError" : "New password cannot be the same as the old password",
"@passwordSameAsOldError": {
"description": "The user tried to use the same password on the change password form"
},
"invalidEmailError": "Please enter a valid email",
"serverUrl": "Server url",
"next": "Next",
"email": "Email",
"password": "Password",
"newPassword": "New password",
"back": "Back",
"login": "login",
"buildingLibrary": "Please wait. Building library",
Expand Down
3 changes: 3 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ import 'core/components/app_window/app_window.dart';
import 'core/main/app_lifecycle_scope.dart';
import 'core/main/locale_scope.dart';
import 'core/main/main_app.dart';
import 'core/theme/app_theme.dart';

void main() {
WidgetsFlutterBinding.ensureInitialized();
AppTheme.ensureInitialized();
VideoPlayer.ensureInitialized();
AppWindow.ensureInitialized();

runApp(
const ProviderScope(
child: LocaleScope(
Expand Down
6 changes: 3 additions & 3 deletions lib/models/user.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,16 @@ class User {
factory User.fromJson(JsonMap json, [String? token]) {
return User(
token: token ?? json['accessToken'],
id: json['userId'],
email: json['userEmail'],
id: json['userId'] ?? json['id'],
email: json['userEmail'] ?? json['email'],
name: json['name'],
shouldChangePassword: json['shouldChangePassword'],
);
}

@override
int get hashCode => id.hashCode;

@override
bool operator ==(Object other) {
return other is User && other.id == id;
Expand Down
26 changes: 23 additions & 3 deletions lib/routes/app_router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const _kLoginRoute = '/login';
const _kPreferencesRoute = 'preferences';
const _kAssetViewerRoute = 'assets';
const _kAlbumRote = 'album';
const _kChangePasswordRoute = 'reset-password';

class AppRouter {
AppRouter(this._auth);
Expand All @@ -26,9 +27,19 @@ class AppRouter {

GlobalKey<NavigatorState> get navigatorKey => _navigatorKey;

Future<String?> _authRedirect(bool authRequired) async {
Future<String?> _authRedirect(
bool authRequired, [
bool isPasswordRoute = false,
]) async {
final authenticated = await _auth.checkAuthStatus();

if (authenticated && !isPasswordRoute) {
final user = await _auth.currentUser();
if (user!.shouldChangePassword) {
return '/$_kChangePasswordRoute';
}
}

if (authenticated && !authRequired) {
return _kLibraryRoute;
}
Expand Down Expand Up @@ -59,6 +70,13 @@ class AppRouter {
builder: (_, __) => const PreferencesScreen(),
);

final _changePasswordRoute = GoRoute(
path: _kChangePasswordRoute,
builder: (_, __) => const AuthScreen(
passwordChange: true,
),
);

late final _albumRoute = GoRoute(
path: _kAlbumRote,
builder: (_, state) => AlbumScreen(album: state.extra as Album),
Expand All @@ -73,6 +91,7 @@ class AppRouter {
_assetViewerRoute,
_preferencesRoute,
_albumRoute,
_changePasswordRoute,
],
),
GoRoute(
Expand All @@ -96,8 +115,7 @@ class AppRouter {

static void toNotificationAssetViewer(AssetViewerScreenState viewerState) =>
to(_kAssetViewerRoute, null, viewerState);
static void toLibrary(BuildContext context) =>
GoRouter.of(context).go(_kLibraryRoute);
static void toLibrary(BuildContext context) => to(_kLibraryRoute, context);
static void toPreferences(BuildContext context) =>
to(_kPreferencesRoute, context);
static void toLogin(BuildContext context) => to(_kLoginRoute, context);
Expand All @@ -108,4 +126,6 @@ class AppRouter {
AssetViewerScreenState viewerState,
) =>
to(_kAssetViewerRoute, context, viewerState);
static void toChangePassword(BuildContext context) =>
to(_kChangePasswordRoute, context);
}
61 changes: 45 additions & 16 deletions lib/screens/auth/auth_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@ import 'package:flutter/material.dart';
import '../../core/components/logo_widget.dart';
import '../../core/components/scaffold/app_scaffold.dart';
import '../../routes/app_router.dart';
import 'change_password_widget.dart';
import 'endpoint_widget.dart';
import 'login_widget.dart';

class AuthScreen extends StatelessWidget {
const AuthScreen({super.key});
const AuthScreen({
this.passwordChange = false,
super.key,
});

static const id = 'auth_screen';

final bool passwordChange;

@override
Widget build(BuildContext context) {
return AppScaffold(
Expand All @@ -19,16 +25,16 @@ class AuthScreen extends StatelessWidget {
body: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 300),
child: const Card(
child: Card(
child: SingleChildScrollView(
padding: EdgeInsets.all(8.0),
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
LogoImageText(),
SizedBox(height: 10),
AuthScreenContent(),
const LogoImageText(),
const SizedBox(height: 10),
AuthScreenContent(passwordChange: passwordChange),
],
),
),
Expand All @@ -40,14 +46,28 @@ class AuthScreen extends StatelessWidget {
}

class AuthScreenContent extends StatefulWidget {
const AuthScreenContent({super.key});
const AuthScreenContent({
required this.passwordChange,
super.key,
});

final bool passwordChange;

@override
State<AuthScreenContent> createState() => _AuthScreenContentState();
}

class _AuthScreenContentState extends State<AuthScreenContent> {
_State _state = _State.endpoint;
late _State _state =
widget.passwordChange ? _State.changePassword : _State.endpoint;

void _updateState(_State newState) {
if (mounted) {
setState(() {
_state = newState;
});
}
}

@override
Widget build(BuildContext context) {
Expand All @@ -56,19 +76,27 @@ class _AuthScreenContentState extends State<AuthScreenContent> {
child: switch (_state) {
_State.endpoint => EndpointWidget(
onEndpointSaved: (isOAuth) {
setState(() {
_state = isOAuth ? _State.oauth : _State.login;
});
_updateState(_state = isOAuth ? _State.oauth : _State.login);
},
),
_State.changePassword => ChangePasswordWidget(
onLogout: () {
_updateState(_State.endpoint);
},
onComplete: () {
AppRouter.toLibrary(context);
},
),
_State _ => LoginWidget(
onBack: () {
setState(() {
_state = _State.endpoint;
});
_updateState(_State.endpoint);
},
onLoginComplete: () {
AppRouter.toLibrary(context);
onLoginComplete: (user) {
if (user.shouldChangePassword) {
_updateState(_State.changePassword);
} else {
AppRouter.toLibrary(context);
}
},
),
},
Expand All @@ -80,4 +108,5 @@ enum _State {
endpoint,
login,
oauth,
changePassword,
}
Loading
Loading