284299b946
Complete Flutter app (Android + iOS) mirroring the web frontend: - Core: Riverpod state, Dio networking with auth interceptor + auto-refresh, go_router navigation, flutter_secure_storage, light/dark theme with MedievalSharp/Crimson Pro fonts, German l10n - Market: search with text/GPS/radius/date/sort filters, list + map views (flutter_map + OSM), detail screen with opening hours, admission prices, single-marker map, pagination - Auth: login (password + magic link tabs), register, OAuth button placeholders, 2FA code prompt on 401, sealed auth state provider - User: profile view/edit/delete with confirm dialog, 2FA setup/disable on security screen - GPS: geolocator with IP-based fallback (geojs.io) matching web behavior - Platform: Android internet + location permissions, iOS NSLocation description - Tests: date/currency/distance formatter unit tests (13 passing) - Zero analysis issues, debug APK builds successfully
71 lines
1.9 KiB
Dart
71 lines
1.9 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
enum AppButtonVariant { primary, outline, text, danger }
|
|
|
|
class AppButton extends StatelessWidget {
|
|
final String label;
|
|
final VoidCallback? onPressed;
|
|
final AppButtonVariant variant;
|
|
final bool isLoading;
|
|
final IconData? icon;
|
|
final bool fullWidth;
|
|
|
|
const AppButton({
|
|
super.key,
|
|
required this.label,
|
|
this.onPressed,
|
|
this.variant = AppButtonVariant.primary,
|
|
this.isLoading = false,
|
|
this.icon,
|
|
this.fullWidth = false,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final child = isLoading
|
|
? const SizedBox(
|
|
width: 20,
|
|
height: 20,
|
|
child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white),
|
|
)
|
|
: icon != null
|
|
? Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(icon, size: 18),
|
|
const SizedBox(width: 8),
|
|
Text(label),
|
|
],
|
|
)
|
|
: Text(label);
|
|
|
|
final button = switch (variant) {
|
|
AppButtonVariant.primary => ElevatedButton(
|
|
onPressed: isLoading ? null : onPressed,
|
|
child: child,
|
|
),
|
|
AppButtonVariant.outline => OutlinedButton(
|
|
onPressed: isLoading ? null : onPressed,
|
|
child: child,
|
|
),
|
|
AppButtonVariant.text => TextButton(
|
|
onPressed: isLoading ? null : onPressed,
|
|
child: child,
|
|
),
|
|
AppButtonVariant.danger => ElevatedButton(
|
|
onPressed: isLoading ? null : onPressed,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Theme.of(context).colorScheme.error,
|
|
foregroundColor: Theme.of(context).colorScheme.onError,
|
|
),
|
|
child: child,
|
|
),
|
|
};
|
|
|
|
if (fullWidth) {
|
|
return SizedBox(width: double.infinity, child: button);
|
|
}
|
|
return button;
|
|
}
|
|
}
|