refactor ui
All checks were successful
BlockStorage/repertory/pipeline/head This commit looks good

This commit is contained in:
2025-09-05 10:11:57 -05:00
parent f5df53f781
commit 47cac7e71c
11 changed files with 479 additions and 441 deletions

View File

@@ -1,17 +1,12 @@
// helpers.dart
import 'dart:ui';
import 'package:convert/convert.dart';
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:repertory/constants.dart' as constants;
import 'package:repertory/models/auth.dart';
import 'package:repertory/models/settings.dart';
import 'package:repertory/widgets/app_dropdown.dart';
import 'package:repertory/widgets/aurora_sweep.dart';
import 'package:sodium_libs/sodium_libs.dart' show SecureKey, StringX;
Future doShowDialog(BuildContext context, Widget child) => showDialog(
@@ -419,7 +414,7 @@ Future<String?> editMountLocation(
controller: controller,
onChanged: (value) => setState(() => currentLocation = value),
)
: AppDropdownFormField<String>(
: AppDropdown<String>(
labelOf: (s) => s,
labelText: "Select drive",
onChanged: (value) => setState(() => currentLocation = value),
@@ -434,168 +429,6 @@ Future<String?> editMountLocation(
);
}
Scaffold createCommonScaffold(
BuildContext context,
String title,
List<Widget> children, {
Widget? advancedWidget,
Widget? floatingActionButton,
bool showBack = false,
}) {
final scheme = Theme.of(context).colorScheme;
final textTheme = Theme.of(context).textTheme;
return Scaffold(
body: SafeArea(
child: Stack(
children: [
Container(
width: double.infinity,
height: double.infinity,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: constants.gradientColors,
),
),
),
Consumer<Settings>(
builder: (_, settings, _) =>
AuroraSweep(enabled: settings.enableAnimations),
),
Positioned.fill(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 6, sigmaY: 6),
child: Container(color: Colors.black.withValues(alpha: 0.06)),
),
),
Padding(
padding: const EdgeInsets.all(constants.padding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [
if (!showBack) ...[
SizedBox(
width: 40,
height: 40,
child: Image.asset(
'assets/images/repertory.png',
fit: BoxFit.contain,
errorBuilder: (_, _, _) {
return Icon(
Icons.folder,
color: scheme.primary,
size: 32,
);
},
),
),
const SizedBox(width: constants.padding),
],
if (showBack) ...[
Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(
constants.borderRadius,
),
onTap: () => Navigator.of(context).pop(),
child: Ink(
width: 40,
height: 40,
decoration: BoxDecoration(
color: scheme.surface.withValues(alpha: 0.40),
borderRadius: BorderRadius.circular(
constants.borderRadius,
),
border: Border.all(
color: scheme.outlineVariant.withValues(
alpha: 0.08,
),
width: 1,
),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.22),
blurRadius: constants.borderRadius,
offset: Offset(0, constants.borderRadius),
),
],
),
child: const Icon(Icons.arrow_back),
),
),
),
const SizedBox(width: constants.padding),
],
Expanded(
child: Text(
title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.w700,
letterSpacing: 0.2,
color: scheme.onSurface.withValues(alpha: 0.96),
),
),
),
const SizedBox(width: constants.padding),
if (!showBack) ...[
const Text("Auto-start"),
Consumer<Settings>(
builder: (context, settings, _) {
return IconButton(
icon: Icon(
settings.autoStart
? Icons.toggle_on
: Icons.toggle_off,
),
color: settings.autoStart
? scheme.primary
: scheme.onSurface.withValues(alpha: 0.70),
onPressed: () =>
settings.setAutoStart(!settings.autoStart),
);
},
),
IconButton(
tooltip: 'Settings',
icon: const Icon(Icons.settings),
onPressed: () {
Navigator.pushNamed(context, '/settings');
},
),
const SizedBox(width: constants.padding),
],
if (showBack && advancedWidget != null) ...[
advancedWidget,
const SizedBox(width: constants.padding),
],
Consumer<Auth>(
builder: (context, auth, _) => IconButton(
tooltip: 'Log out',
icon: const Icon(Icons.logout),
onPressed: auth.logoff,
),
),
],
),
const SizedBox(height: constants.padding),
...children,
],
),
),
],
),
),
floatingActionButton: floatingActionButton,
);
}
InputDecoration createCommonDecoration(
ColorScheme colorScheme,
String label, {

View File

@@ -11,6 +11,7 @@ import 'package:repertory/models/mount_list.dart';
import 'package:repertory/types/mount_config.dart';
import 'package:repertory/utils/safe_set_state_mixin.dart';
import 'package:repertory/widgets/app_dropdown.dart';
import 'package:repertory/widgets/app_scaffold.dart';
import 'package:repertory/widgets/mount_settings.dart';
class AddMountScreen extends StatefulWidget {
@@ -45,148 +46,158 @@ class _AddMountScreenState extends State<AddMountScreen>
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return createCommonScaffold(context, widget.title, [
AppDropdownFormField<String>(
constrainToIntrinsic: true,
isExpanded: false,
labelOf: (s) => s,
labelText: 'Provider Type',
onChanged: (mountType) {
_handleChange(
Provider.of<Auth>(context, listen: false),
mountType ?? '',
);
},
prefixIcon: Icons.miscellaneous_services,
value: _mountType.isEmpty ? null : _mountType,
values: constants.providerTypeList,
widthMultiplier: 2.0,
),
if (_mountType.isNotEmpty && _mountType != 'Remote') ...[
const SizedBox(height: constants.padding),
TextField(
autofocus: true,
controller: _mountNameController,
keyboardType: TextInputType.text,
inputFormatters: [FilteringTextInputFormatter.deny(RegExp(r'\s'))],
onChanged: (_) => _handleChange(
Provider.of<Auth>(context, listen: false),
_mountType,
),
decoration: createCommonDecoration(
scheme,
'Configuration Name',
hintText: 'Enter a unique name',
icon: Icons.drive_file_rename_outline,
),
return AppScaffold(
title: widget.title,
showBack: true,
children: [
AppDropdown<String>(
constrainToIntrinsic: true,
isExpanded: false,
labelOf: (s) => s,
labelText: 'Provider Type',
onChanged: (mountType) {
_handleChange(
Provider.of<Auth>(context, listen: false),
mountType ?? '',
);
},
prefixIcon: Icons.miscellaneous_services,
value: _mountType.isEmpty ? null : _mountType,
values: constants.providerTypeList,
widthMultiplier: 2.0,
),
],
if (_mount != null) ...[
const SizedBox(height: constants.padding),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: constants.padding),
child: ClipRRect(
borderRadius: BorderRadius.circular(constants.borderRadius),
child: MountSettingsWidget(
isAdd: true,
mount: _mount!,
settings: _settings[_mountType]!,
showAdvanced: false,
if (_mountType.isNotEmpty && _mountType != 'Remote') ...[
const SizedBox(height: constants.padding),
TextField(
autofocus: true,
controller: _mountNameController,
keyboardType: TextInputType.text,
inputFormatters: [FilteringTextInputFormatter.deny(RegExp(r'\s'))],
onChanged: (_) => _handleChange(
Provider.of<Auth>(context, listen: false),
_mountType,
),
decoration: createCommonDecoration(
scheme,
'Configuration Name',
hintText: 'Enter a unique name',
icon: Icons.drive_file_rename_outline,
),
),
],
if (_mount != null) ...[
const SizedBox(height: constants.padding),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: constants.padding,
),
child: ClipRRect(
borderRadius: BorderRadius.circular(constants.borderRadius),
child: MountSettingsWidget(
isAdd: true,
mount: _mount!,
settings: _settings[_mountType]!,
showAdvanced: false,
),
),
),
),
),
const SizedBox(height: constants.padding),
Row(
children: [
IntrinsicWidth(
child: ElevatedButton.icon(
label: const Text('Test'),
icon: const Icon(Icons.check),
style: ElevatedButton.styleFrom(
backgroundColor: scheme.primary.withValues(alpha: 0.18),
foregroundColor: scheme.primary,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(constants.borderRadius),
side: BorderSide(
color: scheme.outlineVariant.withValues(alpha: 0.15),
width: 1,
const SizedBox(height: constants.padding),
Row(
children: [
IntrinsicWidth(
child: ElevatedButton.icon(
label: const Text('Test'),
icon: const Icon(Icons.check),
style: ElevatedButton.styleFrom(
backgroundColor: scheme.primary.withValues(alpha: 0.18),
foregroundColor: scheme.primary,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
constants.borderRadius,
),
side: BorderSide(
color: scheme.outlineVariant.withValues(alpha: 0.15),
width: 1,
),
),
),
onPressed: _handleProviderTest,
),
onPressed: _handleProviderTest,
),
),
const SizedBox(width: constants.padding),
IntrinsicWidth(
child: ElevatedButton.icon(
label: const Text('Add'),
icon: const Icon(Icons.add),
style: ElevatedButton.styleFrom(
backgroundColor: scheme.primary,
foregroundColor: scheme.onPrimary,
elevation: 8,
shadowColor: scheme.primary.withValues(alpha: 0.45),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(constants.borderRadius),
const SizedBox(width: constants.padding),
IntrinsicWidth(
child: ElevatedButton.icon(
label: const Text('Add'),
icon: const Icon(Icons.add),
style: ElevatedButton.styleFrom(
backgroundColor: scheme.primary,
foregroundColor: scheme.onPrimary,
elevation: 8,
shadowColor: scheme.primary.withValues(alpha: 0.45),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
constants.borderRadius,
),
),
),
),
onPressed: () async {
final mountList = Provider.of<MountList>(
context,
listen: false,
);
List<String> failed = [];
if (!validateSettings(_settings[_mountType]!, failed)) {
for (var key in failed) {
displayErrorMessage(
context,
"Setting '$key' is not valid",
);
}
return;
}
if (mountList.hasConfigName(_mountNameController.text)) {
return displayErrorMessage(
onPressed: () async {
final mountList = Provider.of<MountList>(
context,
"Configuration name '${_mountNameController.text}' already exists",
listen: false,
);
}
if (_mountType == "Sia" || _mountType == "S3") {
final bucket =
_settings[_mountType]!["${_mountType}Config"]["Bucket"]
as String;
if (mountList.hasBucketName(_mountType, bucket)) {
List<String> failed = [];
if (!validateSettings(_settings[_mountType]!, failed)) {
for (var key in failed) {
displayErrorMessage(
context,
"Setting '$key' is not valid",
);
}
return;
}
if (mountList.hasConfigName(_mountNameController.text)) {
return displayErrorMessage(
context,
"Bucket '$bucket' already exists",
"Configuration name '${_mountNameController.text}' already exists",
);
}
}
final success = await mountList.add(
_mountType,
_mountType == 'Remote'
? '${_settings[_mountType]!['RemoteConfig']['HostNameOrIp']}_${_settings[_mountType]!['RemoteConfig']['ApiPort']}'
: _mountNameController.text,
_settings[_mountType]!,
);
if (_mountType == "Sia" || _mountType == "S3") {
final bucket =
_settings[_mountType]!["${_mountType}Config"]["Bucket"]
as String;
if (mountList.hasBucketName(_mountType, bucket)) {
return displayErrorMessage(
context,
"Bucket '$bucket' already exists",
);
}
}
if (!success || !context.mounted) return;
final success = await mountList.add(
_mountType,
_mountType == 'Remote'
? '${_settings[_mountType]!['RemoteConfig']['HostNameOrIp']}_${_settings[_mountType]!['RemoteConfig']['ApiPort']}'
: _mountNameController.text,
_settings[_mountType]!,
);
Navigator.pop(context);
},
if (!success || !context.mounted) return;
Navigator.pop(context);
},
),
),
),
],
),
],
),
],
],
], showBack: true);
);
}
void _handleChange(Auth auth, String mountType) {

View File

@@ -3,9 +3,9 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:repertory/constants.dart' as constants;
import 'package:repertory/helpers.dart';
import 'package:repertory/models/mount.dart';
import 'package:repertory/utils/safe_set_state_mixin.dart';
import 'package:repertory/widgets/app_scaffold.dart';
import 'package:repertory/widgets/mount_settings.dart';
class EditMountScreen extends StatefulWidget {
@@ -25,27 +25,8 @@ class _EditMountScreenState extends State<EditMountScreen>
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
final textTheme = Theme.of(context).textTheme;
return createCommonScaffold(
context,
widget.title,
[
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: constants.padding),
child: ClipRRect(
borderRadius: BorderRadius.circular(constants.borderRadius),
child: MountSettingsWidget(
mount: widget.mount,
settings: jsonDecode(
jsonEncode(widget.mount.mountConfig.settings),
),
showAdvanced: _showAdvanced,
),
),
),
),
const SizedBox(height: constants.padding),
],
return AppScaffold(
title: widget.title,
showBack: true,
advancedWidget: Row(
mainAxisSize: MainAxisSize.min,
@@ -67,6 +48,24 @@ class _EditMountScreenState extends State<EditMountScreen>
),
],
),
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: constants.padding),
child: ClipRRect(
borderRadius: BorderRadius.circular(constants.borderRadius),
child: MountSettingsWidget(
mount: widget.mount,
settings: jsonDecode(
jsonEncode(widget.mount.mountConfig.settings),
),
showAdvanced: _showAdvanced,
),
),
),
),
const SizedBox(height: constants.padding),
],
);
}
}

View File

@@ -9,6 +9,7 @@ import 'package:repertory/constants.dart' as constants;
import 'package:repertory/helpers.dart';
import 'package:repertory/models/auth.dart';
import 'package:repertory/utils/safe_set_state_mixin.dart';
import 'package:repertory/widgets/app_scaffold.dart';
import 'package:repertory/widgets/ui_settings.dart';
class EditSettingsScreen extends StatefulWidget {
@@ -23,32 +24,36 @@ class _EditSettingsScreenState extends State<EditSettingsScreen>
with SafeSetState<EditSettingsScreen> {
@override
Widget build(BuildContext context) {
return createCommonScaffold(context, widget.title, [
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: constants.padding),
child: ClipRRect(
borderRadius: BorderRadius.circular(constants.borderRadius),
child: FutureBuilder<Map<String, dynamic>>(
future: _grabSettings(),
initialData: const <String, dynamic>{},
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
return AppScaffold(
title: widget.title,
showBack: true,
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: constants.padding),
child: ClipRRect(
borderRadius: BorderRadius.circular(constants.borderRadius),
child: FutureBuilder<Map<String, dynamic>>(
future: _grabSettings(),
initialData: const <String, dynamic>{},
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
return UISettingsWidget(
origSettings: jsonDecode(jsonEncode(snapshot.requireData)),
settings: snapshot.requireData,
showAdvanced: false,
);
},
return UISettingsWidget(
origSettings: jsonDecode(jsonEncode(snapshot.requireData)),
settings: snapshot.requireData,
showAdvanced: false,
);
},
),
),
),
),
),
const SizedBox(height: constants.padding),
], showBack: true);
const SizedBox(height: constants.padding),
],
);
}
Future<Map<String, dynamic>> _grabSettings() async {

View File

@@ -2,7 +2,7 @@
import 'package:flutter/material.dart';
import 'package:repertory/constants.dart' as constants;
import 'package:repertory/helpers.dart';
import 'package:repertory/widgets/app_scaffold.dart';
import 'package:repertory/widgets/mount_list_widget.dart';
class HomeScreen extends StatefulWidget {
@@ -18,17 +18,8 @@ class _HomeScreeState extends State<HomeScreen> {
Widget build(context) {
final scheme = Theme.of(context).colorScheme;
return createCommonScaffold(
context,
widget.title,
[
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: constants.padding),
child: const MountListWidget(),
),
),
],
return AppScaffold(
title: widget.title,
floatingActionButton: Padding(
padding: const EdgeInsets.all(constants.padding),
child: Hero(
@@ -74,6 +65,14 @@ class _HomeScreeState extends State<HomeScreen> {
),
),
),
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: constants.padding),
child: const MountListWidget(),
),
),
],
);
}
}

View File

@@ -57,7 +57,7 @@ void createIntListSetting(
SettingsTile.navigation(
title: createSettingTitle(context, key, description),
leading: Icon(icon),
value: AppDropdownFormField<String>(
value: AppDropdown<String>(
labelOf: (s) => s,
constrainToIntrinsic: true,
onChanged: (newValue) {
@@ -310,7 +310,7 @@ void createStringListSetting(
SettingsTile.navigation(
title: createSettingTitle(context, key, description),
leading: Icon(icon),
value: AppDropdownFormField<String>(
value: AppDropdown<String>(
constrainToIntrinsic: true,
labelOf: (s) => s,
onChanged: (newValue) => setState(() => settings[key] = newValue),

View File

@@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
import 'package:repertory/constants.dart' as constants;
import 'package:repertory/helpers.dart';
class AppDropdownFormField<T> extends StatelessWidget {
const AppDropdownFormField({
class AppDropdown<T> extends StatelessWidget {
const AppDropdown({
super.key,
required this.labelOf,
required this.values,

View File

@@ -0,0 +1,60 @@
// app_icon_button_framed.dart
import 'package:flutter/material.dart';
import 'package:repertory/constants.dart' as constants;
class AppIconButtonFramed extends StatelessWidget {
final IconData icon;
final VoidCallback? onTap;
final Color? iconColor;
const AppIconButtonFramed({
super.key,
required this.icon,
this.onTap,
this.iconColor,
});
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
final radius = BorderRadius.circular(constants.borderRadiusSmall);
return Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
borderRadius: radius,
child: Ink(
width: 46,
height: 46,
decoration: BoxDecoration(
color: scheme.primary.withValues(alpha: 0.12),
borderRadius: radius,
border: Border.all(
color: scheme.outlineVariant.withValues(alpha: 0.08),
width: 1,
),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.24),
blurRadius: constants.borderRadiusSmall / 2.0,
offset: const Offset(0, 5),
),
],
),
child: Center(
child: Transform.scale(
scale: 0.90,
child: Icon(
icon,
color: iconColor ?? scheme.onSurface.withValues(alpha: 0.92),
size: 32.0,
),
),
),
),
),
);
}
}

View File

@@ -0,0 +1,181 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:repertory/constants.dart' as constants;
import 'package:repertory/models/auth.dart';
import 'package:repertory/models/settings.dart';
import 'package:repertory/widgets/aurora_sweep.dart';
class AppScaffold extends StatelessWidget {
const AppScaffold({
super.key,
required this.children,
required this.title,
this.advancedWidget,
this.floatingActionButton,
this.showBack = false,
});
final List<Widget> children;
final String title;
final Widget? advancedWidget;
final Widget? floatingActionButton;
final bool showBack;
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
final textTheme = Theme.of(context).textTheme;
return Scaffold(
body: SafeArea(
child: Stack(
children: [
Container(
width: double.infinity,
height: double.infinity,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: constants.gradientColors,
),
),
),
Consumer<Settings>(
builder: (_, settings, _) =>
AuroraSweep(enabled: settings.enableAnimations),
),
Positioned.fill(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 6, sigmaY: 6),
child: Container(color: Colors.black.withValues(alpha: 0.06)),
),
),
Padding(
padding: const EdgeInsets.all(constants.padding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [
if (!showBack) ...[
SizedBox(
width: 40,
height: 40,
child: Image.asset(
'assets/images/repertory.png',
fit: BoxFit.contain,
errorBuilder: (_, _, _) {
return Icon(
Icons.folder,
color: scheme.primary,
size: 32,
);
},
),
),
const SizedBox(width: constants.padding),
],
if (showBack) ...[
Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(
constants.borderRadius,
),
onTap: () => Navigator.of(context).pop(),
child: Ink(
width: 40,
height: 40,
decoration: BoxDecoration(
color: scheme.surface.withValues(alpha: 0.40),
borderRadius: BorderRadius.circular(
constants.borderRadius,
),
border: Border.all(
color: scheme.outlineVariant.withValues(
alpha: 0.08,
),
width: 1,
),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.22),
blurRadius: constants.borderRadius,
offset: Offset(0, constants.borderRadius),
),
],
),
child: const Icon(Icons.arrow_back),
),
),
),
const SizedBox(width: constants.padding),
],
Expanded(
child: Text(
title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.w700,
letterSpacing: 0.2,
color: scheme.onSurface.withValues(alpha: 0.96),
),
),
),
const SizedBox(width: constants.padding),
if (!showBack) ...[
const Text("Auto-start"),
Consumer<Settings>(
builder: (context, settings, _) {
return IconButton(
icon: Icon(
settings.autoStart
? Icons.toggle_on
: Icons.toggle_off,
),
color: settings.autoStart
? scheme.primary
: scheme.onSurface.withValues(alpha: 0.70),
onPressed: () =>
settings.setAutoStart(!settings.autoStart),
);
},
),
IconButton(
tooltip: 'Settings',
icon: const Icon(Icons.settings),
onPressed: () {
Navigator.pushNamed(context, '/settings');
},
),
const SizedBox(width: constants.padding),
],
if (showBack && advancedWidget != null) ...[
advancedWidget!,
const SizedBox(width: constants.padding),
],
Consumer<Auth>(
builder: (context, auth, _) => IconButton(
tooltip: 'Log out',
icon: const Icon(Icons.logout),
onPressed: auth.logoff,
),
),
],
),
const SizedBox(height: constants.padding),
...children,
],
),
),
],
),
),
floatingActionButton: floatingActionButton,
);
}
}

View File

@@ -0,0 +1,37 @@
// app_toggle_button_framed.dart
import 'package:flutter/material.dart';
import 'package:repertory/widgets/app_icon_button_framed.dart';
class AppToggleButtonFramed extends StatelessWidget {
final bool? mounted;
final VoidCallback? onPressed;
const AppToggleButtonFramed({
super.key,
required this.mounted,
required this.onPressed,
});
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
final bool isOn = mounted ?? false;
IconData icon = Icons.hourglass_top;
Color iconColor = scheme.onSurface.withValues(alpha: 0.60);
if (mounted != null) {
icon = isOn ? Icons.toggle_on : Icons.toggle_off;
iconColor = isOn
? scheme.primary
: scheme.onSurface.withValues(alpha: 0.55);
}
return AppIconButtonFramed(
icon: icon,
iconColor: iconColor,
onTap: mounted == null ? null : onPressed,
);
}
}

View File

@@ -7,6 +7,8 @@ import 'package:repertory/constants.dart' as constants;
import 'package:repertory/helpers.dart';
import 'package:repertory/models/mount.dart';
import 'package:repertory/utils/safe_set_state_mixin.dart';
import 'package:repertory/widgets/app_icon_button_framed.dart';
import 'package:repertory/widgets/app_toggle_button_framed.dart';
class MountWidget extends StatefulWidget {
const MountWidget({super.key});
@@ -82,7 +84,8 @@ class _MountWidgetState extends State<MountWidget>
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_GearBadge(
AppIconButtonFramed(
icon: Icons.settings,
onTap: () {
Navigator.pushNamed(
context,
@@ -107,7 +110,7 @@ class _MountWidgetState extends State<MountWidget>
],
),
),
_ToggleFramed(
AppToggleButtonFramed(
mounted: mount.mounted,
onPressed: _createMountHandler(context, mount),
),
@@ -281,96 +284,6 @@ class _MountWidgetState extends State<MountWidget>
}
}
class _FramedBox extends StatelessWidget {
final IconData icon;
final VoidCallback? onTap;
final Color? iconColor;
const _FramedBox({required this.icon, this.onTap, this.iconColor});
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
final radius = BorderRadius.circular(constants.borderRadiusSmall);
return Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
borderRadius: radius,
child: Ink(
width: 46,
height: 46,
decoration: BoxDecoration(
color: scheme.primary.withValues(alpha: 0.12),
borderRadius: radius,
border: Border.all(
color: scheme.outlineVariant.withValues(alpha: 0.08),
width: 1,
),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.24),
blurRadius: constants.borderRadiusSmall / 2.0,
offset: const Offset(0, 5),
),
],
),
child: Center(
child: Transform.scale(
scale: 0.90,
child: Icon(
icon,
color: iconColor ?? scheme.onSurface.withValues(alpha: 0.92),
size: 32.0,
),
),
),
),
),
);
}
}
class _GearBadge extends StatelessWidget {
final VoidCallback onTap;
const _GearBadge({required this.onTap});
@override
Widget build(BuildContext context) {
return _FramedBox(icon: Icons.settings, onTap: onTap);
}
}
class _ToggleFramed extends StatelessWidget {
final bool? mounted;
final VoidCallback? onPressed;
const _ToggleFramed({required this.mounted, required this.onPressed});
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
final bool isOn = mounted ?? false;
IconData icon = Icons.hourglass_top;
Color iconColor = scheme.onSurface.withValues(alpha: 0.60);
if (mounted != null) {
icon = isOn ? Icons.toggle_on : Icons.toggle_off;
iconColor = isOn
? scheme.primary
: scheme.onSurface.withValues(alpha: 0.55);
}
return _FramedBox(
icon: icon,
iconColor: iconColor,
onTap: mounted == null ? null : onPressed,
);
}
}
class _EditPathButton extends StatelessWidget {
final bool enabled;
final VoidCallback onPressed;