chore: Halkyon Todo's scaffold joins the repository in apps/todo

Its server, client and app as `serverpod create` made them, with the
switch to server-side sessions that was waiting uncommitted, as files:
its one commit holds the scaffold alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-19 20:08:27 +02:00
commit 314a901fa1
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
5 changed files with 16151 additions and 0 deletions

View file

@ -0,0 +1,32 @@
import 'dart:async';
import 'package:halkyon_todo_client/halkyon_todo_client.dart';
import 'package:serverpod_auth_idp_flutter/serverpod_auth_idp_flutter.dart';
import 'package:serverpod_flutter/serverpod_flutter.dart';
// When you are running the app on a physical device, you need to set the
// server URL to the IP address of your computer. You can find the IP
// address by running `ipconfig` on Windows or `ifconfig` on Mac/Linux.
//
// You can set the variable when running or building your app like this:
// E.g. `flutter run --dart-define=SERVER_URL=https://api.example.com/`.
//
// Otherwise, the server URL is fetched from the assets/config.json file or
// defaults to http://$localhost:8080/ if not found.
final serverUrl = getServerUrl();
/// Sets up a global client object that can be used to talk to the server from
/// anywhere in our app. The client is generated from your server code
/// and is set up to connect to a Serverpod running on a local server on
/// the default port. You will need to modify this to connect to staging or
/// production servers.
/// In a larger app, you may want to use the dependency injection of your choice
/// instead of using a global client object. This is just a simple example.
late final Client client;
Future<void> initializeClient() async {
client = Client(await serverUrl)
..connectivityMonitor = FlutterConnectivityMonitor()
..authSessionManager = FlutterAuthSessionManager();
unawaited(client.auth.initialize());
}

View file

@ -0,0 +1,15 @@
// This file is never meant to be used on real apps, just for development.
// ignore: depend_on_referenced_packages
import 'package:flutter_driver/driver_extension.dart';
import 'main.dart' as app;
void main() {
enableFlutterDriverExtension(
// Text entry emulation is disabled by default to allow manual usage of the
// app, but will be required for agents to type through the app. Set this
// parameter to true to switch from manual to automated usage.
enableTextEntryEmulation: false,
);
app.main();
}

View file

@ -0,0 +1,61 @@
import 'package:flutter/material.dart';
import 'client.dart';
import 'screens/greetings_screen.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await initializeClient();
runApp(const MyApp());
}
/// Builds a theme for the given [brightness].
ThemeData _buildTheme(Brightness brightness) {
return ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: brightness,
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Serverpod Demo',
theme: _buildTheme(Brightness.light),
darkTheme: _buildTheme(Brightness.dark),
themeMode: ThemeMode.system,
home: const MyHomePage(title: 'Serverpod Example'),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(title)),
body: const GreetingsScreen(),
// To test authentication in this example app, uncomment the line below
// and comment out the line above. This wraps the GreetingsScreen with a
// SignInScreen, which automatically shows a sign-in UI when the user is
// not authenticated and displays the GreetingsScreen once they sign in.
//
// body: SignInScreen(
// child: GreetingsScreen(
// onSignOut: () async {
// await client.auth.signOutDevice();
// },
// ),
// ),
);
}
}

View file

@ -0,0 +1,122 @@
import 'package:flutter/material.dart';
import '../client.dart';
class GreetingsScreen extends StatefulWidget {
final Future<void> Function()? onSignOut;
const GreetingsScreen({super.key, this.onSignOut});
@override
State<GreetingsScreen> createState() => _GreetingsScreenState();
}
class _GreetingsScreenState extends State<GreetingsScreen> {
/// Holds the last result or null if no result exists yet.
String? _resultMessage;
/// Holds the last error message that we've received from the server or null
/// if no error exists yet.
String? _errorMessage;
final _textEditingController = TextEditingController();
/// Calls the `hello` method of the `greeting` endpoint. Will set either the
/// `_resultMessage` or `_errorMessage` field, depending on if the call
/// is successful.
void _callHello() async {
try {
final result = await client.greeting.hello(_textEditingController.text);
setState(() {
_errorMessage = null;
_resultMessage = result.message;
});
} catch (e) {
setState(() {
_errorMessage = '$e';
});
}
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
if (widget.onSignOut != null) ...[
const Text('You are connected'),
ElevatedButton(
onPressed: widget.onSignOut,
child: const Text('Sign out'),
),
],
const SizedBox(height: 32),
TextField(
controller: _textEditingController,
onSubmitted: (_) => _callHello(),
decoration: InputDecoration(
hintText: 'Enter your name',
border: const OutlineInputBorder(),
suffixIcon: IconButton(
onPressed: _callHello,
icon: const Icon(Icons.send),
),
),
),
const SizedBox(height: 16),
ResultDisplay(
resultMessage: _resultMessage,
errorMessage: _errorMessage,
),
],
),
);
}
}
/// ResultDisplays shows the result of the call. Either the returned result
/// from the `example.greeting` endpoint method or an error message.
class ResultDisplay extends StatelessWidget {
final String? resultMessage;
final String? errorMessage;
const ResultDisplay({super.key, this.resultMessage, this.errorMessage});
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme;
String text;
Color backgroundColor;
Color foregroundColor;
if (errorMessage != null) {
backgroundColor = colors.errorContainer;
foregroundColor = colors.onErrorContainer;
text = errorMessage!;
} else if (resultMessage != null) {
backgroundColor = colors.primaryContainer;
foregroundColor = colors.onPrimaryContainer;
text = resultMessage!;
} else {
backgroundColor = colors.surfaceContainerHighest;
foregroundColor = colors.onSurfaceVariant;
text = 'No server response yet.';
}
return Container(
constraints: const BoxConstraints(minHeight: 60),
width: double.infinity,
padding: const EdgeInsets.all(16),
// Reuses the text field's border so the shape and width always match.
decoration: ShapeDecoration(
color: backgroundColor,
shape: OutlineInputBorder(
borderSide: BorderSide(color: foregroundColor),
),
),
child: Center(
child: Text(text, style: TextStyle(color: foregroundColor)),
),
);
}
}

View file

@ -0,0 +1,78 @@
import 'package:flutter/material.dart';
import 'package:serverpod_auth_idp_flutter/serverpod_auth_idp_flutter.dart';
import '../client.dart';
class SignInScreen extends StatefulWidget {
final Widget child;
const SignInScreen({super.key, required this.child});
@override
State<SignInScreen> createState() => _SignInScreenState();
}
class _SignInScreenState extends State<SignInScreen> {
bool _isSignedIn = false;
@override
void initState() {
super.initState();
client.auth.authInfoListenable.addListener(_updateSignedInState);
_isSignedIn = client.auth.isAuthenticated;
}
@override
void dispose() {
client.auth.authInfoListenable.removeListener(_updateSignedInState);
super.dispose();
}
void _updateSignedInState() {
setState(() {
_isSignedIn = client.auth.isAuthenticated;
});
}
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme;
return _isSignedIn
? widget.child
: Center(
child: SignInWidget(
client: client,
onAuthenticated: () {
context.showSnackBar(
message: 'User authenticated.',
backgroundColor: colors.primaryContainer,
foregroundColor: colors.onPrimaryContainer,
);
},
onError: (error) {
context.showSnackBar(
message: 'Authentication failed: $error',
backgroundColor: colors.errorContainer,
foregroundColor: colors.onErrorContainer,
);
},
),
);
}
}
extension on BuildContext {
void showSnackBar({
required String message,
required Color backgroundColor,
required Color foregroundColor,
}) {
ScaffoldMessenger.of(this).showSnackBar(
SnackBar(
content: Text(message, style: TextStyle(color: foregroundColor)),
backgroundColor: backgroundColor,
duration: const Duration(seconds: 5),
),
);
}
}