전역 구조 리팩터링 및 테스트 확장
This commit is contained in:
131
lib/widgets/components/feedback.dart
Normal file
131
lib/widgets/components/feedback.dart
Normal file
@@ -0,0 +1,131 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
|
||||
/// Superport 전역에서 사용하는 토스트/스낵바 헬퍼.
|
||||
class SuperportToast {
|
||||
SuperportToast._();
|
||||
|
||||
static void success(BuildContext context, String message) {
|
||||
_show(context, message, _ToastVariant.success);
|
||||
}
|
||||
|
||||
static void info(BuildContext context, String message) {
|
||||
_show(context, message, _ToastVariant.info);
|
||||
}
|
||||
|
||||
static void warning(BuildContext context, String message) {
|
||||
_show(context, message, _ToastVariant.warning);
|
||||
}
|
||||
|
||||
static void error(BuildContext context, String message) {
|
||||
_show(context, message, _ToastVariant.error);
|
||||
}
|
||||
|
||||
static void _show(
|
||||
BuildContext context,
|
||||
String message,
|
||||
_ToastVariant variant,
|
||||
) {
|
||||
final theme = ShadTheme.of(context);
|
||||
final (Color background, Color foreground) = switch (variant) {
|
||||
_ToastVariant.success => (
|
||||
theme.colorScheme.primary,
|
||||
theme.colorScheme.primaryForeground,
|
||||
),
|
||||
_ToastVariant.info => (
|
||||
theme.colorScheme.accent,
|
||||
theme.colorScheme.accentForeground,
|
||||
),
|
||||
_ToastVariant.warning => (
|
||||
theme.colorScheme.secondary,
|
||||
theme.colorScheme.secondaryForeground,
|
||||
),
|
||||
_ToastVariant.error => (
|
||||
theme.colorScheme.destructive,
|
||||
theme.colorScheme.destructiveForeground,
|
||||
),
|
||||
};
|
||||
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
messenger
|
||||
..hideCurrentSnackBar()
|
||||
..showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
message,
|
||||
style: theme.textTheme.small.copyWith(
|
||||
color: foreground,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
backgroundColor: background,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
duration: const Duration(seconds: 3),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum _ToastVariant { success, info, warning, error }
|
||||
|
||||
/// 기본 골격을 표현하는 스켈레톤 블록.
|
||||
class SuperportSkeleton extends StatelessWidget {
|
||||
const SuperportSkeleton({
|
||||
super.key,
|
||||
this.width,
|
||||
this.height = 16,
|
||||
this.borderRadius = const BorderRadius.all(Radius.circular(8)),
|
||||
});
|
||||
|
||||
final double? width;
|
||||
final double height;
|
||||
final BorderRadius borderRadius;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = ShadTheme.of(context);
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 600),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.muted,
|
||||
borderRadius: borderRadius,
|
||||
),
|
||||
width: width,
|
||||
height: height,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 리스트 데이터를 대체하는 반복 스켈레톤 레이아웃.
|
||||
class SuperportSkeletonList extends StatelessWidget {
|
||||
const SuperportSkeletonList({
|
||||
super.key,
|
||||
this.itemCount = 6,
|
||||
this.height = 56,
|
||||
this.gap = 12,
|
||||
this.padding = const EdgeInsets.all(16),
|
||||
});
|
||||
|
||||
final int itemCount;
|
||||
final double height;
|
||||
final double gap;
|
||||
final EdgeInsetsGeometry padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: padding,
|
||||
child: Column(
|
||||
children: [
|
||||
for (var i = 0; i < itemCount; i++) ...[
|
||||
SuperportSkeleton(
|
||||
height: height,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
if (i != itemCount - 1) SizedBox(height: gap),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user