Comprehensive codebase audit and cleanup
- Fixed broken test suite: replaced counter widget tests with Console UI tests - Removed dead code: deleted unused MyHomePage widget and svrnty_components.dart library - Updated project branding: renamed package from "my_app" to "console" - Enhanced documentation: rewrote README with project features and setup instructions - Added coding standards: strict typing rules forbidding 'any' type across all languages - Implemented response protocol: structured answer format with context persistence - Fixed backend button: corrected URL from https to http, added proper error handling - Improved .gitignore: added Flutter plugins, CocoaPods, and design folder exclusions - Fixed UI overflow: increased status card height to prevent RenderFlex overflow These changes eliminate technical debt, establish code quality standards, and ensure all functionality works correctly across platforms. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,478 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:animate_do/animate_do.dart';
|
||||
|
||||
/// Svrnty Design System Components
|
||||
/// Reusable, branded components for the Svrnty Console application
|
||||
///
|
||||
/// Brand Colors:
|
||||
/// - Primary (Crimson): #C44D58
|
||||
/// - Secondary (Slate Blue): #475C6C
|
||||
|
||||
// ============================================================================
|
||||
// SVRNTY BUTTONS
|
||||
// ============================================================================
|
||||
|
||||
enum SvrntyButtonVariant { primary, secondary, ghost, danger }
|
||||
|
||||
class SvrntyButton extends StatelessWidget {
|
||||
final String text;
|
||||
final VoidCallback? onPressed;
|
||||
final SvrntyButtonVariant variant;
|
||||
final IconData? icon;
|
||||
final bool isLoading;
|
||||
final bool fullWidth;
|
||||
|
||||
const SvrntyButton({
|
||||
Key? key,
|
||||
required this.text,
|
||||
this.onPressed,
|
||||
this.variant = SvrntyButtonVariant.primary,
|
||||
this.icon,
|
||||
this.isLoading = false,
|
||||
this.fullWidth = false,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
Color backgroundColor;
|
||||
Color textColor;
|
||||
Color? borderColor;
|
||||
|
||||
switch (variant) {
|
||||
case SvrntyButtonVariant.primary:
|
||||
backgroundColor = colorScheme.primary;
|
||||
textColor = Colors.white;
|
||||
borderColor = null;
|
||||
break;
|
||||
case SvrntyButtonVariant.secondary:
|
||||
backgroundColor = colorScheme.secondary;
|
||||
textColor = Colors.white;
|
||||
borderColor = null;
|
||||
break;
|
||||
case SvrntyButtonVariant.ghost:
|
||||
backgroundColor = Colors.transparent;
|
||||
textColor = colorScheme.primary;
|
||||
borderColor = colorScheme.primary;
|
||||
break;
|
||||
case SvrntyButtonVariant.danger:
|
||||
backgroundColor = colorScheme.error;
|
||||
textColor = Colors.white;
|
||||
borderColor = null;
|
||||
break;
|
||||
}
|
||||
|
||||
return FadeInUp(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: SizedBox(
|
||||
width: fullWidth ? double.infinity : null,
|
||||
child: ElevatedButton(
|
||||
onPressed: isLoading ? null : onPressed,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: backgroundColor,
|
||||
foregroundColor: textColor,
|
||||
elevation: variant == SvrntyButtonVariant.ghost ? 0 : 2,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
side: borderColor != null
|
||||
? BorderSide(color: borderColor, width: 2)
|
||||
: BorderSide.none,
|
||||
),
|
||||
),
|
||||
child: isLoading
|
||||
? SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(textColor),
|
||||
),
|
||||
)
|
||||
: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (icon != null) ...[
|
||||
Icon(icon, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
Text(
|
||||
text,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SVRNTY CARDS
|
||||
// ============================================================================
|
||||
|
||||
class SvrntyCard extends StatelessWidget {
|
||||
final Widget child;
|
||||
final Color? accentColor;
|
||||
final VoidCallback? onTap;
|
||||
final bool showBorder;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
const SvrntyCard({
|
||||
Key? key,
|
||||
required this.child,
|
||||
this.accentColor,
|
||||
this.onTap,
|
||||
this.showBorder = true,
|
||||
this.padding,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final effectiveAccentColor = accentColor ?? colorScheme.primary;
|
||||
|
||||
return FadeInUp(
|
||||
duration: const Duration(milliseconds: 400),
|
||||
child: Container(
|
||||
decoration: showBorder
|
||||
? BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
effectiveAccentColor.withOpacity(0.15),
|
||||
effectiveAccentColor.withOpacity(0.05),
|
||||
],
|
||||
),
|
||||
border: Border.all(
|
||||
color: effectiveAccentColor.withOpacity(0.3),
|
||||
width: 1,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
child: Card(
|
||||
elevation: 2,
|
||||
color: showBorder ? Colors.transparent : null,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Container(
|
||||
decoration: showBorder
|
||||
? BoxDecoration(
|
||||
border: Border(
|
||||
left: BorderSide(
|
||||
color: effectiveAccentColor,
|
||||
width: 4,
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
child: Padding(
|
||||
padding: padding ?? const EdgeInsets.all(20),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SVRNTY BADGES
|
||||
// ============================================================================
|
||||
|
||||
enum SvrntyBadgeStatus { success, warning, error, info, neutral }
|
||||
|
||||
class SvrntyBadge extends StatelessWidget {
|
||||
final String text;
|
||||
final SvrntyBadgeStatus status;
|
||||
final IconData? icon;
|
||||
|
||||
const SvrntyBadge({
|
||||
Key? key,
|
||||
required this.text,
|
||||
this.status = SvrntyBadgeStatus.neutral,
|
||||
this.icon,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
Color backgroundColor;
|
||||
Color textColor;
|
||||
|
||||
switch (status) {
|
||||
case SvrntyBadgeStatus.success:
|
||||
backgroundColor = Colors.green;
|
||||
textColor = Colors.white;
|
||||
break;
|
||||
case SvrntyBadgeStatus.warning:
|
||||
backgroundColor = Colors.orange;
|
||||
textColor = Colors.white;
|
||||
break;
|
||||
case SvrntyBadgeStatus.error:
|
||||
backgroundColor = colorScheme.error;
|
||||
textColor = Colors.white;
|
||||
break;
|
||||
case SvrntyBadgeStatus.info:
|
||||
backgroundColor = colorScheme.primary;
|
||||
textColor = Colors.white;
|
||||
break;
|
||||
case SvrntyBadgeStatus.neutral:
|
||||
backgroundColor = colorScheme.secondary;
|
||||
textColor = Colors.white;
|
||||
break;
|
||||
}
|
||||
|
||||
return Pulse(
|
||||
duration: const Duration(milliseconds: 1000),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: backgroundColor.withOpacity(0.3),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (icon != null) ...[
|
||||
Icon(icon, color: textColor, size: 16),
|
||||
const SizedBox(width: 6),
|
||||
],
|
||||
Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
color: textColor,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: 'Montserrat',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SVRNTY ICON BUTTONS
|
||||
// ============================================================================
|
||||
|
||||
class SvrntyIconButton extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final VoidCallback? onPressed;
|
||||
final Color? backgroundColor;
|
||||
final Color? iconColor;
|
||||
final double size;
|
||||
|
||||
const SvrntyIconButton({
|
||||
Key? key,
|
||||
required this.icon,
|
||||
this.onPressed,
|
||||
this.backgroundColor,
|
||||
this.iconColor,
|
||||
this.size = 40,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
return FadeIn(
|
||||
child: Container(
|
||||
width: size,
|
||||
height: size,
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor ?? colorScheme.primary.withOpacity(0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: IconButton(
|
||||
icon: Icon(icon),
|
||||
color: iconColor ?? colorScheme.primary,
|
||||
iconSize: size * 0.5,
|
||||
onPressed: onPressed,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SVRNTY SECTION HEADER
|
||||
// ============================================================================
|
||||
|
||||
class SvrntySectionHeader extends StatelessWidget {
|
||||
final String title;
|
||||
final String? subtitle;
|
||||
final Widget? action;
|
||||
|
||||
const SvrntySectionHeader({
|
||||
Key? key,
|
||||
required this.title,
|
||||
this.subtitle,
|
||||
this.action,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
return FadeInLeft(
|
||||
duration: const Duration(milliseconds: 400),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 4,
|
||||
height: 28,
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.primary,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.onSurface,
|
||||
fontFamily: 'Montserrat',
|
||||
),
|
||||
),
|
||||
if (subtitle != null) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
subtitle!,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
fontFamily: 'Montserrat',
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
if (action != null) action!,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SVRNTY INPUT FIELD
|
||||
// ============================================================================
|
||||
|
||||
class SvrntyTextField extends StatelessWidget {
|
||||
final String label;
|
||||
final String? hint;
|
||||
final TextEditingController? controller;
|
||||
final IconData? prefixIcon;
|
||||
final bool obscureText;
|
||||
final TextInputType? keyboardType;
|
||||
final String? Function(String?)? validator;
|
||||
|
||||
const SvrntyTextField({
|
||||
Key? key,
|
||||
required this.label,
|
||||
this.hint,
|
||||
this.controller,
|
||||
this.prefixIcon,
|
||||
this.obscureText = false,
|
||||
this.keyboardType,
|
||||
this.validator,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
return FadeInUp(
|
||||
duration: const Duration(milliseconds: 350),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: colorScheme.onSurface,
|
||||
fontFamily: 'Montserrat',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextFormField(
|
||||
controller: controller,
|
||||
obscureText: obscureText,
|
||||
keyboardType: keyboardType,
|
||||
validator: validator,
|
||||
style: const TextStyle(fontFamily: 'Montserrat'),
|
||||
decoration: InputDecoration(
|
||||
hintText: hint,
|
||||
prefixIcon: prefixIcon != null ? Icon(prefixIcon) : null,
|
||||
filled: true,
|
||||
fillColor: colorScheme.surfaceContainerHighest,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(
|
||||
color: colorScheme.outline.withOpacity(0.3),
|
||||
),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(
|
||||
color: colorScheme.outline.withOpacity(0.2),
|
||||
),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(
|
||||
color: colorScheme.primary,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(
|
||||
color: colorScheme.error,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,9 @@ import 'package:flutter/material.dart';
|
||||
import 'package:iconsax/iconsax.dart';
|
||||
import 'package:animate_do/animate_do.dart';
|
||||
import 'package:getwidget/getwidget.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'components/navigation_sidebar.dart';
|
||||
import 'pages/architech_page.dart';
|
||||
import 'dart:html' as html;
|
||||
|
||||
class ConsoleLandingPage extends StatefulWidget {
|
||||
const ConsoleLandingPage({Key? key}) : super(key: key);
|
||||
@@ -195,7 +195,7 @@ class _ConsoleLandingPageState extends State<ConsoleLandingPage> {
|
||||
colorScheme.primary,
|
||||
Icons.api,
|
||||
'Active',
|
||||
url: 'https://localhost:7108/swagger/',
|
||||
url: 'http://localhost:7108/swagger/',
|
||||
),
|
||||
_buildStatusCard(
|
||||
'Frontend',
|
||||
@@ -306,7 +306,7 @@ class _ConsoleLandingPageState extends State<ConsoleLandingPage> {
|
||||
return GridView.builder(
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: crossAxisCount,
|
||||
mainAxisExtent: 140,
|
||||
mainAxisExtent: 155,
|
||||
crossAxisSpacing: spacing,
|
||||
mainAxisSpacing: spacing,
|
||||
),
|
||||
@@ -353,9 +353,46 @@ class _ConsoleLandingPageState extends State<ConsoleLandingPage> {
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
onTap: () async {
|
||||
if (url != null) {
|
||||
html.window.open(url, '_blank');
|
||||
try {
|
||||
final uri = Uri.parse(url);
|
||||
if (await canLaunchUrl(uri)) {
|
||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
if (context.mounted) {
|
||||
GFToast.showToast(
|
||||
'Opening $title...',
|
||||
context,
|
||||
toastPosition: GFToastPosition.BOTTOM,
|
||||
textStyle: const TextStyle(fontSize: 14, color: Colors.white),
|
||||
backgroundColor: color.withOpacity(0.9),
|
||||
toastDuration: 2,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (context.mounted) {
|
||||
GFToast.showToast(
|
||||
'Cannot open $url',
|
||||
context,
|
||||
toastPosition: GFToastPosition.BOTTOM,
|
||||
textStyle: const TextStyle(fontSize: 14, color: Colors.white),
|
||||
backgroundColor: colorScheme.error.withOpacity(0.9),
|
||||
toastDuration: 3,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
GFToast.showToast(
|
||||
'Error: ${e.toString()}',
|
||||
context,
|
||||
toastPosition: GFToastPosition.BOTTOM,
|
||||
textStyle: const TextStyle(fontSize: 14, color: Colors.white),
|
||||
backgroundColor: colorScheme.error.withOpacity(0.9),
|
||||
toastDuration: 3,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
GFToast.showToast(
|
||||
'$title tapped',
|
||||
|
||||
@@ -22,89 +22,3 @@ class MyApp extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
const MyHomePage({super.key, required this.title});
|
||||
|
||||
// This widget is the home page of your application. It is stateful, meaning
|
||||
// that it has a State object (defined below) that contains fields that affect
|
||||
// how it looks.
|
||||
|
||||
// This class is the configuration for the state. It holds the values (in this
|
||||
// case the title) provided by the parent (in this case the App widget) and
|
||||
// used by the build method of the State. Fields in a Widget subclass are
|
||||
// always marked "final".
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
State<MyHomePage> createState() => _MyHomePageState();
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
int _counter = 0;
|
||||
|
||||
void _incrementCounter() {
|
||||
setState(() {
|
||||
// This call to setState tells the Flutter framework that something has
|
||||
// changed in this State, which causes it to rerun the build method below
|
||||
// so that the display can reflect the updated values. If we changed
|
||||
// _counter without calling setState(), then the build method would not be
|
||||
// called again, and so nothing would appear to happen.
|
||||
_counter++;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// This method is rerun every time setState is called, for instance as done
|
||||
// by the _incrementCounter method above.
|
||||
//
|
||||
// The Flutter framework has been optimized to make rerunning build methods
|
||||
// fast, so that you can just rebuild anything that needs updating rather
|
||||
// than having to individually change instances of widgets.
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
// TRY THIS: Try changing the color here to a specific color (to
|
||||
// Colors.amber, perhaps?) and trigger a hot reload to see the AppBar
|
||||
// change color while the other colors stay the same.
|
||||
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
||||
// Here we take the value from the MyHomePage object that was created by
|
||||
// the App.build method, and use it to set our appbar title.
|
||||
title: Text(widget.title),
|
||||
),
|
||||
body: Center(
|
||||
// Center is a layout widget. It takes a single child and positions it
|
||||
// in the middle of the parent.
|
||||
child: Column(
|
||||
// Column is also a layout widget. It takes a list of children and
|
||||
// arranges them vertically. By default, it sizes itself to fit its
|
||||
// children horizontally, and tries to be as tall as its parent.
|
||||
//
|
||||
// Column has various properties to control how it sizes itself and
|
||||
// how it positions its children. Here we use mainAxisAlignment to
|
||||
// center the children vertically; the main axis here is the vertical
|
||||
// axis because Columns are vertical (the cross axis would be
|
||||
// horizontal).
|
||||
//
|
||||
// TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint"
|
||||
// action in the IDE, or press "p" in the console), to see the
|
||||
// wireframe for each widget.
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
const Text('You have pushed the button this many times:'),
|
||||
Text(
|
||||
'$_counter',
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _incrementCounter,
|
||||
tooltip: 'Increment',
|
||||
child: const Icon(Icons.add),
|
||||
), // This trailing comma makes auto-formatting nicer for build methods.
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user