feat: Implement new sign in flow

This commit is contained in:
Christian Kußowski 2026-02-01 17:57:03 +01:00
commit 4d7f0295ca
No known key found for this signature in database
GPG key ID: E067ECD60F1A0652
19 changed files with 944 additions and 559 deletions

View file

@ -12,83 +12,92 @@ import 'package:fluffychat/utils/platform_infos.dart';
class LoginScaffold extends StatelessWidget {
final Widget body;
final AppBar? appBar;
final bool enforceMobileMode;
final Widget? bottomNavigationBar;
const LoginScaffold({
super.key,
required this.body,
this.appBar,
this.enforceMobileMode = false,
this.bottomNavigationBar,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final isMobileMode =
enforceMobileMode || !FluffyThemes.isColumnMode(context);
if (isMobileMode) {
return Scaffold(
key: const Key('LoginScaffold'),
appBar: appBar,
body: SafeArea(child: body),
);
}
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
theme.colorScheme.surfaceContainerLow,
theme.colorScheme.surfaceContainer,
theme.colorScheme.surfaceContainerHighest,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Stack(
children: [
if (!MediaQuery.of(context).disableAnimations)
ParticleNetwork(
particleColor: theme.colorScheme.primary,
lineColor: theme.colorScheme.secondary,
return LayoutBuilder(
builder: (context, constraints) {
final isMobileMode = !FluffyThemes.isColumnModeByWidth(
constraints.maxWidth,
);
if (isMobileMode) {
return Scaffold(
key: const Key('LoginScaffold'),
appBar: appBar,
body: SafeArea(child: body),
bottomNavigationBar: bottomNavigationBar,
);
}
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
theme.colorScheme.surfaceContainerLow,
theme.colorScheme.surfaceContainer,
theme.colorScheme.surfaceContainerHighest,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
Column(
),
child: Stack(
children: [
const SizedBox(height: 16),
Expanded(
child: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Material(
borderRadius: BorderRadius.circular(
AppConfig.borderRadius,
),
clipBehavior: Clip.hardEdge,
elevation: theme.appBarTheme.scrolledUnderElevation ?? 4,
shadowColor: theme.appBarTheme.shadowColor,
child: ConstrainedBox(
constraints: isMobileMode
? const BoxConstraints()
: const BoxConstraints(
maxWidth: 480,
maxHeight: 640,
),
child: Scaffold(
key: const Key('LoginScaffold'),
appBar: appBar,
body: SafeArea(child: body),
if (!MediaQuery.of(context).disableAnimations)
ParticleNetwork(
maxSpeed: 0.25,
particleColor: theme.colorScheme.primary,
lineColor: theme.colorScheme.secondary,
),
Column(
children: [
const SizedBox(height: 16),
Expanded(
child: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Material(
borderRadius: BorderRadius.circular(
AppConfig.borderRadius,
),
clipBehavior: Clip.hardEdge,
elevation:
theme.appBarTheme.scrolledUnderElevation ?? 4,
shadowColor: theme.appBarTheme.shadowColor,
child: ConstrainedBox(
constraints: isMobileMode
? const BoxConstraints()
: const BoxConstraints(
maxWidth: 480,
maxHeight: 640,
),
child: Scaffold(
key: const Key('LoginScaffold'),
appBar: appBar,
body: SafeArea(child: body),
bottomNavigationBar: bottomNavigationBar,
),
),
),
),
),
),
),
const _PrivacyButtons(mainAxisAlignment: .center),
],
),
const _PrivacyButtons(mainAxisAlignment: .center),
],
),
],
),
);
},
);
}
}

View file

@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
class ViewModelBuilder<T extends ValueNotifier> extends StatefulWidget {
final T Function() create;
final Widget Function(BuildContext context, T viewModel, Widget? child)
builder;
final Widget? child;
const ViewModelBuilder({
super.key,
required this.create,
required this.builder,
this.child,
});
@override
State<ViewModelBuilder<T>> createState() => _ViewModelBuilderState<T>();
}
class _ViewModelBuilderState<T extends ValueNotifier>
extends State<ViewModelBuilder<T>> {
late final T _viewModel;
@override
void initState() {
_viewModel = widget.create();
super.initState();
}
@override
void dispose() {
_viewModel.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: _viewModel,
builder: (context, value, child) =>
widget.builder.call(context, _viewModel, child),
);
}
}