diff --git a/web/repertory/.cspell/words.txt b/web/repertory/.cspell/words.txt index b93e72f2..ccea28d2 100644 --- a/web/repertory/.cspell/words.txt +++ b/web/repertory/.cspell/words.txt @@ -1,3 +1,5 @@ +auro +aurosweep autofocus autovalidatemode canvaskit diff --git a/web/repertory/.metadata b/web/repertory/.metadata index 4387f5e3..e2ff7d73 100644 --- a/web/repertory/.metadata +++ b/web/repertory/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "35c388afb57ef061d06a39b537336c87e0e3d1b1" + revision: "d7b523b356d15fb81e7d340bbe52b47f93937323" channel: "stable" project_type: app @@ -13,11 +13,11 @@ project_type: app migration: platforms: - platform: root - create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 - base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 + create_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 + base_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 - platform: web - create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 - base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 + create_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 + base_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 # User provided section diff --git a/web/repertory/lib/constants.dart b/web/repertory/lib/constants.dart index 006ee82a..d4196392 100644 --- a/web/repertory/lib/constants.dart +++ b/web/repertory/lib/constants.dart @@ -1,4 +1,6 @@ -import 'package:flutter/material.dart' show GlobalKey, NavigatorState; +// constants.dart + +import 'package:flutter/material.dart' show GlobalKey, NavigatorState, Color; import 'package:sodium_libs/sodium_libs.dart'; const addMountTitle = 'Add New Mount'; @@ -9,10 +11,14 @@ const logonWidth = 300.0; const databaseTypeList = ['rocksdb', 'sqlite']; const downloadTypeList = ['default', 'direct', 'ring_buffer']; const eventLevelList = ['critical', 'error', 'warn', 'info', 'debug', 'trace']; -const padding = 15.0; +const padding = 16.0; +const paddingSmall = 8.0; +const borderRadius = 16.0; +const borderRadiusSmall = 8.0; const protocolTypeList = ['http', 'https']; const providerTypeList = ['Encrypt', 'Remote', 'S3', 'Sia']; const ringBufferSizeList = ['128', '256', '512', '1024', '2048']; +const gradientColors = [Color(0xFF0A0F1F), Color(0xFF1B1C1F)]; final GlobalKey navigatorKey = GlobalKey(); diff --git a/web/repertory/lib/helpers.dart b/web/repertory/lib/helpers.dart index b9fc9249..2b2f5bf5 100644 --- a/web/repertory/lib/helpers.dart +++ b/web/repertory/lib/helpers.dart @@ -1,3 +1,5 @@ +// helpers.dart + import 'package:convert/convert.dart'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; @@ -348,55 +350,95 @@ Future editMountLocation( return await showDialog( context: context, builder: (context) { - return StatefulBuilder( - builder: (context, setState) { - return AlertDialog( - actions: [ - TextButton( - child: const Text('Cancel'), - onPressed: () => Navigator.of(context).pop(null), + var theme = Theme.of(context); + var scheme = theme.colorScheme; + return Theme( + data: theme.copyWith( + dialogTheme: DialogThemeData( + backgroundColor: scheme.surface.withValues(alpha: 0.40), + surfaceTintColor: Colors.transparent, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(constants.borderRadius), + side: BorderSide( + color: scheme.outlineVariant.withValues(alpha: 0.08), + width: 1, ), - TextButton( - child: const Text('OK'), - onPressed: () { - final result = getSettingValidators('Path').firstWhereOrNull( - (validator) => !validator(currentLocation ?? ''), - ); - if (result != null) { - return displayErrorMessage( - context, - "Mount location is not valid", - ); - } - Navigator.of(context).pop(currentLocation); - }, - ), - ], - content: - available.isEmpty - ? TextField( + ), + ), + ), + child: StatefulBuilder( + builder: (context, setState) { + return AlertDialog( + actions: [ + TextButton( + child: const Text('Cancel'), + onPressed: () => Navigator.of(context).pop(null), + ), + TextButton( + child: const Text('OK'), + onPressed: () { + final result = getSettingValidators('Path') + .firstWhereOrNull( + (validator) => !validator(currentLocation ?? ''), + ); + if (result != null) { + return displayErrorMessage( + context, + "Mount location is not valid", + ); + } + Navigator.of(context).pop(currentLocation); + }, + ), + ], + content: available.isEmpty + ? TextField( autofocus: true, controller: controller, - onChanged: - (value) => setState(() => currentLocation = value), + onChanged: (value) => + setState(() => currentLocation = value), ) - : DropdownButton( + : DropdownButton( hint: const Text("Select drive"), value: currentLocation, - onChanged: - (value) => setState(() => currentLocation = value), - items: - available.map>((item) { - return DropdownMenuItem( - value: item, - child: Text(item), - ); - }).toList(), + onChanged: (value) => + setState(() => currentLocation = value), + items: available.map>((item) { + return DropdownMenuItem( + value: item, + child: Text(item), + ); + }).toList(), ), - title: const Text('Mount Location', textAlign: TextAlign.center), - ); - }, + title: const Text('Mount Location', textAlign: TextAlign.center), + ); + }, + ), ); }, ); } + +Future doShowDialog(BuildContext context, Widget child) => showDialog( + context: context, + builder: (context) { + final theme = Theme.of(context); + final scheme = theme.colorScheme; + return Theme( + data: theme.copyWith( + dialogTheme: DialogThemeData( + backgroundColor: scheme.surface.withValues(alpha: 0.40), + surfaceTintColor: Colors.transparent, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(constants.borderRadius), + side: BorderSide( + color: scheme.outlineVariant.withValues(alpha: 0.08), + width: 1, + ), + ), + ), + ), + child: child, + ); + }, +); diff --git a/web/repertory/lib/main.dart b/web/repertory/lib/main.dart index ed724e9d..8cd7e6cf 100644 --- a/web/repertory/lib/main.dart +++ b/web/repertory/lib/main.dart @@ -1,3 +1,5 @@ +// main.dart + import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:repertory/constants.dart' as constants; @@ -123,7 +125,7 @@ class AuthCheck extends StatelessWidget { @override Widget build(BuildContext context) { return Consumer( - builder: (context, auth, __) { + builder: (context, auth, _) { if (!auth.authenticated) { Future.delayed(Duration(milliseconds: 1), () { if (constants.navigatorKey.currentContext == null) { diff --git a/web/repertory/lib/screens/add_mount_screen.dart b/web/repertory/lib/screens/add_mount_screen.dart index 63eb5313..0e43e5ae 100644 --- a/web/repertory/lib/screens/add_mount_screen.dart +++ b/web/repertory/lib/screens/add_mount_screen.dart @@ -1,3 +1,6 @@ +// add_mount_screen.dart + +import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; @@ -7,6 +10,7 @@ import 'package:repertory/models/auth.dart'; import 'package:repertory/models/mount.dart'; import 'package:repertory/models/mount_list.dart'; import 'package:repertory/types/mount_config.dart'; +import 'package:repertory/widgets/aurora_sweep.dart'; import 'package:repertory/widgets/mount_settings.dart'; class AddMountScreen extends StatefulWidget { @@ -30,48 +34,206 @@ class _AddMountScreenState extends State { "Sia": createDefaultSettings("Sia"), }; + @override + void dispose() { + _mountNameController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - title: Text(widget.title), - actions: [ - Consumer( - builder: (context, auth, _) { - return IconButton( - icon: const Icon(Icons.logout), - onPressed: () => auth.logoff(), - ); - }, + final scheme = Theme.of(context).colorScheme; + final textTheme = Theme.of(context).textTheme; + + Widget glassTile({required Widget child, EdgeInsets? padding}) { + return Container( + 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, ), - ], - ), - body: Padding( - padding: const EdgeInsets.all(constants.padding), - child: Consumer( - builder: (context, auth, _) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - children: [ - Card( - margin: EdgeInsets.all(0.0), - child: Padding( - padding: const EdgeInsets.all(constants.padding), + gradient: const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0x07FFFFFF), Color(0x00000000)], + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.22), + blurRadius: constants.borderRadius, + offset: Offset(0, constants.borderRadius), + ), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(constants.borderRadius), + child: Padding( + padding: padding ?? const EdgeInsets.all(constants.padding), + child: child, + ), + ), + ); + } + + return Scaffold( + body: SafeArea( + child: Stack( + children: [ + // Background + Container( + width: double.infinity, + height: double.infinity, + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: constants.gradientColors, + ), + ), + ), + const AuroraSweep( + enabled: true, + duration: Duration(seconds: 28), + primaryAlphaA: 0.04, + primaryAlphaB: 0.03, + ), + Positioned.fill( + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 6, sigmaY: 6), + child: Container(color: Colors.black.withValues(alpha: 0.06)), + ), + ), + + // Content + Padding( + padding: const EdgeInsets.all(constants.padding), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Header: Back • Title • Logout + Row( + children: [ + // Back tile + 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( + widget.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), + + // Logout capsule (glassy) + ClipRRect( + borderRadius: BorderRadius.circular( + constants.borderRadius, + ), + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: constants.borderRadius, + sigmaY: constants.borderRadius, + ), + child: Container( + height: 40, + padding: const EdgeInsets.symmetric(horizontal: 6), + 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, + ), + ), + child: Consumer( + builder: (context, auth, _) { + return IconButton( + tooltip: 'Log out', + icon: const Icon(Icons.logout), + onPressed: () { + auth.logoff(); + }, + ); + }, + ), + ), + ), + ), + ], + ), + + const SizedBox(height: constants.padding), + + // Provider Type (glassy tile) + glassTile( child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, children: [ - const Text('Provider Type'), + Text( + 'Provider Type', + style: textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w600, + ), + ), const SizedBox(width: constants.padding), DropdownButton( - autofocus: true, value: _mountType, - onChanged: (mountType) => - _handleChange(auth, mountType ?? ''), + autofocus: true, + underline: const SizedBox.shrink(), + onChanged: (mountType) { + _handleChange( + Provider.of(context, listen: false), + mountType ?? '', + ); + }, items: constants.providerTypeList .map>((item) { return DropdownMenuItem( @@ -84,21 +246,21 @@ class _AddMountScreenState extends State { ], ), ), - ), - if (_mountType.isNotEmpty && _mountType != 'Remote') - const SizedBox(height: constants.padding), - if (_mountType.isNotEmpty && _mountType != 'Remote') - Card( - margin: EdgeInsets.all(0.0), - child: Padding( - padding: const EdgeInsets.all(constants.padding), + + if (_mountType.isNotEmpty && _mountType != 'Remote') ...[ + const SizedBox(height: constants.padding), + // Config Name (glassy tile) + glassTile( child: Column( crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, children: [ - const Text('Configuration Name'), - const SizedBox(width: constants.padding), + Text( + 'Configuration Name', + style: textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 8), TextField( autofocus: true, controller: _mountNameController, @@ -106,99 +268,157 @@ class _AddMountScreenState extends State { inputFormatters: [ FilteringTextInputFormatter.deny(RegExp(r'\s')), ], - onChanged: (_) => _handleChange(auth, _mountType), + onChanged: (_) => _handleChange( + Provider.of(context, listen: false), + _mountType, + ), + decoration: InputDecoration( + hintText: 'Enter a unique name', + filled: true, + fillColor: scheme.surface.withValues(alpha: 0.30), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular( + constants.borderRadius, + ), + borderSide: BorderSide.none, + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 12, + ), + ), ), ], ), ), - ), - if (_mount != null) ...[ - const SizedBox(height: constants.padding), - Expanded( - child: Card( - margin: EdgeInsets.all(0.0), - child: Padding( - padding: const EdgeInsets.all(constants.padding), - child: MountSettingsWidget( - isAdd: true, - mount: _mount!, - settings: _settings[_mountType]!, - showAdvanced: false, + ], + + if (_mount != null) ...[ + const SizedBox(height: constants.padding), + // Settings (large glass container) + Expanded( + child: glassTile( + padding: EdgeInsets.zero, + child: Padding( + padding: const EdgeInsets.all(constants.padding), + child: MountSettingsWidget( + isAdd: true, + mount: _mount!, + settings: _settings[_mountType]!, + showAdvanced: false, + ), ), ), ), - ), - const SizedBox(height: constants.padding), - Row( - children: [ - ElevatedButton.icon( - label: const Text('Test'), - icon: const Icon(Icons.check), - onPressed: _handleProviderTest, - ), - const SizedBox(width: constants.padding), - ElevatedButton.icon( - label: const Text('Add'), - icon: const Icon(Icons.add), - onPressed: () async { - final mountList = Provider.of(context); + const SizedBox(height: constants.padding), - List failed = []; - if (!validateSettings( - _settings[_mountType]!, - failed, - )) { - for (var key in failed) { - displayErrorMessage( - context, - "Setting '$key' is not valid", - ); - } - return; - } + // Action buttons row + Row( + children: [ + // Test + 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, + ), + const SizedBox(width: constants.padding), - if (mountList.hasConfigName( - _mountNameController.text, - )) { - return displayErrorMessage( + // Add + 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( 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 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); + }, + ), + ], + ), + ], ], - ], - ); - }, + ), + ), + ], ), ), ); @@ -251,7 +471,6 @@ class _AddMountScreenState extends State { if (!mounted) { return; } - super.setState(fn); } } diff --git a/web/repertory/lib/screens/auth_screen.dart b/web/repertory/lib/screens/auth_screen.dart index 4ea5e33b..9ce13c4e 100644 --- a/web/repertory/lib/screens/auth_screen.dart +++ b/web/repertory/lib/screens/auth_screen.dart @@ -1,7 +1,11 @@ +// auth_screen.dart + +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/widgets/aurora_sweep.dart'; class AuthScreen extends StatefulWidget { final String title; @@ -27,30 +31,29 @@ class _AuthScreenState extends State { } @override - Widget build(context) { + Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; InputDecoration decoration(String label, IconData icon) => InputDecoration( labelText: label, prefixIcon: Icon(icon), filled: true, - fillColor: scheme.surfaceContainerLow.withValues(alpha: 0.65), + fillColor: scheme.primary.withValues(alpha: 0.15), border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(constants.borderRadiusSmall), borderSide: BorderSide.none, ), focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(constants.borderRadiusSmall), borderSide: BorderSide(color: scheme.primary, width: 2), ), - contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14), + contentPadding: const EdgeInsets.all(constants.paddingSmall), ); Future doLogin(Auth auth) async { if (!_enabled) { return; } - if (!_formKey.currentState!.validate()) { return; } @@ -89,22 +92,29 @@ class _AuthScreenState extends State { gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, - colors: [Color(0xFF0A0F1F), Color(0xFF1B1C1F)], + colors: constants.gradientColors, stops: [0.0, 1.0], ), ), ), + const AuroraSweep(), + Positioned.fill( + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), + child: Container(color: Colors.black.withValues(alpha: 0.10)), + ), + ), Align( - alignment: const Alignment(0, 0.1), + alignment: const Alignment(0, 0.06), child: IgnorePointer( child: Container( - width: 740, - height: 740, + width: 720, + height: 720, decoration: BoxDecoration( shape: BoxShape.circle, gradient: RadialGradient( colors: [ - scheme.primary.withValues(alpha: 0.22), + scheme.primary.withValues(alpha: 0.20), Colors.transparent, ], stops: const [0.0, 1.0], @@ -116,7 +126,7 @@ class _AuthScreenState extends State { Consumer( builder: (context, auth, _) { if (auth.authenticated) { - Future.delayed(const Duration(milliseconds: 1), () { + WidgetsBinding.instance.addPostFrameCallback((_) { if (constants.navigatorKey.currentContext == null) { return; } @@ -124,7 +134,6 @@ class _AuthScreenState extends State { constants.navigatorKey.currentContext!, ).pushNamedAndRemoveUntil('/', (r) => false); }); - return const SizedBox.shrink(); } @@ -142,13 +151,17 @@ class _AuthScreenState extends State { minWidth: 300, ), child: Card( - elevation: 12, - color: scheme.surface, + elevation: constants.padding, + color: scheme.primary.withValues(alpha: 0.10), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(18), + borderRadius: BorderRadius.circular( + constants.borderRadius, + ), ), child: Padding( - padding: const EdgeInsets.all(constants.padding), + padding: const EdgeInsets.all( + constants.padding * 2.0, + ), child: Form( key: _formKey, autovalidateMode: @@ -160,15 +173,28 @@ class _AuthScreenState extends State { Align( alignment: Alignment.center, child: Container( - width: 56, - height: 56, + width: 64, + height: 64, decoration: BoxDecoration( color: scheme.primary.withValues( - alpha: 0.18, + alpha: 0.11, ), - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular( + constants.borderRadiusSmall, + ), + boxShadow: [ + BoxShadow( + color: scheme.primary.withValues( + alpha: 0.08, + ), + blurRadius: 16, + spreadRadius: 1, + ), + ], + ), + padding: const EdgeInsets.all( + constants.borderRadiusSmall, ), - padding: const EdgeInsets.all(8), child: Image.asset( 'assets/images/repertory.png', fit: BoxFit.contain, @@ -182,7 +208,9 @@ class _AuthScreenState extends State { ), ), ), - const SizedBox(height: 14), + const SizedBox( + height: constants.paddingSmall, + ), Text( constants.appLogonTitle, textAlign: TextAlign.center, @@ -191,7 +219,9 @@ class _AuthScreenState extends State { .headlineSmall ?.copyWith(fontWeight: FontWeight.w600), ), - const SizedBox(height: 6), + const SizedBox( + height: constants.paddingSmall, + ), Text( "Secure access to your mounts", textAlign: TextAlign.center, @@ -200,12 +230,13 @@ class _AuthScreenState extends State { .bodyMedium ?.copyWith( color: scheme.onSurface.withValues( - alpha: 0.7, + alpha: 0.72, ), ), ), - const SizedBox(height: 20), - + const SizedBox( + height: constants.padding * 2.0, + ), TextFormField( autofocus: true, controller: _userController, @@ -261,10 +292,11 @@ class _AuthScreenState extends State { doLogin(auth); }, ), - const SizedBox(height: constants.padding), - + const SizedBox( + height: constants.padding * 2.0, + ), SizedBox( - height: 44, + height: 46, child: ElevatedButton( onPressed: _enabled ? () { @@ -272,9 +304,13 @@ class _AuthScreenState extends State { } : null, style: ElevatedButton.styleFrom( + backgroundColor: scheme.primary + .withValues(alpha: 0.45), + disabledBackgroundColor: scheme.primary + .withValues(alpha: 0.15), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( - 12, + constants.borderRadiusSmall, ), ), ), diff --git a/web/repertory/lib/screens/edit_mount_screen.dart b/web/repertory/lib/screens/edit_mount_screen.dart index 33665576..c38a55f1 100644 --- a/web/repertory/lib/screens/edit_mount_screen.dart +++ b/web/repertory/lib/screens/edit_mount_screen.dart @@ -1,9 +1,14 @@ +// edit_mount_screen.dart + import 'dart:convert'; +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/mount.dart'; +import 'package:repertory/widgets/aurora_sweep.dart'; import 'package:repertory/widgets/mount_settings.dart'; class EditMountScreen extends StatefulWidget { @@ -20,41 +25,273 @@ class _EditMountScreenState extends State { @override Widget build(BuildContext context) { + final scheme = Theme.of(context).colorScheme; + final textTheme = Theme.of(context).textTheme; + return Scaffold( - appBar: AppBar( - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - title: Text(widget.title), - actions: [ - Row( - children: [ - Row( - children: [ - const Text("Advanced"), - IconButton( - icon: Icon( - _showAdvanced ? Icons.toggle_on : Icons.toggle_off, - ), - onPressed: - () => setState(() => _showAdvanced = !_showAdvanced), + body: SafeArea( + child: Stack( + children: [ + // Background gradient + Container( + width: double.infinity, + height: double.infinity, + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: constants.gradientColors, + ), + ), + ), + // Subtle aurora sweep + const AuroraSweep( + enabled: true, + duration: Duration(seconds: 28), + primaryAlphaA: 0.04, + primaryAlphaB: 0.03, + ), + // Light blur + tint for glassiness + Positioned.fill( + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 6, sigmaY: 6), + child: Container(color: Colors.black.withValues(alpha: 0.06)), + ), + ), + + // Foreground content + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: constants.padding), + + // Header row: Back • Title • Advanced • Logout + Padding( + padding: const EdgeInsets.symmetric( + horizontal: constants.padding, ), - ], - ), - Consumer( - builder: (context, auth, _) { - return IconButton( - icon: const Icon(Icons.logout), - onPressed: () => auth.logoff(), - ); - }, - ), - ], - ), - ], - ), - body: MountSettingsWidget( - mount: widget.mount, - settings: jsonDecode(jsonEncode(widget.mount.mountConfig.settings)), - showAdvanced: _showAdvanced, + child: Row( + children: [ + // Back tile (glassy) + 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), + + // Title + Expanded( + child: Text( + widget.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), + + // Advanced toggle capsule (glassy) + ClipRRect( + borderRadius: BorderRadius.circular( + constants.borderRadius, + ), + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: constants.borderRadius, + sigmaY: constants.borderRadius, + ), + child: Container( + height: 40, + padding: const EdgeInsets.symmetric(horizontal: 10), + 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, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Advanced", + style: textTheme.labelLarge?.copyWith( + color: scheme.onSurface.withValues( + alpha: 0.90, + ), + ), + ), + const SizedBox(width: 6), + IconButton( + tooltip: _showAdvanced + ? 'Hide advanced' + : 'Show advanced', + icon: Icon( + _showAdvanced + ? Icons.toggle_on + : Icons.toggle_off, + ), + color: _showAdvanced + ? scheme.primary + : scheme.onSurface.withValues( + alpha: 0.70, + ), + onPressed: () { + setState(() { + _showAdvanced = !_showAdvanced; + }); + }, + ), + ], + ), + ), + ), + ), + + const SizedBox(width: constants.padding), + + // Logout capsule (glassy) + ClipRRect( + borderRadius: BorderRadius.circular( + constants.borderRadius, + ), + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: constants.borderRadius, + sigmaY: constants.borderRadius, + ), + child: Container( + height: 40, + padding: const EdgeInsets.symmetric(horizontal: 6), + 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, + ), + ), + child: Consumer( + builder: (context, auth, _) { + return IconButton( + tooltip: 'Log out', + icon: const Icon(Icons.logout), + onPressed: () { + auth.logoff(); + }, + ); + }, + ), + ), + ), + ), + ], + ), + ), + + const SizedBox(height: constants.padding), + + // Glass container hosting the settings list + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: constants.padding, + ), + child: Material( + color: Colors.transparent, + child: Container( + decoration: BoxDecoration( + color: scheme.surface.withValues(alpha: 0.40), + borderRadius: BorderRadius.circular( + constants.borderRadius, + ), + border: Border.all( + color: scheme.outlineVariant.withValues( + alpha: 0.06, + ), + width: 1, + ), + gradient: const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0x07FFFFFF), Color(0x00000000)], + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.22), + blurRadius: constants.borderRadius, + offset: Offset(0, constants.borderRadius), + ), + ], + ), + 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), + ], + ), + ], + ), ), ); } @@ -64,7 +301,6 @@ class _EditMountScreenState extends State { if (!mounted) { return; } - super.setState(fn); } } diff --git a/web/repertory/lib/screens/edit_settings_screen.dart b/web/repertory/lib/screens/edit_settings_screen.dart index 16ecd494..55b69aaf 100644 --- a/web/repertory/lib/screens/edit_settings_screen.dart +++ b/web/repertory/lib/screens/edit_settings_screen.dart @@ -1,10 +1,15 @@ +// edit_settings_screen.dart + import 'dart:convert' show jsonDecode, jsonEncode; +import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:provider/provider.dart'; +import 'package:repertory/constants.dart' as constants; import 'package:repertory/helpers.dart'; import 'package:repertory/models/auth.dart'; +import 'package:repertory/widgets/aurora_sweep.dart'; import 'package:repertory/widgets/ui_settings.dart'; class EditSettingsScreen extends StatefulWidget { @@ -18,35 +23,216 @@ class EditSettingsScreen extends StatefulWidget { class _EditSettingsScreenState extends State { @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - title: Text(widget.title), - actions: [ - Consumer( - builder: (context, auth, _) { - return IconButton( - icon: const Icon(Icons.logout), - onPressed: () => auth.logoff(), - ); - }, - ), - ], - ), - body: FutureBuilder( - builder: (context, snapshot) { - if (!snapshot.hasData) { - return Center(child: CircularProgressIndicator()); - } + final scheme = Theme.of(context).colorScheme; + final textTheme = Theme.of(context).textTheme; - return UISettingsWidget( - origSettings: jsonDecode(jsonEncode(snapshot.requireData)), - settings: snapshot.requireData, - showAdvanced: false, - ); - }, - future: _grabSettings(), - initialData: {}, + return Scaffold( + body: SafeArea( + child: Stack( + children: [ + // Background + Container( + width: double.infinity, + height: double.infinity, + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: constants.gradientColors, + ), + ), + ), + const AuroraSweep( + enabled: true, + duration: Duration(seconds: 28), + primaryAlphaA: 0.04, + primaryAlphaB: 0.03, + ), + Positioned.fill( + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 6, sigmaY: 6), + child: Container(color: Colors.black.withValues(alpha: 0.06)), + ), + ), + + // Content + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: constants.padding), + + // Header row: Back + Title + Logout capsule + Padding( + padding: const EdgeInsets.symmetric( + horizontal: constants.padding, + ), + child: Row( + children: [ + // Back button styled like our glass tiles + 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), + + // Title + Expanded( + child: Text( + widget.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), + + // Logout capsule (glassy) + ClipRRect( + borderRadius: BorderRadius.circular( + constants.borderRadius, + ), + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: constants.borderRadius, + sigmaY: constants.borderRadius, + ), + child: Container( + height: 40, + padding: const EdgeInsets.symmetric(horizontal: 6), + 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, + ), + ), + child: Consumer( + builder: (context, auth, _) { + return IconButton( + tooltip: 'Log out', + icon: const Icon(Icons.logout), + onPressed: () { + auth.logoff(); + }, + ); + }, + ), + ), + ), + ), + ], + ), + ), + + const SizedBox(height: constants.padding), + + // Glass container holding the settings list + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: constants.padding, + ), + child: Material( + color: Colors.transparent, + child: Container( + decoration: BoxDecoration( + color: scheme.surface.withValues(alpha: 0.40), + borderRadius: BorderRadius.circular( + constants.borderRadius, + ), + border: Border.all( + color: scheme.outlineVariant.withValues( + alpha: 0.06, + ), + width: 1, + ), + gradient: const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0x07FFFFFF), Color(0x00000000)], + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.22), + blurRadius: constants.borderRadius, + offset: Offset(0, constants.borderRadius), + ), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular( + constants.borderRadius, + ), + child: FutureBuilder>( + future: _grabSettings(), + initialData: const {}, + builder: (context, snapshot) { + if (!snapshot.hasData) { + return const Center( + child: CircularProgressIndicator(), + ); + } + + return UISettingsWidget( + origSettings: jsonDecode( + jsonEncode(snapshot.requireData), + ), + settings: snapshot.requireData, + showAdvanced: false, + ); + }, + ), + ), + ), + ), + ), + ), + const SizedBox(height: constants.padding), + ], + ), + ], + ), ), ); } @@ -81,7 +267,6 @@ class _EditSettingsScreenState extends State { if (!mounted) { return; } - super.setState(fn); } } diff --git a/web/repertory/lib/screens/home_screen.dart b/web/repertory/lib/screens/home_screen.dart index 0d83347d..21b2aa49 100644 --- a/web/repertory/lib/screens/home_screen.dart +++ b/web/repertory/lib/screens/home_screen.dart @@ -1,9 +1,12 @@ +// home_screen.dart + +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/mount_list_widget.dart'; +import 'package:repertory/widgets/aurora_sweep.dart'; class HomeScreen extends StatefulWidget { final String title; @@ -16,55 +19,189 @@ class HomeScreen extends StatefulWidget { class _HomeScreeState extends State { @override Widget build(context) { + final scheme = Theme.of(context).colorScheme; + final textTheme = Theme.of(context).textTheme; + return Scaffold( - appBar: AppBar( - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - leading: IconButton( - onPressed: () => Navigator.pushNamed(context, '/settings'), - icon: const Icon(Icons.storage), - ), - title: Text(widget.title), - actions: [ - const Text("Auto-start"), - Consumer( - builder: (context, settings, _) { - return IconButton( - icon: Icon( - settings.autoStart ? Icons.toggle_on : Icons.toggle_off, + 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, ), - onPressed: () => settings.setAutoStart(!settings.autoStart), - ); - }, - ), - const SizedBox(width: constants.padding), - Consumer( - builder: (context, auth, _) { - return IconButton( - icon: const Icon(Icons.logout), - onPressed: () => auth.logoff(), - ); - }, - ), - ], + ), + ), + const AuroraSweep( + enabled: true, + duration: Duration(seconds: 28), + primaryAlphaA: 0.04, + primaryAlphaB: 0.03, + ), + Positioned.fill( + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 6, sigmaY: 6), + child: Container(color: Colors.black.withValues(alpha: 0.06)), + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: constants.padding), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: constants.padding, + ), + child: Row( + children: [ + SizedBox( + width: 48, + height: 48, + child: Image.asset( + 'assets/images/repertory.png', + fit: BoxFit.contain, + errorBuilder: (_, _, _) { + return Icon( + Icons.folder, + color: scheme.primary, + size: 40, + ); + }, + ), + ), + const SizedBox(width: constants.padding), + Expanded( + child: Text( + widget.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), + ClipRRect( + borderRadius: BorderRadius.circular( + constants.borderRadius, + ), + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: constants.borderRadius, + sigmaY: constants.borderRadius, + ), + child: Container( + height: 40, + padding: const EdgeInsets.symmetric(horizontal: 6), + 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, + ), + ), + child: Row( + children: [ + IconButton( + tooltip: 'Settings', + icon: const Icon(Icons.settings), + onPressed: () { + Navigator.pushNamed(context, '/settings'); + }, + ), + Consumer( + builder: (context, auth, _) { + return IconButton( + tooltip: 'Log out', + icon: const Icon(Icons.logout), + onPressed: () { + auth.logoff(); + }, + ); + }, + ), + ], + ), + ), + ), + ), + ], + ), + ), + const SizedBox(height: constants.padding), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: constants.padding, + ), + child: const MountListWidget(), + ), + ), + ], + ), + ], + ), ), - body: Padding( + floatingActionButton: Padding( padding: const EdgeInsets.all(constants.padding), - child: MountListWidget(), - ), - floatingActionButton: FloatingActionButton( - onPressed: () => Navigator.pushNamed(context, '/add'), - tooltip: 'Add Mount', - child: const Icon(Icons.add), + child: Hero( + tag: 'add_mount_fab', + child: Material( + color: Colors.transparent, + elevation: 12, // match card depth + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(constants.borderRadius), + ), + child: Ink( + decoration: BoxDecoration( + // glassy base like MountWidget Card (but a touch bluer) + color: scheme.primary.withValues(alpha: 0.10), + borderRadius: BorderRadius.circular(constants.borderRadius), + border: Border.all( + color: scheme.outlineVariant.withValues(alpha: 0.15), + width: 1, + ), + // subtle top highlight for crisp glass + gradient: const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0x07FFFFFF), Color(0x00000000)], + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.22), + blurRadius: constants.borderRadius, + offset: Offset(0, constants.borderRadius), + ), + ], + ), + child: InkWell( + borderRadius: BorderRadius.circular(constants.borderRadius), + onTap: () { + Navigator.pushNamed(context, '/add'); + }, + child: const SizedBox( + width: 56, + height: 56, + child: Center(child: Icon(Icons.add, size: 28)), + ), + ), + ), + ), + ), ), ); } - - @override - void setState(VoidCallback fn) { - if (!mounted) { - return; - } - - super.setState(fn); - } } diff --git a/web/repertory/lib/settings.dart b/web/repertory/lib/settings.dart index a5d68e89..0c451fbf 100644 --- a/web/repertory/lib/settings.dart +++ b/web/repertory/lib/settings.dart @@ -2,7 +2,8 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:repertory/constants.dart' as constants; -import 'package:repertory/helpers.dart' show Validator, displayErrorMessage; +import 'package:repertory/helpers.dart' + show Validator, displayErrorMessage, doShowDialog; import 'package:settings_ui/settings_ui.dart'; void createBooleanSetting( @@ -57,16 +58,14 @@ void createIntListSetting( value: value.toString(), onChanged: (newValue) { setState( - () => - settings[key] = int.parse( - newValue ?? defaultValue.toString(), - ), + () => settings[key] = int.parse( + newValue ?? defaultValue.toString(), + ), ); }, - items: - valueList.map>((item) { - return DropdownMenuItem(value: item, child: Text(item)); - }).toList(), + items: valueList.map>((item) { + return DropdownMenuItem(value: item, child: Text(item)); + }).toList(), ), ), ); @@ -95,42 +94,40 @@ void createIntSetting( value: Text(value.toString()), onPressed: (_) { String updatedValue = value.toString(); - showDialog( - context: context, - builder: (context) { - return AlertDialog( - actions: [ - TextButton( - child: const Text('Cancel'), - onPressed: () => Navigator.of(context).pop(), - ), - TextButton( - child: const Text('OK'), - onPressed: () { - final result = validators.firstWhereOrNull( - (validator) => !validator(updatedValue), - ); - if (result != null) { - return displayErrorMessage( - context, - "Setting '$key' is not valid", - ); - } - setState(() => settings[key] = int.parse(updatedValue)); - Navigator.of(context).pop(); - }, - ), - ], - content: TextField( - autofocus: true, - controller: TextEditingController(text: updatedValue), - inputFormatters: [FilteringTextInputFormatter.digitsOnly], - keyboardType: TextInputType.number, - onChanged: (nextValue) => updatedValue = nextValue, + doShowDialog( + context, + AlertDialog( + actions: [ + TextButton( + child: const Text('Cancel'), + onPressed: () => Navigator.of(context).pop(), ), - title: createSettingTitle(context, key, description), - ); - }, + TextButton( + child: const Text('OK'), + onPressed: () { + final result = validators.firstWhereOrNull( + (validator) => !validator(updatedValue), + ); + if (result != null) { + return displayErrorMessage( + context, + "Setting '$key' is not valid", + ); + } + setState(() => settings[key] = int.parse(updatedValue)); + Navigator.of(context).pop(); + }, + ), + ], + content: TextField( + autofocus: true, + controller: TextEditingController(text: updatedValue), + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + keyboardType: TextInputType.number, + onChanged: (nextValue) => updatedValue = nextValue, + ), + title: createSettingTitle(context, key, description), + ), ); }, ), @@ -163,107 +160,103 @@ void createPasswordSetting( String updatedValue2 = value; bool hidePassword1 = true; bool hidePassword2 = true; - showDialog( - context: context, - builder: (context) { - return StatefulBuilder( - builder: (context, setDialogState) { - return AlertDialog( - actions: [ - TextButton( - child: const Text('Cancel'), - onPressed: () => Navigator.of(context).pop(), - ), - TextButton( - child: const Text('OK'), - onPressed: () { - if (updatedValue1 != updatedValue2) { - return displayErrorMessage( - context, - "Setting '$key' does not match", - ); - } - - final result = validators.firstWhereOrNull( - (validator) => !validator(updatedValue1), + doShowDialog( + context, + StatefulBuilder( + builder: (context, setDialogState) { + return AlertDialog( + actions: [ + TextButton( + child: const Text('Cancel'), + onPressed: () => Navigator.of(context).pop(), + ), + TextButton( + child: const Text('OK'), + onPressed: () { + if (updatedValue1 != updatedValue2) { + return displayErrorMessage( + context, + "Setting '$key' does not match", ); - if (result != null) { - return displayErrorMessage( - context, - "Setting '$key' is not valid", - ); - } + } - setState(() => settings[key] = updatedValue1); - Navigator.of(context).pop(); - }, + final result = validators.firstWhereOrNull( + (validator) => !validator(updatedValue1), + ); + if (result != null) { + return displayErrorMessage( + context, + "Setting '$key' is not valid", + ); + } + + setState(() => settings[key] = updatedValue1); + Navigator.of(context).pop(); + }, + ), + ], + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Expanded( + child: TextField( + autofocus: true, + controller: TextEditingController( + text: updatedValue1, + ), + obscureText: hidePassword1, + obscuringCharacter: '*', + onChanged: (value) => updatedValue1 = value, + ), + ), + IconButton( + onPressed: () => setDialogState( + () => hidePassword1 = !hidePassword1, + ), + icon: Icon( + hidePassword1 + ? Icons.visibility + : Icons.visibility_off, + ), + ), + ], + ), + const SizedBox(height: constants.padding), + Row( + children: [ + Expanded( + child: TextField( + autofocus: false, + controller: TextEditingController( + text: updatedValue2, + ), + obscureText: hidePassword2, + obscuringCharacter: '*', + onChanged: (value) => updatedValue2 = value, + ), + ), + IconButton( + onPressed: () => setDialogState( + () => hidePassword2 = !hidePassword2, + ), + icon: Icon( + hidePassword2 + ? Icons.visibility + : Icons.visibility_off, + ), + ), + ], ), ], - content: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Row( - children: [ - Expanded( - child: TextField( - autofocus: true, - controller: TextEditingController( - text: updatedValue1, - ), - obscureText: hidePassword1, - obscuringCharacter: '*', - onChanged: (value) => updatedValue1 = value, - ), - ), - IconButton( - onPressed: - () => setDialogState( - () => hidePassword1 = !hidePassword1, - ), - icon: Icon( - hidePassword1 - ? Icons.visibility - : Icons.visibility_off, - ), - ), - ], - ), - const SizedBox(height: constants.padding), - Row( - children: [ - Expanded( - child: TextField( - autofocus: false, - controller: TextEditingController( - text: updatedValue2, - ), - obscureText: hidePassword2, - obscuringCharacter: '*', - onChanged: (value) => updatedValue2 = value, - ), - ), - IconButton( - onPressed: - () => setDialogState( - () => hidePassword2 = !hidePassword2, - ), - icon: Icon( - hidePassword2 - ? Icons.visibility - : Icons.visibility_off, - ), - ), - ], - ), - ], - ), - title: createSettingTitle(context, key, description), - ); - }, - ); - }, + ), + title: createSettingTitle(context, key, description), + ); + }, + ), ); }, ), @@ -313,10 +306,9 @@ void createStringListSetting( value: DropdownButton( value: value, onChanged: (newValue) => setState(() => settings[key] = newValue), - items: - valueList.map>((item) { - return DropdownMenuItem(value: item, child: Text(item)); - }).toList(), + items: valueList.map>((item) { + return DropdownMenuItem(value: item, child: Text(item)); + }).toList(), ), ), ); @@ -345,43 +337,41 @@ void createStringSetting( value: Text(value), onPressed: (_) { String updatedValue = value; - showDialog( - context: context, - builder: (context) { - return AlertDialog( - actions: [ - TextButton( - child: const Text('Cancel'), - onPressed: () => Navigator.of(context).pop(), - ), - TextButton( - child: const Text('OK'), - onPressed: () { - final result = validators.firstWhereOrNull( - (validator) => !validator(updatedValue), - ); - if (result != null) { - return displayErrorMessage( - context, - "Setting '$key' is not valid", - ); - } - setState(() => settings[key] = updatedValue); - Navigator.of(context).pop(); - }, - ), - ], - content: TextField( - autofocus: true, - controller: TextEditingController(text: updatedValue), - inputFormatters: [ - FilteringTextInputFormatter.deny(RegExp(r'\s')), - ], - onChanged: (value) => updatedValue = value, + doShowDialog( + context, + AlertDialog( + actions: [ + TextButton( + child: const Text('Cancel'), + onPressed: () => Navigator.of(context).pop(), ), - title: createSettingTitle(context, key, description), - ); - }, + TextButton( + child: const Text('OK'), + onPressed: () { + final result = validators.firstWhereOrNull( + (validator) => !validator(updatedValue), + ); + if (result != null) { + return displayErrorMessage( + context, + "Setting '$key' is not valid", + ); + } + setState(() => settings[key] = updatedValue); + Navigator.of(context).pop(); + }, + ), + ], + content: TextField( + autofocus: true, + controller: TextEditingController(text: updatedValue), + inputFormatters: [ + FilteringTextInputFormatter.deny(RegExp(r'\s')), + ], + onChanged: (value) => updatedValue = value, + ), + title: createSettingTitle(context, key, description), + ), ); }, ), diff --git a/web/repertory/lib/widgets/aurora_sweep.dart b/web/repertory/lib/widgets/aurora_sweep.dart new file mode 100644 index 00000000..d60adaa9 --- /dev/null +++ b/web/repertory/lib/widgets/aurora_sweep.dart @@ -0,0 +1,122 @@ +// aurora_sweep.dart + +import 'dart:math' as math; +import 'package:flutter/material.dart'; + +class AuroraSweep extends StatefulWidget { + const AuroraSweep({ + super.key, + this.enabled = true, + this.duration = const Duration(seconds: 28), + this.primaryAlphaA = 0.04, // default dimmer for crispness + this.primaryAlphaB = 0.03, + this.staticPhase = 0.25, + this.radiusX = 0.85, + this.radiusY = 0.35, + this.beginYOffset = -0.55, + this.endYOffset = 0.90, + }); + + final bool enabled; + final Duration duration; + final double primaryAlphaA; + final double primaryAlphaB; + final double staticPhase; + final double radiusX; + final double radiusY; + final double beginYOffset; + final double endYOffset; + + @override + State createState() => _AuroraSweepState(); +} + +class _AuroraSweepState extends State + with SingleTickerProviderStateMixin { + late final AnimationController _c = AnimationController( + vsync: this, + duration: widget.duration, + ); + + @override + void initState() { + super.initState(); + if (widget.enabled) { + _c.repeat(); + } else { + _c.value = widget.staticPhase.clamp(0.0, 1.0); + } + } + + @override + void didUpdateWidget(covariant AuroraSweep oldWidget) { + super.didUpdateWidget(oldWidget); + + if (oldWidget.duration != widget.duration) { + _c.duration = widget.duration; + } + + if (widget.enabled && !_c.isAnimating) { + _c.repeat(); + } else if (!widget.enabled && _c.isAnimating) { + _c.stop(); + _c.value = widget.staticPhase.clamp(0.0, 1.0); + } + } + + @override + void dispose() { + _c.dispose(); + super.dispose(); + } + + (Alignment, Alignment) _alignmentsFromPhase(double t) { + final theta = 2 * math.pi * t; + final begin = Alignment( + widget.radiusX * math.cos(theta), + widget.beginYOffset + widget.radiusY * math.sin(theta), + ); + final end = Alignment( + widget.radiusX * math.cos(theta + math.pi / 2), + widget.endYOffset + widget.radiusY * math.sin(theta + math.pi / 2), + ); + return (begin, end); + } + + @override + Widget build(BuildContext context) { + final scheme = Theme.of(context).colorScheme; + + Widget paint(double t) { + final (begin, end) = _alignmentsFromPhase(t); + return IgnorePointer( + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: begin, + end: end, + colors: [ + scheme.primary.withValues(alpha: widget.primaryAlphaA), + Colors.transparent, + scheme.primary.withValues(alpha: widget.primaryAlphaB), + ], + stops: const [0.0, 0.5, 1.0], + ), + ), + ), + ); + } + + if (!widget.enabled) { + return paint(_c.value); + } + + return AnimatedBuilder( + animation: _c, + builder: (_, _) { + final t = _c.value; + return paint(t); + }, + ); + } +} diff --git a/web/repertory/lib/widgets/mount_settings.dart b/web/repertory/lib/widgets/mount_settings.dart index 058a741c..028a01fe 100644 --- a/web/repertory/lib/widgets/mount_settings.dart +++ b/web/repertory/lib/widgets/mount_settings.dart @@ -1,3 +1,5 @@ +// mount_settings.dart + import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:repertory/constants.dart' as constants; @@ -35,6 +37,20 @@ class MountSettingsWidget extends StatefulWidget { class _MountSettingsWidgetState extends State { @override Widget build(BuildContext context) { + final scheme = Theme.of(context).colorScheme; + + // Theme SettingsList to your glass aesthetic (consistent with UISettingsWidget) + final theme = SettingsThemeData( + settingsListBackground: Colors.transparent, + settingsSectionBackground: scheme.surface.withValues(alpha: 0.30), + titleTextColor: scheme.onSurface.withValues(alpha: 0.96), + trailingTextColor: scheme.onSurface.withValues(alpha: 0.80), + tileDescriptionTextColor: scheme.onSurface.withValues(alpha: 0.68), + leadingIconsColor: scheme.onSurface.withValues(alpha: 0.90), + dividerColor: scheme.outlineVariant.withValues(alpha: 0.10), + tileHighlightColor: scheme.primary.withValues(alpha: 0.08), + ); + List commonSettings = []; List encryptConfigSettings = []; List hostConfigSettings = []; @@ -363,11 +379,8 @@ class _MountSettingsWidgetState extends State { description: getSettingDescription('$key.$subKey'), validators: [ ...getSettingValidators('$key.$subKey'), - (value) => - !Provider.of( - context, - listen: false, - ).hasBucketName( + (value) => !Provider.of(context, listen: false) + .hasBucketName( widget.mount.type, value, excludeName: widget.mount.name, @@ -383,6 +396,9 @@ class _MountSettingsWidgetState extends State { return SettingsList( shrinkWrap: false, + platform: DevicePlatform.device, + lightTheme: theme, + darkTheme: theme, sections: [ if (encryptConfigSettings.isNotEmpty) SettingsSection( @@ -412,10 +428,9 @@ class _MountSettingsWidgetState extends State { if (remoteMountSettings.isNotEmpty) SettingsSection( title: const Text('Remote Mount'), - tiles: - widget.settings['RemoteMount']['Enable'] as bool - ? remoteMountSettings - : [remoteMountSettings[0]], + tiles: (widget.settings['RemoteMount']['Enable'] as bool) + ? remoteMountSettings + : [remoteMountSettings[0]], ), if (commonSettings.isNotEmpty) SettingsSection(title: const Text('Settings'), tiles: commonSettings), @@ -617,38 +632,6 @@ class _MountSettingsWidgetState extends State { }); } - @override - void dispose() { - if (!widget.isAdd) { - final settings = getChanged( - widget.mount.mountConfig.settings, - widget.settings, - ); - if (settings.isNotEmpty) { - final mount = widget.mount; - final key = - Provider.of( - constants.navigatorKey.currentContext!, - listen: false, - ).key; - convertAllToString(settings, key).then((map) { - map.forEach((key, value) { - if (value is Map) { - value.forEach((subKey, subValue) { - mount.setValue('$key.$subKey', subValue); - }); - return; - } - - mount.setValue(key, value); - }); - }); - } - } - - super.dispose(); - } - void _parseS3Config(List s3ConfigSettings, String key, value) { value.forEach((subKey, subValue) { switch (subKey) { @@ -686,11 +669,8 @@ class _MountSettingsWidgetState extends State { description: getSettingDescription('$key.$subKey'), validators: [ ...getSettingValidators('$key.$subKey'), - (value) => - !Provider.of( - context, - listen: false, - ).hasBucketName( + (value) => !Provider.of(context, listen: false) + .hasBucketName( widget.mount.type, value, excludeName: widget.mount.name, @@ -1016,16 +996,45 @@ class _MountSettingsWidgetState extends State { }); } + @override + void dispose() { + if (!widget.isAdd) { + final settings = getChanged( + widget.mount.mountConfig.settings, + widget.settings, + ); + if (settings.isNotEmpty) { + final mount = widget.mount; + final key = Provider.of( + constants.navigatorKey.currentContext!, + listen: false, + ).key; + convertAllToString(settings, key).then((map) { + map.forEach((key, value) { + if (value is Map) { + value.forEach((subKey, subValue) { + mount.setValue('$key.$subKey', subValue); + }); + return; + } + + mount.setValue(key, value); + }); + }); + } + } + + super.dispose(); + } + @override void setState(VoidCallback fn) { if (!mounted) { return; } - if (widget.onChanged != null) { widget.onChanged!(); } - super.setState(fn); } } diff --git a/web/repertory/lib/widgets/mount_widget.dart b/web/repertory/lib/widgets/mount_widget.dart index dfa3e052..e211596a 100644 --- a/web/repertory/lib/widgets/mount_widget.dart +++ b/web/repertory/lib/widgets/mount_widget.dart @@ -1,8 +1,7 @@ -import 'dart:async'; +// mount_widget.dart -import 'package:collection/collection.dart'; +import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:repertory/constants.dart' as constants; import 'package:repertory/helpers.dart'; @@ -22,98 +21,165 @@ class _MountWidgetState extends State { @override Widget build(BuildContext context) { + final scheme = Theme.of(context).colorScheme; + final textTheme = Theme.of(context).textTheme; + return Card( margin: const EdgeInsets.all(0.0), - child: Consumer( - builder: (context, Mount mount, _) { - final textColor = Theme.of(context).colorScheme.onSurface; - final subTextColor = Theme.of(context).brightness == Brightness.dark - ? Colors.white38 - : Colors.black87; + elevation: 12, + color: scheme.primary.withValues(alpha: 0.15), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(constants.borderRadiusSmall), + side: BorderSide( + color: scheme.outlineVariant.withValues(alpha: 0.06), + width: 1, + ), + ), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(constants.borderRadiusSmall), + gradient: const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0x07FFFFFF), Color(0x00000000)], + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.22), + blurRadius: constants.borderRadiusSmall, + offset: const Offset(0, constants.borderRadiusSmall), + ), + ], + ), + child: Consumer( + builder: (context, Mount mount, _) { + final titleStyle = textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w700, + letterSpacing: 0.15, + color: scheme.onSurface.withValues(alpha: 0.96), + ); + final subStyle = textTheme.bodyMedium?.copyWith( + color: scheme.onSurface.withValues(alpha: 0.78), + ); + final pathStyle = textTheme.bodySmall?.copyWith( + color: scheme.onSurface.withValues(alpha: 0.68), + ); - final nameText = SelectableText( - formatMountName(mount.type, mount.name), - style: TextStyle(color: subTextColor), - ); + final nameText = SelectableText( + formatMountName(mount.type, mount.name), + style: subStyle, + ); - return ListTile( - isThreeLine: true, - leading: IconButton( - icon: Icon(Icons.settings, color: textColor), - onPressed: () => - Navigator.pushNamed(context, '/edit', arguments: mount), - ), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - nameText, - SelectableText( - mount.path.isEmpty && mount.mounted == null - ? 'loading...' - : mount.path.isEmpty - ? '' - : mount.path, - style: TextStyle(color: subTextColor), - ), - ], - ), - title: SelectableText( - mount.provider, - style: TextStyle(color: textColor, fontWeight: FontWeight.bold), - ), - trailing: Row( - mainAxisAlignment: MainAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (mount.mounted != null && !mount.mounted!) - IconButton( - icon: const Icon(Icons.edit), - color: subTextColor, - tooltip: 'Edit mount location', - onPressed: () async { - setState(() => _editEnabled = false); - final available = await mount.getAvailableLocations(); - if (context.mounted) { - final location = await editMountLocation( - context, - available, - location: mount.path, - ); - if (location != null) { - await mount.setMountLocation(location); - } - } - setState(() => _editEnabled = true); - }, + return ListTile( + isThreeLine: true, + contentPadding: const EdgeInsets.all(constants.paddingSmall), + leading: Container( + width: 46, + height: 46, + decoration: BoxDecoration( + color: scheme.primary.withValues(alpha: 0.12), + borderRadius: BorderRadius.circular( + constants.borderRadiusSmall, ), - IconButton( + border: Border.all( + color: scheme.outlineVariant.withValues(alpha: 0.08), + width: 1, + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.24), + blurRadius: constants.borderRadiusSmall, + offset: const Offset(0, 5), + ), + ], + ), + child: IconButton( + tooltip: 'Edit settings', icon: Icon( - mount.mounted == null - ? Icons.hourglass_top - : mount.mounted! - ? Icons.toggle_on - : Icons.toggle_off, - color: mount.mounted ?? false - ? Theme.of(context).colorScheme.primary - : subTextColor, + Icons.settings, + color: scheme.onSurface.withValues(alpha: 0.92), + size: 22, ), - tooltip: mount.mounted == null - ? '' - : mount.mounted! - ? 'Unmount' - : 'Mount', - onPressed: _createMountHandler(context, mount), + onPressed: () { + Navigator.pushNamed(context, '/edit', arguments: mount); + }, ), - ], - ), - ); - }, + ), + title: SelectableText(mount.provider, style: titleStyle), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + nameText, + const SizedBox(height: 3), + SelectableText( + mount.path.isEmpty && mount.mounted == null + ? 'loading...' + : mount.path.isEmpty + ? '' + : mount.path, + style: pathStyle, + ), + ], + ), + trailing: Row( + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + if (mount.mounted != null && !mount.mounted!) + IconButton( + icon: const Icon(Icons.edit), + color: scheme.onSurface.withValues(alpha: 0.70), + tooltip: 'Edit mount location', + onPressed: () async { + setState(() { + _editEnabled = false; + }); + final available = await mount.getAvailableLocations(); + if (context.mounted) { + final location = await editMountLocation( + context, + available, + location: mount.path, + ); + if (location != null) { + await mount.setMountLocation(location); + } + } + setState(() { + _editEnabled = true; + }); + }, + ), + IconButton( + iconSize: 32, + splashRadius: 26, + icon: Icon( + mount.mounted == null + ? Icons.hourglass_top + : mount.mounted! + ? Icons.toggle_on + : Icons.toggle_off, + ), + color: mount.mounted ?? false + ? scheme.primary + : scheme.outline.withValues(alpha: 0.70), + tooltip: mount.mounted == null + ? '' + : mount.mounted! + ? 'Unmount' + : 'Mount', + onPressed: _createMountHandler(context, mount), + ), + ], + ), + ); + }, + ), ), ); } - VoidCallback? _createMountHandler(context, Mount mount) { + VoidCallback? _createMountHandler(BuildContext context, Mount mount) { return _enabled && mount.mounted != null ? () async { if (mount.mounted == null) { @@ -128,15 +194,16 @@ class _MountWidgetState extends State { final location = await _getMountLocation(context, mount); - cleanup() { + void cleanup() { setState(() { _enabled = true; }); } - if (!mounted && location == null) { + if (!context.mounted && location == null) { displayErrorMessage(context, "Mount location is not set"); - return cleanup(); + cleanup(); + return; } final success = await mount.mount(mounted, location: location); @@ -144,7 +211,8 @@ class _MountWidgetState extends State { mounted || constants.navigatorKey.currentContext == null || !constants.navigatorKey.currentContext!.mounted) { - return cleanup(); + cleanup(); + return; } displayErrorMessage( @@ -163,7 +231,7 @@ class _MountWidgetState extends State { super.dispose(); } - Future _getMountLocation(context, Mount mount) async { + Future _getMountLocation(BuildContext context, Mount mount) async { if (mount.mounted ?? false) { return null; } @@ -197,7 +265,6 @@ class _MountWidgetState extends State { if (!mounted) { return; } - super.setState(fn); } } diff --git a/web/repertory/lib/widgets/ui_settings.dart b/web/repertory/lib/widgets/ui_settings.dart index 7699a7d9..dc08ddf1 100644 --- a/web/repertory/lib/widgets/ui_settings.dart +++ b/web/repertory/lib/widgets/ui_settings.dart @@ -1,3 +1,5 @@ +// ui_settings.dart + import 'dart:convert' show jsonEncode; import 'package:flutter/material.dart'; @@ -35,6 +37,22 @@ class UISettingsWidget extends StatefulWidget { class _UISettingsWidgetState extends State { @override Widget build(BuildContext context) { + final scheme = Theme.of(context).colorScheme; + + // Theme the SettingsList to align with your glass look + final theme = SettingsThemeData( + settingsListBackground: Colors.transparent, + tileDescriptionTextColor: scheme.onSurface.withValues(alpha: 0.68), + settingsSectionBackground: scheme.surface.withValues( + alpha: 0.30, + ), // slight glass base + tileHighlightColor: scheme.primary.withValues(alpha: 0.08), + titleTextColor: scheme.onSurface.withValues(alpha: 0.96), + trailingTextColor: scheme.onSurface.withValues(alpha: 0.80), + leadingIconsColor: scheme.onSurface.withValues(alpha: 0.90), + dividerColor: scheme.outlineVariant.withValues(alpha: 0.10), + ); + List commonSettings = []; widget.settings.forEach((key, value) { @@ -56,6 +74,7 @@ class _UISettingsWidgetState extends State { ); } break; + case 'ApiPort': { createIntSetting( @@ -73,6 +92,7 @@ class _UISettingsWidgetState extends State { ); } break; + case 'ApiUser': { createStringSetting( @@ -96,6 +116,9 @@ class _UISettingsWidgetState extends State { return SettingsList( shrinkWrap: false, + platform: DevicePlatform.device, + lightTheme: theme, + darkTheme: theme, sections: [ SettingsSection(title: const Text('Settings'), tiles: commonSettings), ], @@ -106,11 +129,10 @@ class _UISettingsWidgetState extends State { void dispose() { final settings = getChanged(widget.origSettings, widget.settings); if (settings.isNotEmpty) { - final key = - Provider.of( - constants.navigatorKey.currentContext!, - listen: false, - ).key; + final key = Provider.of( + constants.navigatorKey.currentContext!, + listen: false, + ).key; convertAllToString(settings, key) .then((map) async { try { @@ -149,7 +171,6 @@ class _UISettingsWidgetState extends State { if (!mounted) { return; } - super.setState(fn); } } diff --git a/web/repertory/pubspec.yaml b/web/repertory/pubspec.yaml index 756de0d0..04d90fce 100644 --- a/web/repertory/pubspec.yaml +++ b/web/repertory/pubspec.yaml @@ -38,7 +38,7 @@ dependencies: http: ^1.3.0 provider: ^6.1.2 settings_ui: ^2.0.2 - sodium_libs: ^3.4.4+1 + sodium_libs: ^3.4.6+1 convert: ^3.1.2 dev_dependencies: diff --git a/web/repertory/web/favicon.png b/web/repertory/web/favicon.png index 8aaa46ac..ac69855c 100644 Binary files a/web/repertory/web/favicon.png and b/web/repertory/web/favicon.png differ diff --git a/web/repertory/web/index.html b/web/repertory/web/index.html index efa4bc1d..b77560f8 100644 --- a/web/repertory/web/index.html +++ b/web/repertory/web/index.html @@ -1,44 +1,37 @@ - - - - - + This is a placeholder for base href that will be replaced by the value of + the `--base-href` argument provided to `flutter build`. + --> + - - - + + + - - - - - + + + + + - - + + - repertory - - - - - - - - + repertory + + + + + + + \ No newline at end of file