Improve visible focus styling in admin content
This commit is contained in:
@@ -543,17 +543,20 @@ class _AdminScreenState extends ConsumerState<AdminScreen> {
|
|||||||
SizedBox(height: 24 * s),
|
SizedBox(height: 24 * s),
|
||||||
],
|
],
|
||||||
|
|
||||||
ElevatedButton.icon(
|
_tvActionButton(
|
||||||
onPressed: _saveJumat,
|
s: s,
|
||||||
style: ElevatedButton.styleFrom(
|
child: ElevatedButton.icon(
|
||||||
backgroundColor: SacredColors.secondary,
|
onPressed: _saveJumat,
|
||||||
foregroundColor: Colors.black,
|
style: ElevatedButton.styleFrom(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 40 * s, vertical: 20 * s),
|
backgroundColor: SacredColors.secondary,
|
||||||
textStyle: TextStyle(fontSize: 18 * s, fontWeight: FontWeight.bold),
|
foregroundColor: Colors.black,
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(SacredRadii.lg)),
|
padding: EdgeInsets.symmetric(horizontal: 40 * s, vertical: 20 * s),
|
||||||
|
textStyle: TextStyle(fontSize: 18 * s, fontWeight: FontWeight.bold),
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(SacredRadii.lg)),
|
||||||
|
),
|
||||||
|
icon: const Icon(Icons.save_rounded),
|
||||||
|
label: const Text('SIMPAN DATA JUMAT'),
|
||||||
),
|
),
|
||||||
icon: const Icon(Icons.save_rounded),
|
|
||||||
label: const Text('SIMPAN DATA JUMAT'),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
@@ -625,16 +628,19 @@ class _AdminScreenState extends ConsumerState<AdminScreen> {
|
|||||||
children: [
|
children: [
|
||||||
_sectionLabel('Tipografi & Skala Teks', s),
|
_sectionLabel('Tipografi & Skala Teks', s),
|
||||||
SizedBox(height: 12 * s),
|
SizedBox(height: 12 * s),
|
||||||
SegmentedButton<int>(
|
_buildSegmentedControl(
|
||||||
segments: const [
|
s: s,
|
||||||
ButtonSegment(value: 0, label: Text('Kecil')),
|
child: SegmentedButton<int>(
|
||||||
ButtonSegment(value: 1, label: Text('Normal')),
|
segments: const [
|
||||||
ButtonSegment(value: 2, label: Text('Besar')),
|
ButtonSegment(value: 0, label: Text('Kecil')),
|
||||||
],
|
ButtonSegment(value: 1, label: Text('Normal')),
|
||||||
selected: {_textScaleIndex},
|
ButtonSegment(value: 2, label: Text('Besar')),
|
||||||
onSelectionChanged: (val) {
|
],
|
||||||
setState(() => _textScaleIndex = val.first);
|
selected: {_textScaleIndex},
|
||||||
},
|
onSelectionChanged: (val) {
|
||||||
|
setState(() => _textScaleIndex = val.first);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 28 * s),
|
SizedBox(height: 28 * s),
|
||||||
_buildTvIntStepperField(
|
_buildTvIntStepperField(
|
||||||
@@ -692,12 +698,11 @@ class _AdminScreenState extends ConsumerState<AdminScreen> {
|
|||||||
|
|
||||||
_sectionLabel('Background Layar Utama (Unsplash)', s),
|
_sectionLabel('Background Layar Utama (Unsplash)', s),
|
||||||
SizedBox(height: 12 * s),
|
SizedBox(height: 12 * s),
|
||||||
SwitchListTile(
|
_buildSwitchTile(
|
||||||
title: Text('Gunakan Foto Unsplash API', style: GoogleFonts.plusJakartaSans(fontSize: 18 * s, color: SacredColors.onSurface)),
|
s: s,
|
||||||
|
title: 'Gunakan Foto Unsplash API',
|
||||||
value: _useUnsplash,
|
value: _useUnsplash,
|
||||||
onChanged: (val) => setState(() => _useUnsplash = val),
|
onChanged: (val) => setState(() => _useUnsplash = val),
|
||||||
activeThumbColor: SacredColors.primary,
|
|
||||||
contentPadding: EdgeInsets.zero,
|
|
||||||
),
|
),
|
||||||
if (_useUnsplash) ...[
|
if (_useUnsplash) ...[
|
||||||
SizedBox(height: 12 * s),
|
SizedBox(height: 12 * s),
|
||||||
@@ -715,17 +720,20 @@ class _AdminScreenState extends ConsumerState<AdminScreen> {
|
|||||||
],
|
],
|
||||||
|
|
||||||
SizedBox(height: 56 * s),
|
SizedBox(height: 56 * s),
|
||||||
ElevatedButton.icon(
|
_tvActionButton(
|
||||||
onPressed: _saveTampilan,
|
s: s,
|
||||||
style: ElevatedButton.styleFrom(
|
child: ElevatedButton.icon(
|
||||||
backgroundColor: SacredColors.primary,
|
onPressed: _saveTampilan,
|
||||||
foregroundColor: SacredColors.onPrimary,
|
style: ElevatedButton.styleFrom(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 48 * s, vertical: 24 * s),
|
backgroundColor: SacredColors.primary,
|
||||||
textStyle: TextStyle(fontSize: 18 * s, fontWeight: FontWeight.bold),
|
foregroundColor: SacredColors.onPrimary,
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(SacredRadii.lg)),
|
padding: EdgeInsets.symmetric(horizontal: 48 * s, vertical: 24 * s),
|
||||||
|
textStyle: TextStyle(fontSize: 18 * s, fontWeight: FontWeight.bold),
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(SacredRadii.lg)),
|
||||||
|
),
|
||||||
|
icon: HugeIcon(icon: HugeIcons.strokeRoundedFloppyDisk, color: SacredColors.onPrimary),
|
||||||
|
label: const Text('SIMPAN TAMPILAN'),
|
||||||
),
|
),
|
||||||
icon: HugeIcon(icon: HugeIcons.strokeRoundedFloppyDisk, color: SacredColors.onPrimary),
|
|
||||||
label: const Text('SIMPAN TAMPILAN'),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
@@ -775,20 +783,23 @@ class _AdminScreenState extends ConsumerState<AdminScreen> {
|
|||||||
] else
|
] else
|
||||||
Text('Belum ada foto latar masjid.', style: GoogleFonts.manrope(fontSize: 16 * s, color: SacredColors.onSurfaceVariant)),
|
Text('Belum ada foto latar masjid.', style: GoogleFonts.manrope(fontSize: 16 * s, color: SacredColors.onSurfaceVariant)),
|
||||||
SizedBox(height: 16 * s),
|
SizedBox(height: 16 * s),
|
||||||
ElevatedButton.icon(
|
_tvActionButton(
|
||||||
onPressed: () async {
|
s: s,
|
||||||
final res = await FilePicker.platform.pickFiles(type: FileType.image);
|
child: ElevatedButton.icon(
|
||||||
if (res != null && res.files.single.path != null) {
|
onPressed: () async {
|
||||||
setState(() => _brandedBgImage = res.files.single.path);
|
final res = await FilePicker.platform.pickFiles(type: FileType.image);
|
||||||
_saveTampilan();
|
if (res != null && res.files.single.path != null) {
|
||||||
}
|
setState(() => _brandedBgImage = res.files.single.path);
|
||||||
},
|
_saveTampilan();
|
||||||
icon: HugeIcon(icon: HugeIcons.strokeRoundedImage01, color: SacredColors.onPrimary, size: 20 * s),
|
}
|
||||||
label: Text('PILIH FOTO MASJID', style: TextStyle(fontSize: 16 * s)),
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
icon: HugeIcon(icon: HugeIcons.strokeRoundedImage01, color: SacredColors.onPrimary, size: 20 * s),
|
||||||
backgroundColor: SacredColors.secondary,
|
label: Text('PILIH FOTO MASJID', style: TextStyle(fontSize: 16 * s)),
|
||||||
foregroundColor: SacredColors.onSecondary,
|
style: ElevatedButton.styleFrom(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 24 * s, vertical: 16 * s),
|
backgroundColor: SacredColors.secondary,
|
||||||
|
foregroundColor: SacredColors.onSecondary,
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 24 * s, vertical: 16 * s),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -803,26 +814,29 @@ class _AdminScreenState extends ConsumerState<AdminScreen> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
_sectionLabel('Galeri Gambar Slideshow', s),
|
_sectionLabel('Galeri Gambar Slideshow', s),
|
||||||
ElevatedButton.icon(
|
_tvActionButton(
|
||||||
onPressed: () async {
|
s: s,
|
||||||
final res = await FilePicker.platform.pickFiles(type: FileType.image, allowMultiple: true);
|
child: ElevatedButton.icon(
|
||||||
if (res != null) {
|
onPressed: () async {
|
||||||
setState(() {
|
final res = await FilePicker.platform.pickFiles(type: FileType.image, allowMultiple: true);
|
||||||
for (var path in res.paths) {
|
if (res != null) {
|
||||||
if (path != null && !_slideshowImages.contains(path)) {
|
setState(() {
|
||||||
_slideshowImages.add(path);
|
for (var path in res.paths) {
|
||||||
|
if (path != null && !_slideshowImages.contains(path)) {
|
||||||
|
_slideshowImages.add(path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
_saveTampilan();
|
||||||
_saveTampilan();
|
}
|
||||||
}
|
},
|
||||||
},
|
icon: HugeIcon(icon: HugeIcons.strokeRoundedPlusSign, color: SacredColors.onSecondary, size: 18 * s),
|
||||||
icon: HugeIcon(icon: HugeIcons.strokeRoundedPlusSign, color: SacredColors.onSecondary, size: 18 * s),
|
label: Text('TAMBAH FOTO', style: TextStyle(fontSize: 14 * s)),
|
||||||
label: Text('TAMBAH FOTO', style: TextStyle(fontSize: 14 * s)),
|
style: ElevatedButton.styleFrom(
|
||||||
style: ElevatedButton.styleFrom(
|
backgroundColor: SacredColors.secondary,
|
||||||
backgroundColor: SacredColors.secondary,
|
foregroundColor: SacredColors.onSecondary,
|
||||||
foregroundColor: SacredColors.onSecondary,
|
padding: EdgeInsets.symmetric(horizontal: 20 * s, vertical: 14 * s),
|
||||||
padding: EdgeInsets.symmetric(horizontal: 20 * s, vertical: 14 * s),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -876,18 +890,21 @@ class _AdminScreenState extends ConsumerState<AdminScreen> {
|
|||||||
children: [
|
children: [
|
||||||
Text('Mode Animasi:', style: GoogleFonts.manrope(fontSize: 16 * s, color: SacredColors.onSurfaceVariant)),
|
Text('Mode Animasi:', style: GoogleFonts.manrope(fontSize: 16 * s, color: SacredColors.onSurfaceVariant)),
|
||||||
SizedBox(width: 12 * s),
|
SizedBox(width: 12 * s),
|
||||||
SegmentedButton<String>(
|
_buildSegmentedControl(
|
||||||
segments: [
|
s: s,
|
||||||
ButtonSegment(value: 'marquee', label: Text('Marquee', style: GoogleFonts.manrope(fontSize: 16 * s))),
|
child: SegmentedButton<String>(
|
||||||
ButtonSegment(value: 'fade', label: Text('Fade In-Out', style: GoogleFonts.manrope(fontSize: 16 * s))),
|
segments: [
|
||||||
],
|
ButtonSegment(value: 'marquee', label: Text('Marquee', style: GoogleFonts.manrope(fontSize: 16 * s))),
|
||||||
selected: {_marqueeAnimType},
|
ButtonSegment(value: 'fade', label: Text('Fade In-Out', style: GoogleFonts.manrope(fontSize: 16 * s))),
|
||||||
onSelectionChanged: (val) => setState(() => _marqueeAnimType = val.first),
|
],
|
||||||
style: ButtonStyle(
|
selected: {_marqueeAnimType},
|
||||||
backgroundColor: WidgetStateProperty.resolveWith((states) =>
|
onSelectionChanged: (val) => setState(() => _marqueeAnimType = val.first),
|
||||||
states.contains(WidgetState.selected) ? SacredColors.primary : SacredColors.surfaceContainerLowest),
|
style: ButtonStyle(
|
||||||
foregroundColor: WidgetStateProperty.resolveWith((states) =>
|
backgroundColor: WidgetStateProperty.resolveWith((states) =>
|
||||||
states.contains(WidgetState.selected) ? SacredColors.onPrimary : SacredColors.onSurfaceVariant),
|
states.contains(WidgetState.selected) ? SacredColors.primary : SacredColors.surfaceContainerLowest),
|
||||||
|
foregroundColor: WidgetStateProperty.resolveWith((states) =>
|
||||||
|
states.contains(WidgetState.selected) ? SacredColors.onPrimary : SacredColors.onSurfaceVariant),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -986,30 +1003,36 @@ class _AdminScreenState extends ConsumerState<AdminScreen> {
|
|||||||
SizedBox(height: 20 * s),
|
SizedBox(height: 20 * s),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
OutlinedButton.icon(
|
_tvActionButton(
|
||||||
onPressed: () {
|
s: s,
|
||||||
setState(() {
|
child: OutlinedButton.icon(
|
||||||
_runningTexts.add('');
|
onPressed: () {
|
||||||
_runningTextDurations.add(12);
|
setState(() {
|
||||||
});
|
_runningTexts.add('');
|
||||||
},
|
_runningTextDurations.add(12);
|
||||||
icon: HugeIcon(icon: HugeIcons.strokeRoundedPlusSign, color: SacredColors.primary, size: 20 * s),
|
});
|
||||||
label: Text('TAMBAH BARIS', style: GoogleFonts.plusJakartaSans(fontSize: 16 * s, color: SacredColors.primary)),
|
},
|
||||||
style: OutlinedButton.styleFrom(
|
icon: HugeIcon(icon: HugeIcons.strokeRoundedPlusSign, color: SacredColors.primary, size: 20 * s),
|
||||||
side: BorderSide(color: SacredColors.primary.withValues(alpha: 0.5)),
|
label: Text('TAMBAH BARIS', style: GoogleFonts.plusJakartaSans(fontSize: 16 * s, color: SacredColors.primary)),
|
||||||
padding: EdgeInsets.symmetric(horizontal: 24 * s, vertical: 16 * s),
|
style: OutlinedButton.styleFrom(
|
||||||
|
side: BorderSide(color: SacredColors.primary.withValues(alpha: 0.5)),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 24 * s, vertical: 16 * s),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 16 * s),
|
SizedBox(width: 16 * s),
|
||||||
ElevatedButton.icon(
|
_tvActionButton(
|
||||||
onPressed: _saveTampilan,
|
s: s,
|
||||||
style: ElevatedButton.styleFrom(
|
child: ElevatedButton.icon(
|
||||||
backgroundColor: SacredColors.primary,
|
onPressed: _saveTampilan,
|
||||||
foregroundColor: SacredColors.onPrimary,
|
style: ElevatedButton.styleFrom(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 32 * s, vertical: 16 * s),
|
backgroundColor: SacredColors.primary,
|
||||||
|
foregroundColor: SacredColors.onPrimary,
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 32 * s, vertical: 16 * s),
|
||||||
|
),
|
||||||
|
icon: HugeIcon(icon: HugeIcons.strokeRoundedFloppyDisk, color: SacredColors.onPrimary, size: 18 * s),
|
||||||
|
label: Text('SIMPAN TEKS', style: GoogleFonts.plusJakartaSans(fontSize: 16 * s, fontWeight: FontWeight.bold)),
|
||||||
),
|
),
|
||||||
icon: HugeIcon(icon: HugeIcons.strokeRoundedFloppyDisk, color: SacredColors.onPrimary, size: 18 * s),
|
|
||||||
label: Text('SIMPAN TEKS', style: GoogleFonts.plusJakartaSans(fontSize: 16 * s, fontWeight: FontWeight.bold)),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -1025,13 +1048,126 @@ class _AdminScreenState extends ConsumerState<AdminScreen> {
|
|||||||
Widget _adminCard(double s, {required Widget child}) {
|
Widget _adminCard(double s, {required Widget child}) {
|
||||||
return _scrollAware(
|
return _scrollAware(
|
||||||
controller: _scrollControllerForTab(_selectedTab),
|
controller: _scrollControllerForTab(_selectedTab),
|
||||||
|
child: _TvFocusFrame(
|
||||||
|
scale: s,
|
||||||
|
borderRadius: BorderRadius.circular(SacredRadii.xl),
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: EdgeInsets.all(36 * s),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: SacredColors.surfaceContainerHighest.withValues(alpha: 0.3),
|
||||||
|
borderRadius: BorderRadius.circular(SacredRadii.xl),
|
||||||
|
border: Border.all(color: SacredColors.outlineVariant.withValues(alpha: 0.2)),
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _tvFocusable({
|
||||||
|
required Widget child,
|
||||||
|
required double s,
|
||||||
|
double radius = SacredRadii.md,
|
||||||
|
bool scrollAware = true,
|
||||||
|
}) {
|
||||||
|
final framed = _TvFocusFrame(
|
||||||
|
scale: s,
|
||||||
|
borderRadius: BorderRadius.circular(radius),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!scrollAware) return framed;
|
||||||
|
|
||||||
|
return _scrollAware(
|
||||||
|
controller: _scrollControllerForTab(_selectedTab),
|
||||||
|
child: framed,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _tvActionButton({
|
||||||
|
required Widget child,
|
||||||
|
required double s,
|
||||||
|
double radius = SacredRadii.lg,
|
||||||
|
}) {
|
||||||
|
return _tvFocusable(
|
||||||
|
child: child,
|
||||||
|
s: s,
|
||||||
|
radius: radius,
|
||||||
|
scrollAware: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildReadonlyField(TextEditingController controller, double s) {
|
||||||
|
return _tvFocusable(
|
||||||
|
s: s,
|
||||||
|
child: TextField(
|
||||||
|
controller: controller,
|
||||||
|
readOnly: true,
|
||||||
|
style: GoogleFonts.plusJakartaSans(
|
||||||
|
fontSize: 24 * s,
|
||||||
|
color: SacredColors.onSurface,
|
||||||
|
),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
filled: true,
|
||||||
|
fillColor: SacredColors.surfaceContainerLowest,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(SacredRadii.md),
|
||||||
|
borderSide: BorderSide.none,
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(SacredRadii.md),
|
||||||
|
borderSide: const BorderSide(color: SacredColors.primary, width: 2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSwitchTile({
|
||||||
|
required double s,
|
||||||
|
required String title,
|
||||||
|
required bool value,
|
||||||
|
required ValueChanged<bool> onChanged,
|
||||||
|
}) {
|
||||||
|
return _tvFocusable(
|
||||||
|
s: s,
|
||||||
|
radius: SacredRadii.md,
|
||||||
child: Container(
|
child: Container(
|
||||||
width: double.infinity,
|
|
||||||
padding: EdgeInsets.all(36 * s),
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: SacredColors.surfaceContainerHighest.withValues(alpha: 0.3),
|
color: SacredColors.surfaceContainerLowest,
|
||||||
borderRadius: BorderRadius.circular(SacredRadii.xl),
|
borderRadius: BorderRadius.circular(SacredRadii.md),
|
||||||
border: Border.all(color: SacredColors.outlineVariant.withValues(alpha: 0.2)),
|
),
|
||||||
|
child: SwitchListTile(
|
||||||
|
title: Text(
|
||||||
|
title,
|
||||||
|
style: GoogleFonts.plusJakartaSans(
|
||||||
|
fontSize: 18 * s,
|
||||||
|
color: SacredColors.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
value: value,
|
||||||
|
onChanged: onChanged,
|
||||||
|
activeThumbColor: SacredColors.primary,
|
||||||
|
contentPadding: EdgeInsets.symmetric(horizontal: 12 * s),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSegmentedControl({
|
||||||
|
required double s,
|
||||||
|
required Widget child,
|
||||||
|
double radius = SacredRadii.md,
|
||||||
|
}) {
|
||||||
|
return _tvFocusable(
|
||||||
|
s: s,
|
||||||
|
radius: radius,
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.all(6 * s),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: SacredColors.surfaceContainerLowest,
|
||||||
|
borderRadius: BorderRadius.circular(radius),
|
||||||
),
|
),
|
||||||
child: child,
|
child: child,
|
||||||
),
|
),
|
||||||
@@ -1095,43 +1231,40 @@ class _AdminScreenState extends ConsumerState<AdminScreen> {
|
|||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TextField(
|
child: _buildReadonlyField(_cityCtrl, s),
|
||||||
controller: _cityCtrl,
|
|
||||||
readOnly: true,
|
|
||||||
style: GoogleFonts.plusJakartaSans(fontSize: 24 * s, color: SacredColors.onSurface),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
filled: true,
|
|
||||||
fillColor: SacredColors.surfaceContainerLowest,
|
|
||||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(SacredRadii.md), borderSide: BorderSide.none),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
SizedBox(width: 16 * s),
|
SizedBox(width: 16 * s),
|
||||||
ElevatedButton.icon(
|
_tvActionButton(
|
||||||
onPressed: () => _showCitySearchDialog(s),
|
s: s,
|
||||||
icon: HugeIcon(icon: HugeIcons.strokeRoundedSearch01, color: SacredColors.onPrimary),
|
child: ElevatedButton.icon(
|
||||||
label: Text('CARI KOTA', style: TextStyle(fontSize: 16 * s)),
|
onPressed: () => _showCitySearchDialog(s),
|
||||||
style: ElevatedButton.styleFrom(
|
icon: HugeIcon(icon: HugeIcons.strokeRoundedSearch01, color: SacredColors.onPrimary),
|
||||||
backgroundColor: SacredColors.secondary,
|
label: Text('CARI KOTA', style: TextStyle(fontSize: 16 * s)),
|
||||||
foregroundColor: SacredColors.onPrimary,
|
style: ElevatedButton.styleFrom(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 24 * s, vertical: 24 * s),
|
backgroundColor: SacredColors.secondary,
|
||||||
|
foregroundColor: SacredColors.onPrimary,
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 24 * s, vertical: 24 * s),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
SizedBox(height: 64 * s),
|
SizedBox(height: 64 * s),
|
||||||
ElevatedButton.icon(
|
_tvActionButton(
|
||||||
onPressed: _saveIdentity,
|
s: s,
|
||||||
style: ElevatedButton.styleFrom(
|
child: ElevatedButton.icon(
|
||||||
backgroundColor: SacredColors.primary,
|
onPressed: _saveIdentity,
|
||||||
foregroundColor: SacredColors.onPrimary,
|
style: ElevatedButton.styleFrom(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 48 * s, vertical: 24 * s),
|
backgroundColor: SacredColors.primary,
|
||||||
textStyle: TextStyle(fontSize: 20 * s, fontWeight: FontWeight.bold),
|
foregroundColor: SacredColors.onPrimary,
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(SacredRadii.lg)),
|
padding: EdgeInsets.symmetric(horizontal: 48 * s, vertical: 24 * s),
|
||||||
|
textStyle: TextStyle(fontSize: 20 * s, fontWeight: FontWeight.bold),
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(SacredRadii.lg)),
|
||||||
|
),
|
||||||
|
icon: HugeIcon(icon: HugeIcons.strokeRoundedFloppyDisk, color: SacredColors.onPrimary),
|
||||||
|
label: const Text('SIMPAN PERUBAHAN TULISAN'),
|
||||||
),
|
),
|
||||||
icon: HugeIcon(icon: HugeIcons.strokeRoundedFloppyDisk, color: SacredColors.onPrimary),
|
|
||||||
label: const Text('SIMPAN PERUBAHAN TULISAN'),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -1201,19 +1334,22 @@ class _AdminScreenState extends ConsumerState<AdminScreen> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ElevatedButton.icon(
|
_tvActionButton(
|
||||||
onPressed: _isSyncing ? null : _syncData,
|
s: s,
|
||||||
style: ElevatedButton.styleFrom(
|
child: ElevatedButton.icon(
|
||||||
backgroundColor: SacredColors.secondary,
|
onPressed: _isSyncing ? null : _syncData,
|
||||||
foregroundColor: SacredColors.onSecondary,
|
style: ElevatedButton.styleFrom(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 48 * s, vertical: 32 * s),
|
backgroundColor: SacredColors.secondary,
|
||||||
textStyle: TextStyle(fontSize: 20 * s, fontWeight: FontWeight.bold),
|
foregroundColor: SacredColors.onSecondary,
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(SacredRadii.lg)),
|
padding: EdgeInsets.symmetric(horizontal: 48 * s, vertical: 32 * s),
|
||||||
|
textStyle: TextStyle(fontSize: 20 * s, fontWeight: FontWeight.bold),
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(SacredRadii.lg)),
|
||||||
|
),
|
||||||
|
icon: _isSyncing
|
||||||
|
? SizedBox(width: 24*s, height: 24*s, child: const CircularProgressIndicator(color: SacredColors.onSecondary, strokeWidth: 3))
|
||||||
|
: HugeIcon(icon: HugeIcons.strokeRoundedCloudDownload, color: SacredColors.onSecondary),
|
||||||
|
label: Text(_isSyncing ? 'MENYINKRONKAN...' : 'SINKRONKAN DATA BULAN INI'),
|
||||||
),
|
),
|
||||||
icon: _isSyncing
|
|
||||||
? SizedBox(width: 24*s, height: 24*s, child: const CircularProgressIndicator(color: SacredColors.onSecondary, strokeWidth: 3))
|
|
||||||
: HugeIcon(icon: HugeIcons.strokeRoundedCloudDownload, color: SacredColors.onSecondary),
|
|
||||||
label: Text(_isSyncing ? 'MENYINKRONKAN...' : 'SINKRONKAN DATA BULAN INI'),
|
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -1297,28 +1433,34 @@ class _AdminScreenState extends ConsumerState<AdminScreen> {
|
|||||||
SizedBox(height: 16 * s),
|
SizedBox(height: 16 * s),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
OutlinedButton.icon(
|
_tvActionButton(
|
||||||
onPressed: () {
|
s: s,
|
||||||
setState(() {
|
child: OutlinedButton.icon(
|
||||||
_hijriOffsetDays = 0;
|
onPressed: () {
|
||||||
});
|
setState(() {
|
||||||
},
|
_hijriOffsetDays = 0;
|
||||||
icon: const Icon(Icons.refresh),
|
});
|
||||||
label: const Text('RESET OFFSET'),
|
},
|
||||||
|
icon: const Icon(Icons.refresh),
|
||||||
|
label: const Text('RESET OFFSET'),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 16 * s),
|
SizedBox(width: 16 * s),
|
||||||
ElevatedButton.icon(
|
_tvActionButton(
|
||||||
onPressed: _saveHijriSettings,
|
s: s,
|
||||||
style: ElevatedButton.styleFrom(
|
child: ElevatedButton.icon(
|
||||||
backgroundColor: SacredColors.primary,
|
onPressed: _saveHijriSettings,
|
||||||
foregroundColor: SacredColors.onPrimary,
|
style: ElevatedButton.styleFrom(
|
||||||
padding: EdgeInsets.symmetric(
|
backgroundColor: SacredColors.primary,
|
||||||
horizontal: 28 * s,
|
foregroundColor: SacredColors.onPrimary,
|
||||||
vertical: 18 * s,
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 28 * s,
|
||||||
|
vertical: 18 * s,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
icon: const Icon(Icons.save_rounded),
|
||||||
|
label: const Text('SIMPAN OFFSET HIJRIAH'),
|
||||||
),
|
),
|
||||||
icon: const Icon(Icons.save_rounded),
|
|
||||||
label: const Text('SIMPAN OFFSET HIJRIAH'),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -1463,17 +1605,20 @@ class _AdminScreenState extends ConsumerState<AdminScreen> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: 32 * s),
|
SizedBox(height: 32 * s),
|
||||||
ElevatedButton.icon(
|
_tvActionButton(
|
||||||
onPressed: _saveJadwalTimingSettings,
|
s: s,
|
||||||
style: ElevatedButton.styleFrom(
|
child: ElevatedButton.icon(
|
||||||
backgroundColor: SacredColors.secondary,
|
onPressed: _saveJadwalTimingSettings,
|
||||||
foregroundColor: Colors.black,
|
style: ElevatedButton.styleFrom(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 40 * s, vertical: 20 * s),
|
backgroundColor: SacredColors.secondary,
|
||||||
textStyle: TextStyle(fontSize: 18 * s, fontWeight: FontWeight.bold),
|
foregroundColor: Colors.black,
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(SacredRadii.lg)),
|
padding: EdgeInsets.symmetric(horizontal: 40 * s, vertical: 20 * s),
|
||||||
|
textStyle: TextStyle(fontSize: 18 * s, fontWeight: FontWeight.bold),
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(SacredRadii.lg)),
|
||||||
|
),
|
||||||
|
icon: const Icon(Icons.timer),
|
||||||
|
label: const Text('SIMPAN PENGATURAN JADWAL'),
|
||||||
),
|
),
|
||||||
icon: const Icon(Icons.timer),
|
|
||||||
label: const Text('SIMPAN PENGATURAN JADWAL'),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
@@ -1822,7 +1967,10 @@ class _AdminScreenState extends ConsumerState<AdminScreen> {
|
|||||||
|
|
||||||
return _scrollAware(
|
return _scrollAware(
|
||||||
controller: _scrollControllerForTab(_selectedTab),
|
controller: _scrollControllerForTab(_selectedTab),
|
||||||
child: Container(
|
child: _TvFocusFrame(
|
||||||
|
scale: s,
|
||||||
|
borderRadius: BorderRadius.circular(SacredRadii.md),
|
||||||
|
child: Container(
|
||||||
padding: EdgeInsets.all(16 * s),
|
padding: EdgeInsets.all(16 * s),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: SacredColors.surfaceContainerLowest,
|
color: SacredColors.surfaceContainerLowest,
|
||||||
@@ -1941,6 +2089,7 @@ class _AdminScreenState extends ConsumerState<AdminScreen> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1948,39 +2097,43 @@ class _AdminScreenState extends ConsumerState<AdminScreen> {
|
|||||||
Widget _buildTextField(String label, TextEditingController ctrl, double s, {int maxLines = 1}) {
|
Widget _buildTextField(String label, TextEditingController ctrl, double s, {int maxLines = 1}) {
|
||||||
return _scrollAware(
|
return _scrollAware(
|
||||||
controller: _scrollControllerForTab(_selectedTab),
|
controller: _scrollControllerForTab(_selectedTab),
|
||||||
child: Column(
|
child: _TvFocusFrame(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
scale: s,
|
||||||
children: [
|
borderRadius: BorderRadius.circular(SacredRadii.md),
|
||||||
Text(
|
child: Column(
|
||||||
label,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
style: GoogleFonts.manrope(
|
children: [
|
||||||
fontSize: 16 * s,
|
Text(
|
||||||
fontWeight: FontWeight.w600,
|
label,
|
||||||
color: SacredColors.onSurfaceVariant,
|
style: GoogleFonts.manrope(
|
||||||
),
|
fontSize: 16 * s,
|
||||||
),
|
fontWeight: FontWeight.w600,
|
||||||
SizedBox(height: 12 * s),
|
color: SacredColors.onSurfaceVariant,
|
||||||
TextField(
|
|
||||||
controller: ctrl,
|
|
||||||
maxLines: maxLines,
|
|
||||||
style: GoogleFonts.plusJakartaSans(
|
|
||||||
fontSize: 24 * s,
|
|
||||||
color: SacredColors.onSurface,
|
|
||||||
),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
filled: true,
|
|
||||||
fillColor: SacredColors.surfaceContainerLowest,
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(SacredRadii.md),
|
|
||||||
borderSide: BorderSide(color: SacredColors.outlineVariant.withValues(alpha: 0.5)),
|
|
||||||
),
|
|
||||||
focusedBorder: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(SacredRadii.md),
|
|
||||||
borderSide: const BorderSide(color: SacredColors.primary, width: 2),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
SizedBox(height: 12 * s),
|
||||||
|
TextField(
|
||||||
|
controller: ctrl,
|
||||||
|
maxLines: maxLines,
|
||||||
|
style: GoogleFonts.plusJakartaSans(
|
||||||
|
fontSize: 24 * s,
|
||||||
|
color: SacredColors.onSurface,
|
||||||
|
),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
filled: true,
|
||||||
|
fillColor: SacredColors.surfaceContainerLowest,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(SacredRadii.md),
|
||||||
|
borderSide: BorderSide(color: SacredColors.outlineVariant.withValues(alpha: 0.5)),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(SacredRadii.md),
|
||||||
|
borderSide: const BorderSide(color: SacredColors.primary, width: 2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2038,7 +2191,9 @@ class _AdminScreenState extends ConsumerState<AdminScreen> {
|
|||||||
const step = 0.05;
|
const step = 0.05;
|
||||||
const presets = [0.75, 1.0, 1.25, 1.5];
|
const presets = [0.75, 1.0, 1.25, 1.5];
|
||||||
|
|
||||||
return Container(
|
return _tvFocusable(
|
||||||
|
s: s,
|
||||||
|
child: Container(
|
||||||
padding: EdgeInsets.all(16 * s),
|
padding: EdgeInsets.all(16 * s),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: SacredColors.surfaceContainerLowest,
|
color: SacredColors.surfaceContainerLowest,
|
||||||
@@ -2140,32 +2295,38 @@ class _AdminScreenState extends ConsumerState<AdminScreen> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _tvStepBtn({required double s, required String label, required VoidCallback onPressed}) {
|
Widget _tvStepBtn({required double s, required String label, required VoidCallback onPressed}) {
|
||||||
return Material(
|
return _tvFocusable(
|
||||||
color: Colors.transparent,
|
s: s,
|
||||||
child: InkWell(
|
radius: SacredRadii.sm,
|
||||||
focusColor: SacredColors.primary.withValues(alpha: 0.35),
|
scrollAware: false,
|
||||||
hoverColor: SacredColors.primary.withValues(alpha: 0.15),
|
child: Material(
|
||||||
borderRadius: BorderRadius.circular(SacredRadii.sm),
|
color: Colors.transparent,
|
||||||
onTap: onPressed,
|
child: InkWell(
|
||||||
child: Container(
|
focusColor: SacredColors.primary.withValues(alpha: 0.35),
|
||||||
width: 42 * s,
|
hoverColor: SacredColors.primary.withValues(alpha: 0.15),
|
||||||
height: 38 * s,
|
borderRadius: BorderRadius.circular(SacredRadii.sm),
|
||||||
alignment: Alignment.center,
|
onTap: onPressed,
|
||||||
decoration: BoxDecoration(
|
child: Container(
|
||||||
border: Border.all(color: SacredColors.outlineVariant.withValues(alpha: 0.4)),
|
width: 42 * s,
|
||||||
borderRadius: BorderRadius.circular(SacredRadii.sm),
|
height: 38 * s,
|
||||||
color: SacredColors.surfaceContainerHighest,
|
alignment: Alignment.center,
|
||||||
),
|
decoration: BoxDecoration(
|
||||||
child: Text(
|
border: Border.all(color: SacredColors.outlineVariant.withValues(alpha: 0.4)),
|
||||||
label,
|
borderRadius: BorderRadius.circular(SacredRadii.sm),
|
||||||
style: GoogleFonts.manrope(
|
color: SacredColors.surfaceContainerHighest,
|
||||||
fontSize: 15 * s,
|
),
|
||||||
fontWeight: FontWeight.w700,
|
child: Text(
|
||||||
color: SacredColors.onSurface,
|
label,
|
||||||
|
style: GoogleFonts.manrope(
|
||||||
|
fontSize: 15 * s,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: SacredColors.onSurface,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -2255,30 +2416,34 @@ class _AdminScreenState extends ConsumerState<AdminScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _simulasiCard({required double s, required String title, required dynamic icon, required String desc, required VoidCallback onTap}) {
|
Widget _simulasiCard({required double s, required String title, required dynamic icon, required String desc, required VoidCallback onTap}) {
|
||||||
return InkWell(
|
return _tvFocusable(
|
||||||
onTap: onTap,
|
s: s,
|
||||||
borderRadius: BorderRadius.circular(SacredRadii.lg),
|
radius: SacredRadii.lg,
|
||||||
child: Container(
|
child: InkWell(
|
||||||
width: 320 * s,
|
onTap: onTap,
|
||||||
padding: EdgeInsets.all(24 * s),
|
borderRadius: BorderRadius.circular(SacredRadii.lg),
|
||||||
decoration: BoxDecoration(
|
child: Container(
|
||||||
color: SacredColors.surfaceContainerLowest,
|
width: 320 * s,
|
||||||
borderRadius: BorderRadius.circular(SacredRadii.lg),
|
padding: EdgeInsets.all(24 * s),
|
||||||
border: Border.all(color: SacredColors.outlineVariant.withValues(alpha: 0.5)),
|
decoration: BoxDecoration(
|
||||||
boxShadow: [
|
color: SacredColors.surfaceContainerLowest,
|
||||||
BoxShadow(color: Colors.black.withValues(alpha: 0.05), blurRadius: 10 * s, offset: Offset(0, 4 * s)),
|
borderRadius: BorderRadius.circular(SacredRadii.lg),
|
||||||
],
|
border: Border.all(color: SacredColors.outlineVariant.withValues(alpha: 0.5)),
|
||||||
),
|
boxShadow: [
|
||||||
child: Column(
|
BoxShadow(color: Colors.black.withValues(alpha: 0.05), blurRadius: 10 * s, offset: Offset(0, 4 * s)),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
],
|
||||||
mainAxisSize: MainAxisSize.min,
|
),
|
||||||
children: [
|
child: Column(
|
||||||
HugeIcon(icon: icon, color: SacredColors.primary, size: 40 * s),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
SizedBox(height: 16 * s),
|
mainAxisSize: MainAxisSize.min,
|
||||||
Text(title, style: GoogleFonts.plusJakartaSans(fontSize: 20 * s, fontWeight: FontWeight.bold, color: SacredColors.onSurface)),
|
children: [
|
||||||
SizedBox(height: 8 * s),
|
HugeIcon(icon: icon, color: SacredColors.primary, size: 40 * s),
|
||||||
Text(desc, style: GoogleFonts.manrope(fontSize: 14 * s, color: SacredColors.onSurfaceVariant)),
|
SizedBox(height: 16 * s),
|
||||||
],
|
Text(title, style: GoogleFonts.plusJakartaSans(fontSize: 20 * s, fontWeight: FontWeight.bold, color: SacredColors.onSurface)),
|
||||||
|
SizedBox(height: 8 * s),
|
||||||
|
Text(desc, style: GoogleFonts.manrope(fontSize: 14 * s, color: SacredColors.onSurfaceVariant)),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -2358,6 +2523,67 @@ class _NavButton extends StatefulWidget {
|
|||||||
State<_NavButton> createState() => _NavButtonState();
|
State<_NavButton> createState() => _NavButtonState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _TvFocusFrame extends StatefulWidget {
|
||||||
|
final Widget child;
|
||||||
|
final double scale;
|
||||||
|
final BorderRadius borderRadius;
|
||||||
|
|
||||||
|
const _TvFocusFrame({
|
||||||
|
required this.child,
|
||||||
|
required this.scale,
|
||||||
|
required this.borderRadius,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_TvFocusFrame> createState() => _TvFocusFrameState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TvFocusFrameState extends State<_TvFocusFrame> {
|
||||||
|
bool _hasFocus = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final s = widget.scale;
|
||||||
|
|
||||||
|
return Focus(
|
||||||
|
canRequestFocus: false,
|
||||||
|
descendantsAreFocusable: true,
|
||||||
|
onFocusChange: (value) {
|
||||||
|
if (_hasFocus != value) {
|
||||||
|
setState(() => _hasFocus = value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 140),
|
||||||
|
curve: Curves.easeOutCubic,
|
||||||
|
padding: EdgeInsets.all(_hasFocus ? 4 * s : 0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: _hasFocus
|
||||||
|
? SacredColors.primary.withValues(alpha: 0.08)
|
||||||
|
: Colors.transparent,
|
||||||
|
borderRadius: widget.borderRadius,
|
||||||
|
border: Border.all(
|
||||||
|
color: _hasFocus
|
||||||
|
? SacredColors.primary.withValues(alpha: 0.7)
|
||||||
|
: Colors.transparent,
|
||||||
|
width: _hasFocus ? 2 : 0,
|
||||||
|
),
|
||||||
|
boxShadow: _hasFocus
|
||||||
|
? [
|
||||||
|
BoxShadow(
|
||||||
|
color: SacredColors.primary.withValues(alpha: 0.18),
|
||||||
|
blurRadius: 18 * s,
|
||||||
|
spreadRadius: 1 * s,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
child: widget.child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _NavButtonState extends State<_NavButton> {
|
class _NavButtonState extends State<_NavButton> {
|
||||||
bool _isFocused = false;
|
bool _isFocused = false;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user