Files
repertory/web/repertory/lib/widgets/mount_widget.dart

289 lines
9.4 KiB
Dart

// mount_widget.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:provider/provider.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_outlined_icon_button.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});
@override
State<MountWidget> createState() => _MountWidgetState();
}
class _MountWidgetState extends State<MountWidget>
with SafeSetState<MountWidget> {
bool _enabled = true;
bool _editEnabled = true;
Timer? _timer;
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
final textTheme = Theme.of(context).textTheme;
final titleStyle = textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w700,
color: scheme.onSurface,
);
final subStyle = textTheme.bodyMedium?.copyWith(color: scheme.onSurface);
final visualDensity = VisualDensity(
horizontal: -VisualDensity.maximumDensity,
vertical: -(VisualDensity.maximumDensity * 2.0),
);
return ConstrainedBox(
constraints: const BoxConstraints(minHeight: 120),
child: Card(
margin: const EdgeInsets.all(0.0),
elevation: 12,
color: scheme.primary.withValues(alpha: constants.primaryAlpha),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(constants.borderRadiusSmall),
side: BorderSide(
color: scheme.outlineVariant.withValues(
alpha: constants.outlineAlpha,
),
width: 1,
),
),
child: Padding(
padding: const EdgeInsets.all(constants.padding),
child: Consumer<Mount>(
builder: (context, Mount mount, _) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
AppIconButtonFramed(
icon: Icons.settings,
onTap: () {
Navigator.pushNamed(
context,
'/edit',
arguments: mount,
);
},
),
const SizedBox(width: constants.padding),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SelectableText(mount.provider, style: titleStyle),
SelectableText(
'Name • ${formatMountName(mount.type, mount.name)}',
style: subStyle,
),
],
),
),
AppToggleButtonFramed(
mounted: mount.mounted,
onPressed: _createMountHandler(context, mount),
),
],
),
const SizedBox(height: constants.padding),
Row(
children: [
AppOutlinedIconButton(
text: 'Edit path',
icon: Icons.edit,
enabled: mount.mounted == false,
onPressed: () async {
if (!_editEnabled) {
return;
}
setState(() {
_editEnabled = false;
});
final available = await mount.getAvailableLocations();
if (!mounted) {
setState(() {
_editEnabled = true;
});
return;
}
if (!context.mounted) {
return;
}
final location = await editMountLocation(
context,
available,
location: mount.path,
);
if (location != null) {
await mount.setMountLocation(location);
}
if (mounted) {
setState(() {
_editEnabled = true;
});
}
},
),
const SizedBox(width: constants.padding),
Expanded(
child: SelectableText(
_prettyPath(mount),
style: subStyle,
),
),
IntrinsicWidth(
child: Theme(
data: Theme.of(context).copyWith(
listTileTheme: const ListTileThemeData(
contentPadding: EdgeInsets.zero,
horizontalTitleGap: 0,
minLeadingWidth: 0,
minVerticalPadding: 0,
),
checkboxTheme: CheckboxThemeData(
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
visualDensity: visualDensity,
),
),
child: CheckboxListTile(
contentPadding: EdgeInsets.zero,
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
onChanged: (_) async {
return mount.setMountAutoStart(!mount.autoStart);
},
title: Row(
mainAxisSize: MainAxisSize.min,
children: const [
Icon(
Icons.auto_mode,
size: constants.smallIconSize,
),
SizedBox(width: constants.paddingSmall),
Text('Auto-mount'),
SizedBox(width: constants.paddingSmall),
],
),
value: mount.autoStart,
visualDensity: visualDensity,
),
),
),
],
),
],
);
},
),
),
),
);
}
String _prettyPath(Mount mount) {
if (mount.path.isEmpty && mount.mounted == null) {
return 'loading...';
}
if (mount.path.isEmpty) {
return '<mount location not set>';
}
return mount.path;
}
VoidCallback? _createMountHandler(BuildContext context, Mount mount) {
if (!(_enabled && mount.mounted != null)) {
return null;
}
return () async {
if (mount.mounted == null) {
return;
}
final mounted = mount.mounted!;
setState(() {
_enabled = false;
});
final location = await _getMountLocation(context, mount);
void cleanup() {
setState(() {
_enabled = true;
});
}
if (!mounted && location == null) {
if (!context.mounted) {
return;
}
displayErrorMessage(context, 'Mount location is not set');
cleanup();
return;
}
final success = await mount.mount(mounted, location: location);
if (success ||
mounted ||
constants.navigatorKey.currentContext == null ||
!constants.navigatorKey.currentContext!.mounted) {
cleanup();
return;
}
displayErrorMessage(
context,
'Mount location is not available: $location',
);
cleanup();
};
}
Future<String?> _getMountLocation(BuildContext context, Mount mount) async {
if (mount.mounted ?? false) {
return null;
}
if (mount.path.isNotEmpty) {
return mount.path;
}
String? location = await mount.getMountLocation();
if (location != null) {
return location;
}
// ignore: use_build_context_synchronously
return editMountLocation(context, await mount.getAvailableLocations());
}
@override
void initState() {
super.initState();
_timer = Timer.periodic(const Duration(seconds: 5), (_) {
Provider.of<Mount>(context, listen: false).refresh();
});
}
@override
void dispose() {
_timer?.cancel();
_timer = null;
super.dispose();
}
}