This commit is contained in:
@@ -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, {
|
||||
|
@@ -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) {
|
||||
|
@@ -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),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -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(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -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),
|
||||
|
@@ -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,
|
||||
|
60
web/repertory/lib/widgets/app_icon_button_framed.dart
Normal file
60
web/repertory/lib/widgets/app_icon_button_framed.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
181
web/repertory/lib/widgets/app_scaffold.dart
Normal file
181
web/repertory/lib/widgets/app_scaffold.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
37
web/repertory/lib/widgets/app_toggle_button_framed.dart
Normal file
37
web/repertory/lib/widgets/app_toggle_button_framed.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
@@ -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;
|
||||
|
Reference in New Issue
Block a user