initial commit
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../core/api_client.dart';
|
||||
|
||||
final apiClientProvider = Provider<ApiClient>((ref) => ApiClient());
|
||||
|
||||
class AuthState {
|
||||
final bool isAuthenticated;
|
||||
final String? email;
|
||||
const AuthState({required this.isAuthenticated, this.email});
|
||||
}
|
||||
|
||||
final authProvider =
|
||||
NotifierProvider<AuthController, AuthState>(() => AuthController());
|
||||
|
||||
class AuthController extends Notifier<AuthState> {
|
||||
late final ApiClient _api = ref.read(apiClientProvider);
|
||||
|
||||
@override
|
||||
AuthState build() => const AuthState(isAuthenticated: false);
|
||||
|
||||
Future<void> login(String email, String password) async {
|
||||
await _api.call('login', {'email': email, 'password': password});
|
||||
state = AuthState(isAuthenticated: true, email: email);
|
||||
}
|
||||
|
||||
Future<void> logout() async {
|
||||
await _api.call('logout', {});
|
||||
state = const AuthState(isAuthenticated: false);
|
||||
}
|
||||
|
||||
Future<void> register(String email, String password) async {
|
||||
await _api.call('register', {'email': email, 'password': password});
|
||||
}
|
||||
|
||||
Future<void> checkSession() async {
|
||||
try {
|
||||
final data = await _api.call('session', {});
|
||||
if (data['loggedIn'] == true) {
|
||||
state = AuthState(isAuthenticated: true, email: data['email']);
|
||||
}
|
||||
} catch (_) {
|
||||
state = const AuthState(isAuthenticated: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:dio/dio.dart'; // <— wichtig für DioException
|
||||
import '../auth_controller.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class LoginScreen extends ConsumerStatefulWidget {
|
||||
const LoginScreen({super.key});
|
||||
@override
|
||||
ConsumerState<LoginScreen> createState() => _LoginScreenState();
|
||||
}
|
||||
|
||||
class _LoginScreenState extends ConsumerState<LoginScreen> {
|
||||
final emailCtrl = TextEditingController();
|
||||
final passCtrl = TextEditingController();
|
||||
bool loading = false;
|
||||
String? error;
|
||||
|
||||
Future<void> _submit() async {
|
||||
setState(() => loading = true);
|
||||
try {
|
||||
await ref
|
||||
.read(authProvider.notifier)
|
||||
.login(emailCtrl.text, passCtrl.text);
|
||||
if (mounted) context.go('/');
|
||||
} catch (e) {
|
||||
setState(() => error = e.toString());
|
||||
} finally {
|
||||
setState(() => loading = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 400),
|
||||
child: Card(
|
||||
margin: const EdgeInsets.all(16),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text('Igel Login',
|
||||
style:
|
||||
TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: emailCtrl,
|
||||
decoration: const InputDecoration(labelText: 'E-Mail')),
|
||||
TextField(
|
||||
controller: passCtrl,
|
||||
decoration: const InputDecoration(labelText: 'Passwort'),
|
||||
obscureText: true),
|
||||
const SizedBox(height: 16),
|
||||
if (error != null)
|
||||
Text(error!, style: const TextStyle(color: Colors.red)),
|
||||
const SizedBox(height: 8),
|
||||
ElevatedButton(
|
||||
onPressed: loading ? null : _submit,
|
||||
child: loading
|
||||
? const CircularProgressIndicator()
|
||||
: const Text('Login'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
final email = emailCtrl.text.trim();
|
||||
final pw = passCtrl.text;
|
||||
if (email.isEmpty ||
|
||||
!email.contains('@') ||
|
||||
pw.length < 6) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text(
|
||||
'Bitte gültige E-Mail und Passwort (min. 6 Zeichen) eingeben.')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await ref
|
||||
.read(authProvider.notifier)
|
||||
.register(email, pw);
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text(
|
||||
'Registrierung erfolgreich – bitte einloggen.')),
|
||||
);
|
||||
} on DioException catch (e) {
|
||||
final msg = e.response?.data is Map &&
|
||||
(e.response!.data['error'] != null)
|
||||
? e.response!.data['error'].toString()
|
||||
: e.message ?? 'Unbekannter Fehler';
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Fehler: $msg')),
|
||||
);
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Fehler: $e')),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: const Text('Neu registrieren'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../../core/api_client.dart';
|
||||
import '../models/hedgehog.dart';
|
||||
|
||||
class HedgehogService {
|
||||
final Ref ref;
|
||||
HedgehogService(this.ref);
|
||||
|
||||
ApiClient get _api => ref.read(apiClientProvider);
|
||||
|
||||
Future<List<Hedgehog>> list() async {
|
||||
final data = await _api.call('hedgehog.list', {});
|
||||
return (data as List)
|
||||
.map((e) => Hedgehog.fromJson(Map<String, dynamic>.from(e)))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<Hedgehog> create(String name) async {
|
||||
final data = await _api.call('hedgehog.create', {'name': name});
|
||||
return Hedgehog.fromJson(Map<String, dynamic>.from(data));
|
||||
}
|
||||
}
|
||||
|
||||
final hedgehogServiceProvider = Provider((ref) => HedgehogService(ref));
|
||||
@@ -0,0 +1,16 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
part 'hedgehog.freezed.dart';
|
||||
part 'hedgehog.g.dart';
|
||||
|
||||
@freezed
|
||||
class Hedgehog with _$Hedgehog {
|
||||
const factory Hedgehog({
|
||||
required int id,
|
||||
required String name,
|
||||
String? species,
|
||||
String? notes,
|
||||
}) = _Hedgehog;
|
||||
|
||||
factory Hedgehog.fromJson(Map<String, dynamic> json) =>
|
||||
_$HedgehogFromJson(json);
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'hedgehog.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
Hedgehog _$HedgehogFromJson(Map<String, dynamic> json) {
|
||||
return _Hedgehog.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$Hedgehog {
|
||||
int get id => throw _privateConstructorUsedError;
|
||||
String get name => throw _privateConstructorUsedError;
|
||||
String? get species => throw _privateConstructorUsedError;
|
||||
String? get notes => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this Hedgehog to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of Hedgehog
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$HedgehogCopyWith<Hedgehog> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $HedgehogCopyWith<$Res> {
|
||||
factory $HedgehogCopyWith(Hedgehog value, $Res Function(Hedgehog) then) =
|
||||
_$HedgehogCopyWithImpl<$Res, Hedgehog>;
|
||||
@useResult
|
||||
$Res call({int id, String name, String? species, String? notes});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$HedgehogCopyWithImpl<$Res, $Val extends Hedgehog>
|
||||
implements $HedgehogCopyWith<$Res> {
|
||||
_$HedgehogCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of Hedgehog
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? id = null,
|
||||
Object? name = null,
|
||||
Object? species = freezed,
|
||||
Object? notes = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
id: null == id
|
||||
? _value.id
|
||||
: id // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
species: freezed == species
|
||||
? _value.species
|
||||
: species // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
notes: freezed == notes
|
||||
? _value.notes
|
||||
: notes // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$HedgehogImplCopyWith<$Res>
|
||||
implements $HedgehogCopyWith<$Res> {
|
||||
factory _$$HedgehogImplCopyWith(
|
||||
_$HedgehogImpl value, $Res Function(_$HedgehogImpl) then) =
|
||||
__$$HedgehogImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({int id, String name, String? species, String? notes});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$HedgehogImplCopyWithImpl<$Res>
|
||||
extends _$HedgehogCopyWithImpl<$Res, _$HedgehogImpl>
|
||||
implements _$$HedgehogImplCopyWith<$Res> {
|
||||
__$$HedgehogImplCopyWithImpl(
|
||||
_$HedgehogImpl _value, $Res Function(_$HedgehogImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of Hedgehog
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? id = null,
|
||||
Object? name = null,
|
||||
Object? species = freezed,
|
||||
Object? notes = freezed,
|
||||
}) {
|
||||
return _then(_$HedgehogImpl(
|
||||
id: null == id
|
||||
? _value.id
|
||||
: id // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
species: freezed == species
|
||||
? _value.species
|
||||
: species // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
notes: freezed == notes
|
||||
? _value.notes
|
||||
: notes // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$HedgehogImpl implements _Hedgehog {
|
||||
const _$HedgehogImpl(
|
||||
{required this.id, required this.name, this.species, this.notes});
|
||||
|
||||
factory _$HedgehogImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$HedgehogImplFromJson(json);
|
||||
|
||||
@override
|
||||
final int id;
|
||||
@override
|
||||
final String name;
|
||||
@override
|
||||
final String? species;
|
||||
@override
|
||||
final String? notes;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Hedgehog(id: $id, name: $name, species: $species, notes: $notes)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$HedgehogImpl &&
|
||||
(identical(other.id, id) || other.id == id) &&
|
||||
(identical(other.name, name) || other.name == name) &&
|
||||
(identical(other.species, species) || other.species == species) &&
|
||||
(identical(other.notes, notes) || other.notes == notes));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, id, name, species, notes);
|
||||
|
||||
/// Create a copy of Hedgehog
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$HedgehogImplCopyWith<_$HedgehogImpl> get copyWith =>
|
||||
__$$HedgehogImplCopyWithImpl<_$HedgehogImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$HedgehogImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _Hedgehog implements Hedgehog {
|
||||
const factory _Hedgehog(
|
||||
{required final int id,
|
||||
required final String name,
|
||||
final String? species,
|
||||
final String? notes}) = _$HedgehogImpl;
|
||||
|
||||
factory _Hedgehog.fromJson(Map<String, dynamic> json) =
|
||||
_$HedgehogImpl.fromJson;
|
||||
|
||||
@override
|
||||
int get id;
|
||||
@override
|
||||
String get name;
|
||||
@override
|
||||
String? get species;
|
||||
@override
|
||||
String? get notes;
|
||||
|
||||
/// Create a copy of Hedgehog
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$HedgehogImplCopyWith<_$HedgehogImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'hedgehog.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$HedgehogImpl _$$HedgehogImplFromJson(Map<String, dynamic> json) =>
|
||||
_$HedgehogImpl(
|
||||
id: (json['id'] as num).toInt(),
|
||||
name: json['name'] as String,
|
||||
species: json['species'] as String?,
|
||||
notes: json['notes'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$HedgehogImplToJson(_$HedgehogImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'name': instance.name,
|
||||
'species': instance.species,
|
||||
'notes': instance.notes,
|
||||
};
|
||||
@@ -0,0 +1,181 @@
|
||||
// lib/features/hedgehogs/ui/dashboard_screen.dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../auth/auth_controller.dart';
|
||||
import '../data/hedgehog_service.dart';
|
||||
import '../models/hedgehog.dart';
|
||||
|
||||
class DashboardScreen extends ConsumerStatefulWidget {
|
||||
const DashboardScreen({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<DashboardScreen> createState() => _DashboardScreenState();
|
||||
}
|
||||
|
||||
class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
late Future<List<Hedgehog>> _future;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_future = ref.read(hedgehogServiceProvider).list();
|
||||
}
|
||||
|
||||
Future<void> _reload() async {
|
||||
setState(() {
|
||||
_future = ref.read(hedgehogServiceProvider).list();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _createHedgehogDialog() async {
|
||||
final service = ref.read(hedgehogServiceProvider);
|
||||
final nameCtrl = TextEditingController();
|
||||
|
||||
final created = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: const Text('Neuer Igel'),
|
||||
content: TextField(
|
||||
controller: nameCtrl,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Name',
|
||||
hintText: 'z. B. Frida',
|
||||
),
|
||||
textInputAction: TextInputAction.done,
|
||||
onSubmitted: (_) => Navigator.of(ctx).pop(true),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(ctx).pop(false),
|
||||
child: const Text('Abbrechen'),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.of(ctx).pop(true),
|
||||
child: const Text('Speichern'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (created == true && nameCtrl.text.trim().isNotEmpty) {
|
||||
try {
|
||||
await service.create(nameCtrl.text.trim());
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Igel angelegt.')),
|
||||
);
|
||||
await _reload();
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Fehler beim Anlegen: $e')),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final auth = ref.watch(authProvider);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Meine Igel${auth.email != null ? " (${auth.email})" : ""}'),
|
||||
actions: [
|
||||
IconButton(
|
||||
tooltip: 'Abmelden',
|
||||
onPressed: () async {
|
||||
await ref.read(authProvider.notifier).logout();
|
||||
if (!mounted) return;
|
||||
context.go('/login');
|
||||
},
|
||||
icon: const Icon(Icons.logout),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: _reload,
|
||||
child: FutureBuilder<List<Hedgehog>>(
|
||||
future: _future,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState != ConnectionState.done) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
if (snapshot.hasError) {
|
||||
return ListView(
|
||||
children: [
|
||||
const SizedBox(height: 48),
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Text(
|
||||
'Fehler beim Laden:\n${snapshot.error}',
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Center(
|
||||
child: FilledButton.icon(
|
||||
onPressed: _reload,
|
||||
icon: const Icon(Icons.refresh),
|
||||
label: const Text('Erneut versuchen'),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
final list = snapshot.data ?? const <Hedgehog>[];
|
||||
if (list.isEmpty) {
|
||||
return ListView(
|
||||
children: [
|
||||
const SizedBox(height: 80),
|
||||
const Icon(Icons.pets, size: 72),
|
||||
const SizedBox(height: 12),
|
||||
const Center(
|
||||
child: Text(
|
||||
'Noch keine Igel angelegt.',
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Center(
|
||||
child: TextButton.icon(
|
||||
onPressed: _createHedgehogDialog,
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('Jetzt ersten Igel anlegen'),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return ListView.separated(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
itemCount: list.length,
|
||||
separatorBuilder: (_, __) => const Divider(height: 1),
|
||||
itemBuilder: (context, i) {
|
||||
final h = list[i];
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.pets),
|
||||
title: Text(h.name),
|
||||
subtitle: Text(h.species ?? '—'),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () => context.push('/hedgehogs/${h.id}'),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _createHedgehogDialog,
|
||||
tooltip: 'Neuer Igel',
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class HedgehogDetailScreen extends StatelessWidget {
|
||||
final int id;
|
||||
const HedgehogDetailScreen({super.key, required this.id});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('Igel #$id')),
|
||||
body: const Center(child: Text('Detailansicht (Chart, Messungen etc.) kommt später')),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// lib/features/measurements/models/measurement.dart
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
part 'measurement.freezed.dart';
|
||||
part 'measurement.g.dart';
|
||||
|
||||
@freezed
|
||||
class Measurement with _$Measurement {
|
||||
const factory Measurement({
|
||||
required int id,
|
||||
required int hedgehogId,
|
||||
required DateTime measuredAt,
|
||||
int? weightGrams,
|
||||
int? lengthMm,
|
||||
double? temperatureC,
|
||||
String? note,
|
||||
}) = _Measurement;
|
||||
|
||||
factory Measurement.fromJson(Map<String, dynamic> json) => _$MeasurementFromJson(json);
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'measurement.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
Measurement _$MeasurementFromJson(Map<String, dynamic> json) {
|
||||
return _Measurement.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$Measurement {
|
||||
int get id => throw _privateConstructorUsedError;
|
||||
int get hedgehogId => throw _privateConstructorUsedError;
|
||||
DateTime get measuredAt => throw _privateConstructorUsedError;
|
||||
int? get weightGrams => throw _privateConstructorUsedError;
|
||||
int? get lengthMm => throw _privateConstructorUsedError;
|
||||
double? get temperatureC => throw _privateConstructorUsedError;
|
||||
String? get note => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this Measurement to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of Measurement
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$MeasurementCopyWith<Measurement> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $MeasurementCopyWith<$Res> {
|
||||
factory $MeasurementCopyWith(
|
||||
Measurement value, $Res Function(Measurement) then) =
|
||||
_$MeasurementCopyWithImpl<$Res, Measurement>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{int id,
|
||||
int hedgehogId,
|
||||
DateTime measuredAt,
|
||||
int? weightGrams,
|
||||
int? lengthMm,
|
||||
double? temperatureC,
|
||||
String? note});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$MeasurementCopyWithImpl<$Res, $Val extends Measurement>
|
||||
implements $MeasurementCopyWith<$Res> {
|
||||
_$MeasurementCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of Measurement
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? id = null,
|
||||
Object? hedgehogId = null,
|
||||
Object? measuredAt = null,
|
||||
Object? weightGrams = freezed,
|
||||
Object? lengthMm = freezed,
|
||||
Object? temperatureC = freezed,
|
||||
Object? note = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
id: null == id
|
||||
? _value.id
|
||||
: id // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
hedgehogId: null == hedgehogId
|
||||
? _value.hedgehogId
|
||||
: hedgehogId // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
measuredAt: null == measuredAt
|
||||
? _value.measuredAt
|
||||
: measuredAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
weightGrams: freezed == weightGrams
|
||||
? _value.weightGrams
|
||||
: weightGrams // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
lengthMm: freezed == lengthMm
|
||||
? _value.lengthMm
|
||||
: lengthMm // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
temperatureC: freezed == temperatureC
|
||||
? _value.temperatureC
|
||||
: temperatureC // ignore: cast_nullable_to_non_nullable
|
||||
as double?,
|
||||
note: freezed == note
|
||||
? _value.note
|
||||
: note // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$MeasurementImplCopyWith<$Res>
|
||||
implements $MeasurementCopyWith<$Res> {
|
||||
factory _$$MeasurementImplCopyWith(
|
||||
_$MeasurementImpl value, $Res Function(_$MeasurementImpl) then) =
|
||||
__$$MeasurementImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{int id,
|
||||
int hedgehogId,
|
||||
DateTime measuredAt,
|
||||
int? weightGrams,
|
||||
int? lengthMm,
|
||||
double? temperatureC,
|
||||
String? note});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$MeasurementImplCopyWithImpl<$Res>
|
||||
extends _$MeasurementCopyWithImpl<$Res, _$MeasurementImpl>
|
||||
implements _$$MeasurementImplCopyWith<$Res> {
|
||||
__$$MeasurementImplCopyWithImpl(
|
||||
_$MeasurementImpl _value, $Res Function(_$MeasurementImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of Measurement
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? id = null,
|
||||
Object? hedgehogId = null,
|
||||
Object? measuredAt = null,
|
||||
Object? weightGrams = freezed,
|
||||
Object? lengthMm = freezed,
|
||||
Object? temperatureC = freezed,
|
||||
Object? note = freezed,
|
||||
}) {
|
||||
return _then(_$MeasurementImpl(
|
||||
id: null == id
|
||||
? _value.id
|
||||
: id // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
hedgehogId: null == hedgehogId
|
||||
? _value.hedgehogId
|
||||
: hedgehogId // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
measuredAt: null == measuredAt
|
||||
? _value.measuredAt
|
||||
: measuredAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
weightGrams: freezed == weightGrams
|
||||
? _value.weightGrams
|
||||
: weightGrams // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
lengthMm: freezed == lengthMm
|
||||
? _value.lengthMm
|
||||
: lengthMm // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
temperatureC: freezed == temperatureC
|
||||
? _value.temperatureC
|
||||
: temperatureC // ignore: cast_nullable_to_non_nullable
|
||||
as double?,
|
||||
note: freezed == note
|
||||
? _value.note
|
||||
: note // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$MeasurementImpl implements _Measurement {
|
||||
const _$MeasurementImpl(
|
||||
{required this.id,
|
||||
required this.hedgehogId,
|
||||
required this.measuredAt,
|
||||
this.weightGrams,
|
||||
this.lengthMm,
|
||||
this.temperatureC,
|
||||
this.note});
|
||||
|
||||
factory _$MeasurementImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$MeasurementImplFromJson(json);
|
||||
|
||||
@override
|
||||
final int id;
|
||||
@override
|
||||
final int hedgehogId;
|
||||
@override
|
||||
final DateTime measuredAt;
|
||||
@override
|
||||
final int? weightGrams;
|
||||
@override
|
||||
final int? lengthMm;
|
||||
@override
|
||||
final double? temperatureC;
|
||||
@override
|
||||
final String? note;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Measurement(id: $id, hedgehogId: $hedgehogId, measuredAt: $measuredAt, weightGrams: $weightGrams, lengthMm: $lengthMm, temperatureC: $temperatureC, note: $note)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$MeasurementImpl &&
|
||||
(identical(other.id, id) || other.id == id) &&
|
||||
(identical(other.hedgehogId, hedgehogId) ||
|
||||
other.hedgehogId == hedgehogId) &&
|
||||
(identical(other.measuredAt, measuredAt) ||
|
||||
other.measuredAt == measuredAt) &&
|
||||
(identical(other.weightGrams, weightGrams) ||
|
||||
other.weightGrams == weightGrams) &&
|
||||
(identical(other.lengthMm, lengthMm) ||
|
||||
other.lengthMm == lengthMm) &&
|
||||
(identical(other.temperatureC, temperatureC) ||
|
||||
other.temperatureC == temperatureC) &&
|
||||
(identical(other.note, note) || other.note == note));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, id, hedgehogId, measuredAt,
|
||||
weightGrams, lengthMm, temperatureC, note);
|
||||
|
||||
/// Create a copy of Measurement
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$MeasurementImplCopyWith<_$MeasurementImpl> get copyWith =>
|
||||
__$$MeasurementImplCopyWithImpl<_$MeasurementImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$MeasurementImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _Measurement implements Measurement {
|
||||
const factory _Measurement(
|
||||
{required final int id,
|
||||
required final int hedgehogId,
|
||||
required final DateTime measuredAt,
|
||||
final int? weightGrams,
|
||||
final int? lengthMm,
|
||||
final double? temperatureC,
|
||||
final String? note}) = _$MeasurementImpl;
|
||||
|
||||
factory _Measurement.fromJson(Map<String, dynamic> json) =
|
||||
_$MeasurementImpl.fromJson;
|
||||
|
||||
@override
|
||||
int get id;
|
||||
@override
|
||||
int get hedgehogId;
|
||||
@override
|
||||
DateTime get measuredAt;
|
||||
@override
|
||||
int? get weightGrams;
|
||||
@override
|
||||
int? get lengthMm;
|
||||
@override
|
||||
double? get temperatureC;
|
||||
@override
|
||||
String? get note;
|
||||
|
||||
/// Create a copy of Measurement
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$MeasurementImplCopyWith<_$MeasurementImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'measurement.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$MeasurementImpl _$$MeasurementImplFromJson(Map<String, dynamic> json) =>
|
||||
_$MeasurementImpl(
|
||||
id: (json['id'] as num).toInt(),
|
||||
hedgehogId: (json['hedgehogId'] as num).toInt(),
|
||||
measuredAt: DateTime.parse(json['measuredAt'] as String),
|
||||
weightGrams: (json['weightGrams'] as num?)?.toInt(),
|
||||
lengthMm: (json['lengthMm'] as num?)?.toInt(),
|
||||
temperatureC: (json['temperatureC'] as num?)?.toDouble(),
|
||||
note: json['note'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$MeasurementImplToJson(_$MeasurementImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'hedgehogId': instance.hedgehogId,
|
||||
'measuredAt': instance.measuredAt.toIso8601String(),
|
||||
'weightGrams': instance.weightGrams,
|
||||
'lengthMm': instance.lengthMm,
|
||||
'temperatureC': instance.temperatureC,
|
||||
'note': instance.note,
|
||||
};
|
||||
Reference in New Issue
Block a user