211 lines
5.7 KiB
Dart
211 lines
5.7 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:shadcn_ui/shadcn_ui.dart';
|
|
import 'package:shimmer/shimmer.dart';
|
|
|
|
import '../theme/spacing.dart';
|
|
|
|
class SkeletonLineCard extends StatelessWidget {
|
|
const SkeletonLineCard({
|
|
super.key,
|
|
this.lineWidths = const [0.4, 1, 0.8, 0.6],
|
|
this.height,
|
|
});
|
|
|
|
final List<double> lineWidths;
|
|
final double? height;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final cs = ShadTheme.of(context).colorScheme;
|
|
return Container(
|
|
height: height,
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.all(Spacing.cardPadding),
|
|
decoration: BoxDecoration(
|
|
color: cs.card,
|
|
border: Border.all(color: cs.border, width: 1),
|
|
borderRadius: BorderRadius.circular(Spacing.radiusCard),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
for (var i = 0; i < lineWidths.length; i++) ...[
|
|
_SkeletonLine(widthFactor: lineWidths[i]),
|
|
if (i != lineWidths.length - 1) const SizedBox(height: 10),
|
|
],
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class SkeletonInstitutionCard extends StatelessWidget {
|
|
const SkeletonInstitutionCard({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return const SkeletonLineCard(lineWidths: [0.4, 1, 0.6]);
|
|
}
|
|
}
|
|
|
|
class SkeletonListenCard extends StatelessWidget {
|
|
const SkeletonListenCard({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return const SkeletonLineCard(lineWidths: [0.4, 1, 0.6]);
|
|
}
|
|
}
|
|
|
|
class SkeletonSectionCard extends StatelessWidget {
|
|
const SkeletonSectionCard({
|
|
super.key,
|
|
required this.roman,
|
|
required this.lineWidths,
|
|
this.baseColor,
|
|
this.highlightColor,
|
|
});
|
|
|
|
final String roman;
|
|
final List<double> lineWidths;
|
|
final Color? baseColor;
|
|
final Color? highlightColor;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final cs = ShadTheme.of(context).colorScheme;
|
|
final brightness = ShadTheme.of(context).brightness;
|
|
final resolvedBaseColor =
|
|
baseColor ??
|
|
(brightness == Brightness.dark
|
|
? cs.secondary
|
|
: cs.border.withValues(alpha: 0.95));
|
|
final resolvedHighlightColor =
|
|
highlightColor ??
|
|
(brightness == Brightness.dark
|
|
? cs.card
|
|
: cs.secondary.withValues(alpha: 0.95));
|
|
return Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: Spacing.cardPadding,
|
|
vertical: 16,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: cs.card,
|
|
border: Border.all(color: cs.border, width: 1),
|
|
borderRadius: BorderRadius.circular(Spacing.radiusCard),
|
|
),
|
|
child: Shimmer.fromColors(
|
|
baseColor: resolvedBaseColor,
|
|
highlightColor: resolvedHighlightColor,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
for (var i = 0; i < lineWidths.length; i++) ...[
|
|
_SkeletonLine(
|
|
widthFactor: lineWidths[i],
|
|
height: i == 0 ? 14 : 12,
|
|
),
|
|
if (i != lineWidths.length - 1) const SizedBox(height: 10),
|
|
],
|
|
const SizedBox(height: 2),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _SkeletonLine extends StatelessWidget {
|
|
const _SkeletonLine({required this.widthFactor, this.height = 12});
|
|
|
|
final double widthFactor;
|
|
final double height;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final cs = ShadTheme.of(context).colorScheme;
|
|
return FractionallySizedBox(
|
|
widthFactor: widthFactor,
|
|
child: Shimmer.fromColors(
|
|
baseColor: cs.secondary,
|
|
highlightColor: cs.muted,
|
|
child: Container(
|
|
height: height,
|
|
decoration: BoxDecoration(
|
|
color: cs.secondary,
|
|
borderRadius: BorderRadius.circular(6),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class SectionSkeletonList extends StatelessWidget {
|
|
const SectionSkeletonList({
|
|
super.key,
|
|
required this.count,
|
|
required this.lineWidthsForIndex,
|
|
this.romanLabels,
|
|
});
|
|
|
|
final int count;
|
|
final List<double> Function(int index) lineWidthsForIndex;
|
|
final List<String>? romanLabels;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final labels =
|
|
romanLabels ??
|
|
List<String>.generate(count, (index) => _roman(index + 1));
|
|
return SliverPadding(
|
|
padding: const EdgeInsets.fromLTRB(Spacing.page, 4, Spacing.page, 24),
|
|
sliver: SliverList.separated(
|
|
itemCount: count,
|
|
separatorBuilder: (_, _) => const SizedBox(height: Spacing.cardGap),
|
|
itemBuilder: (context, index) => SkeletonSectionCard(
|
|
roman: labels[index],
|
|
lineWidths: lineWidthsForIndex(index),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class SectionSkeletonListView extends StatelessWidget {
|
|
const SectionSkeletonListView({
|
|
super.key,
|
|
required this.count,
|
|
required this.lineWidthsForIndex,
|
|
this.romanLabels,
|
|
});
|
|
|
|
final int count;
|
|
final List<double> Function(int index) lineWidthsForIndex;
|
|
final List<String>? romanLabels;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final labels =
|
|
romanLabels ??
|
|
List<String>.generate(count, (index) => _roman(index + 1));
|
|
return ListView.separated(
|
|
padding: const EdgeInsets.only(bottom: 72),
|
|
itemCount: count,
|
|
separatorBuilder: (_, _) => const SizedBox(height: Spacing.cardGap),
|
|
itemBuilder: (context, index) => SkeletonSectionCard(
|
|
roman: labels[index],
|
|
lineWidths: lineWidthsForIndex(index),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
String _roman(int value) {
|
|
const numerals = ['I.', 'II.', 'III.', 'IV.', 'V.', 'VI.', 'VII.', 'VIII.'];
|
|
if (value <= 0 || value > numerals.length) return '$value.';
|
|
return numerals[value - 1];
|
|
}
|