[ui] UI theme should match repertory blue #61
All checks were successful
BlockStorage/repertory/pipeline/head This commit looks good
All checks were successful
BlockStorage/repertory/pipeline/head This commit looks good
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
auro
|
||||||
|
aurosweep
|
||||||
autofocus
|
autofocus
|
||||||
autovalidatemode
|
autovalidatemode
|
||||||
canvaskit
|
canvaskit
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
# This file should be version controlled and should not be manually edited.
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
version:
|
version:
|
||||||
revision: "35c388afb57ef061d06a39b537336c87e0e3d1b1"
|
revision: "d7b523b356d15fb81e7d340bbe52b47f93937323"
|
||||||
channel: "stable"
|
channel: "stable"
|
||||||
|
|
||||||
project_type: app
|
project_type: app
|
||||||
@@ -13,11 +13,11 @@ project_type: app
|
|||||||
migration:
|
migration:
|
||||||
platforms:
|
platforms:
|
||||||
- platform: root
|
- platform: root
|
||||||
create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
|
create_revision: d7b523b356d15fb81e7d340bbe52b47f93937323
|
||||||
base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
|
base_revision: d7b523b356d15fb81e7d340bbe52b47f93937323
|
||||||
- platform: web
|
- platform: web
|
||||||
create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
|
create_revision: d7b523b356d15fb81e7d340bbe52b47f93937323
|
||||||
base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
|
base_revision: d7b523b356d15fb81e7d340bbe52b47f93937323
|
||||||
|
|
||||||
# User provided section
|
# User provided section
|
||||||
|
|
||||||
|
@@ -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';
|
import 'package:sodium_libs/sodium_libs.dart';
|
||||||
|
|
||||||
const addMountTitle = 'Add New Mount';
|
const addMountTitle = 'Add New Mount';
|
||||||
@@ -9,10 +11,14 @@ const logonWidth = 300.0;
|
|||||||
const databaseTypeList = ['rocksdb', 'sqlite'];
|
const databaseTypeList = ['rocksdb', 'sqlite'];
|
||||||
const downloadTypeList = ['default', 'direct', 'ring_buffer'];
|
const downloadTypeList = ['default', 'direct', 'ring_buffer'];
|
||||||
const eventLevelList = ['critical', 'error', 'warn', 'info', 'debug', 'trace'];
|
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 protocolTypeList = ['http', 'https'];
|
||||||
const providerTypeList = ['Encrypt', 'Remote', 'S3', 'Sia'];
|
const providerTypeList = ['Encrypt', 'Remote', 'S3', 'Sia'];
|
||||||
const ringBufferSizeList = ['128', '256', '512', '1024', '2048'];
|
const ringBufferSizeList = ['128', '256', '512', '1024', '2048'];
|
||||||
|
const gradientColors = [Color(0xFF0A0F1F), Color(0xFF1B1C1F)];
|
||||||
|
|
||||||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
// helpers.dart
|
||||||
|
|
||||||
import 'package:convert/convert.dart';
|
import 'package:convert/convert.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
@@ -348,55 +350,95 @@ Future<String?> editMountLocation(
|
|||||||
return await showDialog(
|
return await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return StatefulBuilder(
|
var theme = Theme.of(context);
|
||||||
builder: (context, setState) {
|
var scheme = theme.colorScheme;
|
||||||
return AlertDialog(
|
return Theme(
|
||||||
actions: [
|
data: theme.copyWith(
|
||||||
TextButton(
|
dialogTheme: DialogThemeData(
|
||||||
child: const Text('Cancel'),
|
backgroundColor: scheme.surface.withValues(alpha: 0.40),
|
||||||
onPressed: () => Navigator.of(context).pop(null),
|
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(
|
child: StatefulBuilder(
|
||||||
(validator) => !validator(currentLocation ?? ''),
|
builder: (context, setState) {
|
||||||
);
|
return AlertDialog(
|
||||||
if (result != null) {
|
actions: [
|
||||||
return displayErrorMessage(
|
TextButton(
|
||||||
context,
|
child: const Text('Cancel'),
|
||||||
"Mount location is not valid",
|
onPressed: () => Navigator.of(context).pop(null),
|
||||||
);
|
),
|
||||||
}
|
TextButton(
|
||||||
Navigator.of(context).pop(currentLocation);
|
child: const Text('OK'),
|
||||||
},
|
onPressed: () {
|
||||||
),
|
final result = getSettingValidators('Path')
|
||||||
],
|
.firstWhereOrNull(
|
||||||
content:
|
(validator) => !validator(currentLocation ?? ''),
|
||||||
available.isEmpty
|
);
|
||||||
? TextField(
|
if (result != null) {
|
||||||
|
return displayErrorMessage(
|
||||||
|
context,
|
||||||
|
"Mount location is not valid",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Navigator.of(context).pop(currentLocation);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
content: available.isEmpty
|
||||||
|
? TextField(
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
controller: controller,
|
controller: controller,
|
||||||
onChanged:
|
onChanged: (value) =>
|
||||||
(value) => setState(() => currentLocation = value),
|
setState(() => currentLocation = value),
|
||||||
)
|
)
|
||||||
: DropdownButton<String>(
|
: DropdownButton<String>(
|
||||||
hint: const Text("Select drive"),
|
hint: const Text("Select drive"),
|
||||||
value: currentLocation,
|
value: currentLocation,
|
||||||
onChanged:
|
onChanged: (value) =>
|
||||||
(value) => setState(() => currentLocation = value),
|
setState(() => currentLocation = value),
|
||||||
items:
|
items: available.map<DropdownMenuItem<String>>((item) {
|
||||||
available.map<DropdownMenuItem<String>>((item) {
|
return DropdownMenuItem<String>(
|
||||||
return DropdownMenuItem<String>(
|
value: item,
|
||||||
value: item,
|
child: Text(item),
|
||||||
child: Text(item),
|
);
|
||||||
);
|
}).toList(),
|
||||||
}).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,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
// main.dart
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:repertory/constants.dart' as constants;
|
import 'package:repertory/constants.dart' as constants;
|
||||||
@@ -123,7 +125,7 @@ class AuthCheck extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Consumer<Auth>(
|
return Consumer<Auth>(
|
||||||
builder: (context, auth, __) {
|
builder: (context, auth, _) {
|
||||||
if (!auth.authenticated) {
|
if (!auth.authenticated) {
|
||||||
Future.delayed(Duration(milliseconds: 1), () {
|
Future.delayed(Duration(milliseconds: 1), () {
|
||||||
if (constants.navigatorKey.currentContext == null) {
|
if (constants.navigatorKey.currentContext == null) {
|
||||||
|
@@ -1,3 +1,6 @@
|
|||||||
|
// add_mount_screen.dart
|
||||||
|
|
||||||
|
import 'dart:ui';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:provider/provider.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.dart';
|
||||||
import 'package:repertory/models/mount_list.dart';
|
import 'package:repertory/models/mount_list.dart';
|
||||||
import 'package:repertory/types/mount_config.dart';
|
import 'package:repertory/types/mount_config.dart';
|
||||||
|
import 'package:repertory/widgets/aurora_sweep.dart';
|
||||||
import 'package:repertory/widgets/mount_settings.dart';
|
import 'package:repertory/widgets/mount_settings.dart';
|
||||||
|
|
||||||
class AddMountScreen extends StatefulWidget {
|
class AddMountScreen extends StatefulWidget {
|
||||||
@@ -30,48 +34,206 @@ class _AddMountScreenState extends State<AddMountScreen> {
|
|||||||
"Sia": createDefaultSettings("Sia"),
|
"Sia": createDefaultSettings("Sia"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_mountNameController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
final scheme = Theme.of(context).colorScheme;
|
||||||
appBar: AppBar(
|
final textTheme = Theme.of(context).textTheme;
|
||||||
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
|
||||||
title: Text(widget.title),
|
Widget glassTile({required Widget child, EdgeInsets? padding}) {
|
||||||
actions: [
|
return Container(
|
||||||
Consumer<Auth>(
|
decoration: BoxDecoration(
|
||||||
builder: (context, auth, _) {
|
color: scheme.surface.withValues(alpha: 0.40),
|
||||||
return IconButton(
|
borderRadius: BorderRadius.circular(constants.borderRadius),
|
||||||
icon: const Icon(Icons.logout),
|
border: Border.all(
|
||||||
onPressed: () => auth.logoff(),
|
color: scheme.outlineVariant.withValues(alpha: 0.08),
|
||||||
);
|
width: 1,
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
gradient: const LinearGradient(
|
||||||
),
|
begin: Alignment.topCenter,
|
||||||
body: Padding(
|
end: Alignment.bottomCenter,
|
||||||
padding: const EdgeInsets.all(constants.padding),
|
colors: [Color(0x07FFFFFF), Color(0x00000000)],
|
||||||
child: Consumer<Auth>(
|
),
|
||||||
builder: (context, auth, _) {
|
boxShadow: [
|
||||||
return Column(
|
BoxShadow(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
color: Colors.black.withValues(alpha: 0.22),
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
blurRadius: constants.borderRadius,
|
||||||
mainAxisSize: MainAxisSize.max,
|
offset: Offset(0, constants.borderRadius),
|
||||||
children: [
|
),
|
||||||
Card(
|
],
|
||||||
margin: EdgeInsets.all(0.0),
|
),
|
||||||
child: Padding(
|
child: ClipRRect(
|
||||||
padding: const EdgeInsets.all(constants.padding),
|
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<Auth>(
|
||||||
|
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(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
children: [
|
||||||
const Text('Provider Type'),
|
Text(
|
||||||
|
'Provider Type',
|
||||||
|
style: textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
const SizedBox(width: constants.padding),
|
const SizedBox(width: constants.padding),
|
||||||
DropdownButton<String>(
|
DropdownButton<String>(
|
||||||
autofocus: true,
|
|
||||||
value: _mountType,
|
value: _mountType,
|
||||||
onChanged: (mountType) =>
|
autofocus: true,
|
||||||
_handleChange(auth, mountType ?? ''),
|
underline: const SizedBox.shrink(),
|
||||||
|
onChanged: (mountType) {
|
||||||
|
_handleChange(
|
||||||
|
Provider.of<Auth>(context, listen: false),
|
||||||
|
mountType ?? '',
|
||||||
|
);
|
||||||
|
},
|
||||||
items: constants.providerTypeList
|
items: constants.providerTypeList
|
||||||
.map<DropdownMenuItem<String>>((item) {
|
.map<DropdownMenuItem<String>>((item) {
|
||||||
return DropdownMenuItem<String>(
|
return DropdownMenuItem<String>(
|
||||||
@@ -84,21 +246,21 @@ class _AddMountScreenState extends State<AddMountScreen> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
if (_mountType.isNotEmpty && _mountType != 'Remote')
|
if (_mountType.isNotEmpty && _mountType != 'Remote') ...[
|
||||||
const SizedBox(height: constants.padding),
|
const SizedBox(height: constants.padding),
|
||||||
if (_mountType.isNotEmpty && _mountType != 'Remote')
|
// Config Name (glassy tile)
|
||||||
Card(
|
glassTile(
|
||||||
margin: EdgeInsets.all(0.0),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(constants.padding),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
children: [
|
||||||
const Text('Configuration Name'),
|
Text(
|
||||||
const SizedBox(width: constants.padding),
|
'Configuration Name',
|
||||||
|
style: textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
TextField(
|
TextField(
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
controller: _mountNameController,
|
controller: _mountNameController,
|
||||||
@@ -106,99 +268,157 @@ class _AddMountScreenState extends State<AddMountScreen> {
|
|||||||
inputFormatters: [
|
inputFormatters: [
|
||||||
FilteringTextInputFormatter.deny(RegExp(r'\s')),
|
FilteringTextInputFormatter.deny(RegExp(r'\s')),
|
||||||
],
|
],
|
||||||
onChanged: (_) => _handleChange(auth, _mountType),
|
onChanged: (_) => _handleChange(
|
||||||
|
Provider.of<Auth>(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),
|
if (_mount != null) ...[
|
||||||
Expanded(
|
const SizedBox(height: constants.padding),
|
||||||
child: Card(
|
// Settings (large glass container)
|
||||||
margin: EdgeInsets.all(0.0),
|
Expanded(
|
||||||
child: Padding(
|
child: glassTile(
|
||||||
padding: const EdgeInsets.all(constants.padding),
|
padding: EdgeInsets.zero,
|
||||||
child: MountSettingsWidget(
|
child: Padding(
|
||||||
isAdd: true,
|
padding: const EdgeInsets.all(constants.padding),
|
||||||
mount: _mount!,
|
child: MountSettingsWidget(
|
||||||
settings: _settings[_mountType]!,
|
isAdd: true,
|
||||||
showAdvanced: false,
|
mount: _mount!,
|
||||||
|
settings: _settings[_mountType]!,
|
||||||
|
showAdvanced: false,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: constants.padding),
|
||||||
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<MountList>(context);
|
|
||||||
|
|
||||||
List<String> failed = [];
|
// Action buttons row
|
||||||
if (!validateSettings(
|
Row(
|
||||||
_settings[_mountType]!,
|
children: [
|
||||||
failed,
|
// Test
|
||||||
)) {
|
ElevatedButton.icon(
|
||||||
for (var key in failed) {
|
label: const Text('Test'),
|
||||||
displayErrorMessage(
|
icon: const Icon(Icons.check),
|
||||||
context,
|
style: ElevatedButton.styleFrom(
|
||||||
"Setting '$key' is not valid",
|
backgroundColor: scheme.primary.withValues(
|
||||||
);
|
alpha: 0.18,
|
||||||
}
|
),
|
||||||
return;
|
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(
|
// Add
|
||||||
_mountNameController.text,
|
ElevatedButton.icon(
|
||||||
)) {
|
label: const Text('Add'),
|
||||||
return displayErrorMessage(
|
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,
|
context,
|
||||||
"Configuration name '${_mountNameController.text}' already exists",
|
listen: false,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
if (_mountType == "Sia" || _mountType == "S3") {
|
List<String> failed = [];
|
||||||
final bucket =
|
if (!validateSettings(
|
||||||
_settings[_mountType]!["${_mountType}Config"]["Bucket"]
|
_settings[_mountType]!,
|
||||||
as String;
|
failed,
|
||||||
if (mountList.hasBucketName(_mountType, bucket)) {
|
)) {
|
||||||
|
for (var key in failed) {
|
||||||
|
displayErrorMessage(
|
||||||
|
context,
|
||||||
|
"Setting '$key' is not valid",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mountList.hasConfigName(
|
||||||
|
_mountNameController.text,
|
||||||
|
)) {
|
||||||
return displayErrorMessage(
|
return displayErrorMessage(
|
||||||
context,
|
context,
|
||||||
"Bucket '$bucket' already exists",
|
"Configuration name '${_mountNameController.text}' already exists",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
final success = await mountList.add(
|
if (_mountType == "Sia" || _mountType == "S3") {
|
||||||
_mountType,
|
final bucket =
|
||||||
_mountType == 'Remote'
|
_settings[_mountType]!["${_mountType}Config"]["Bucket"]
|
||||||
? '${_settings[_mountType]!['RemoteConfig']['HostNameOrIp']}_${_settings[_mountType]!['RemoteConfig']['ApiPort']}'
|
as String;
|
||||||
: _mountNameController.text,
|
if (mountList.hasBucketName(_mountType, bucket)) {
|
||||||
_settings[_mountType]!,
|
return displayErrorMessage(
|
||||||
);
|
context,
|
||||||
|
"Bucket '$bucket' already exists",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!success || !context.mounted) {
|
final success = await mountList.add(
|
||||||
return;
|
_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<AddMountScreen> {
|
|||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
super.setState(fn);
|
super.setState(fn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,11 @@
|
|||||||
|
// auth_screen.dart
|
||||||
|
|
||||||
|
import 'dart:ui';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:repertory/constants.dart' as constants;
|
import 'package:repertory/constants.dart' as constants;
|
||||||
import 'package:repertory/models/auth.dart';
|
import 'package:repertory/models/auth.dart';
|
||||||
|
import 'package:repertory/widgets/aurora_sweep.dart';
|
||||||
|
|
||||||
class AuthScreen extends StatefulWidget {
|
class AuthScreen extends StatefulWidget {
|
||||||
final String title;
|
final String title;
|
||||||
@@ -27,30 +31,29 @@ class _AuthScreenState extends State<AuthScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(context) {
|
Widget build(BuildContext context) {
|
||||||
final scheme = Theme.of(context).colorScheme;
|
final scheme = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
InputDecoration decoration(String label, IconData icon) => InputDecoration(
|
InputDecoration decoration(String label, IconData icon) => InputDecoration(
|
||||||
labelText: label,
|
labelText: label,
|
||||||
prefixIcon: Icon(icon),
|
prefixIcon: Icon(icon),
|
||||||
filled: true,
|
filled: true,
|
||||||
fillColor: scheme.surfaceContainerLow.withValues(alpha: 0.65),
|
fillColor: scheme.primary.withValues(alpha: 0.15),
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(constants.borderRadiusSmall),
|
||||||
borderSide: BorderSide.none,
|
borderSide: BorderSide.none,
|
||||||
),
|
),
|
||||||
focusedBorder: OutlineInputBorder(
|
focusedBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(constants.borderRadiusSmall),
|
||||||
borderSide: BorderSide(color: scheme.primary, width: 2),
|
borderSide: BorderSide(color: scheme.primary, width: 2),
|
||||||
),
|
),
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14),
|
contentPadding: const EdgeInsets.all(constants.paddingSmall),
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<void> doLogin(Auth auth) async {
|
Future<void> doLogin(Auth auth) async {
|
||||||
if (!_enabled) {
|
if (!_enabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_formKey.currentState!.validate()) {
|
if (!_formKey.currentState!.validate()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -89,22 +92,29 @@ class _AuthScreenState extends State<AuthScreen> {
|
|||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
begin: Alignment.topLeft,
|
begin: Alignment.topLeft,
|
||||||
end: Alignment.bottomRight,
|
end: Alignment.bottomRight,
|
||||||
colors: [Color(0xFF0A0F1F), Color(0xFF1B1C1F)],
|
colors: constants.gradientColors,
|
||||||
stops: [0.0, 1.0],
|
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(
|
Align(
|
||||||
alignment: const Alignment(0, 0.1),
|
alignment: const Alignment(0, 0.06),
|
||||||
child: IgnorePointer(
|
child: IgnorePointer(
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 740,
|
width: 720,
|
||||||
height: 740,
|
height: 720,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
gradient: RadialGradient(
|
gradient: RadialGradient(
|
||||||
colors: [
|
colors: [
|
||||||
scheme.primary.withValues(alpha: 0.22),
|
scheme.primary.withValues(alpha: 0.20),
|
||||||
Colors.transparent,
|
Colors.transparent,
|
||||||
],
|
],
|
||||||
stops: const [0.0, 1.0],
|
stops: const [0.0, 1.0],
|
||||||
@@ -116,7 +126,7 @@ class _AuthScreenState extends State<AuthScreen> {
|
|||||||
Consumer<Auth>(
|
Consumer<Auth>(
|
||||||
builder: (context, auth, _) {
|
builder: (context, auth, _) {
|
||||||
if (auth.authenticated) {
|
if (auth.authenticated) {
|
||||||
Future.delayed(const Duration(milliseconds: 1), () {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
if (constants.navigatorKey.currentContext == null) {
|
if (constants.navigatorKey.currentContext == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -124,7 +134,6 @@ class _AuthScreenState extends State<AuthScreen> {
|
|||||||
constants.navigatorKey.currentContext!,
|
constants.navigatorKey.currentContext!,
|
||||||
).pushNamedAndRemoveUntil('/', (r) => false);
|
).pushNamedAndRemoveUntil('/', (r) => false);
|
||||||
});
|
});
|
||||||
|
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,13 +151,17 @@ class _AuthScreenState extends State<AuthScreen> {
|
|||||||
minWidth: 300,
|
minWidth: 300,
|
||||||
),
|
),
|
||||||
child: Card(
|
child: Card(
|
||||||
elevation: 12,
|
elevation: constants.padding,
|
||||||
color: scheme.surface,
|
color: scheme.primary.withValues(alpha: 0.10),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(18),
|
borderRadius: BorderRadius.circular(
|
||||||
|
constants.borderRadius,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(constants.padding),
|
padding: const EdgeInsets.all(
|
||||||
|
constants.padding * 2.0,
|
||||||
|
),
|
||||||
child: Form(
|
child: Form(
|
||||||
key: _formKey,
|
key: _formKey,
|
||||||
autovalidateMode:
|
autovalidateMode:
|
||||||
@@ -160,15 +173,28 @@ class _AuthScreenState extends State<AuthScreen> {
|
|||||||
Align(
|
Align(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 56,
|
width: 64,
|
||||||
height: 56,
|
height: 64,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: scheme.primary.withValues(
|
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(
|
child: Image.asset(
|
||||||
'assets/images/repertory.png',
|
'assets/images/repertory.png',
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
@@ -182,7 +208,9 @@ class _AuthScreenState extends State<AuthScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 14),
|
const SizedBox(
|
||||||
|
height: constants.paddingSmall,
|
||||||
|
),
|
||||||
Text(
|
Text(
|
||||||
constants.appLogonTitle,
|
constants.appLogonTitle,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
@@ -191,7 +219,9 @@ class _AuthScreenState extends State<AuthScreen> {
|
|||||||
.headlineSmall
|
.headlineSmall
|
||||||
?.copyWith(fontWeight: FontWeight.w600),
|
?.copyWith(fontWeight: FontWeight.w600),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(
|
||||||
|
height: constants.paddingSmall,
|
||||||
|
),
|
||||||
Text(
|
Text(
|
||||||
"Secure access to your mounts",
|
"Secure access to your mounts",
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
@@ -200,12 +230,13 @@ class _AuthScreenState extends State<AuthScreen> {
|
|||||||
.bodyMedium
|
.bodyMedium
|
||||||
?.copyWith(
|
?.copyWith(
|
||||||
color: scheme.onSurface.withValues(
|
color: scheme.onSurface.withValues(
|
||||||
alpha: 0.7,
|
alpha: 0.72,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(
|
||||||
|
height: constants.padding * 2.0,
|
||||||
|
),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
controller: _userController,
|
controller: _userController,
|
||||||
@@ -261,10 +292,11 @@ class _AuthScreenState extends State<AuthScreen> {
|
|||||||
doLogin(auth);
|
doLogin(auth);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(height: constants.padding),
|
const SizedBox(
|
||||||
|
height: constants.padding * 2.0,
|
||||||
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 44,
|
height: 46,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: _enabled
|
onPressed: _enabled
|
||||||
? () {
|
? () {
|
||||||
@@ -272,9 +304,13 @@ class _AuthScreenState extends State<AuthScreen> {
|
|||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: scheme.primary
|
||||||
|
.withValues(alpha: 0.45),
|
||||||
|
disabledBackgroundColor: scheme.primary
|
||||||
|
.withValues(alpha: 0.15),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(
|
borderRadius: BorderRadius.circular(
|
||||||
12,
|
constants.borderRadiusSmall,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@@ -1,9 +1,14 @@
|
|||||||
|
// edit_mount_screen.dart
|
||||||
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'dart:ui';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:repertory/constants.dart' as constants;
|
||||||
import 'package:repertory/models/auth.dart';
|
import 'package:repertory/models/auth.dart';
|
||||||
import 'package:repertory/models/mount.dart';
|
import 'package:repertory/models/mount.dart';
|
||||||
|
import 'package:repertory/widgets/aurora_sweep.dart';
|
||||||
import 'package:repertory/widgets/mount_settings.dart';
|
import 'package:repertory/widgets/mount_settings.dart';
|
||||||
|
|
||||||
class EditMountScreen extends StatefulWidget {
|
class EditMountScreen extends StatefulWidget {
|
||||||
@@ -20,41 +25,273 @@ class _EditMountScreenState extends State<EditMountScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final scheme = Theme.of(context).colorScheme;
|
||||||
|
final textTheme = Theme.of(context).textTheme;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
body: SafeArea(
|
||||||
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
child: Stack(
|
||||||
title: Text(widget.title),
|
children: [
|
||||||
actions: [
|
// Background gradient
|
||||||
Row(
|
Container(
|
||||||
children: [
|
width: double.infinity,
|
||||||
Row(
|
height: double.infinity,
|
||||||
children: [
|
decoration: const BoxDecoration(
|
||||||
const Text("Advanced"),
|
gradient: LinearGradient(
|
||||||
IconButton(
|
begin: Alignment.topLeft,
|
||||||
icon: Icon(
|
end: Alignment.bottomRight,
|
||||||
_showAdvanced ? Icons.toggle_on : Icons.toggle_off,
|
colors: constants.gradientColors,
|
||||||
),
|
),
|
||||||
onPressed:
|
),
|
||||||
() => setState(() => _showAdvanced = !_showAdvanced),
|
),
|
||||||
|
// 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,
|
||||||
),
|
),
|
||||||
],
|
child: Row(
|
||||||
),
|
children: [
|
||||||
Consumer<Auth>(
|
// Back tile (glassy)
|
||||||
builder: (context, auth, _) {
|
Material(
|
||||||
return IconButton(
|
color: Colors.transparent,
|
||||||
icon: const Icon(Icons.logout),
|
child: InkWell(
|
||||||
onPressed: () => auth.logoff(),
|
borderRadius: BorderRadius.circular(
|
||||||
);
|
constants.borderRadius,
|
||||||
},
|
),
|
||||||
),
|
onTap: () {
|
||||||
],
|
Navigator.of(context).pop();
|
||||||
),
|
},
|
||||||
],
|
child: Ink(
|
||||||
),
|
width: 40,
|
||||||
body: MountSettingsWidget(
|
height: 40,
|
||||||
mount: widget.mount,
|
decoration: BoxDecoration(
|
||||||
settings: jsonDecode(jsonEncode(widget.mount.mountConfig.settings)),
|
color: scheme.surface.withValues(alpha: 0.40),
|
||||||
showAdvanced: _showAdvanced,
|
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<Auth>(
|
||||||
|
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<EditMountScreen> {
|
|||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
super.setState(fn);
|
super.setState(fn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,15 @@
|
|||||||
|
// edit_settings_screen.dart
|
||||||
|
|
||||||
import 'dart:convert' show jsonDecode, jsonEncode;
|
import 'dart:convert' show jsonDecode, jsonEncode;
|
||||||
|
|
||||||
|
import 'dart:ui';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:repertory/constants.dart' as constants;
|
||||||
import 'package:repertory/helpers.dart';
|
import 'package:repertory/helpers.dart';
|
||||||
import 'package:repertory/models/auth.dart';
|
import 'package:repertory/models/auth.dart';
|
||||||
|
import 'package:repertory/widgets/aurora_sweep.dart';
|
||||||
import 'package:repertory/widgets/ui_settings.dart';
|
import 'package:repertory/widgets/ui_settings.dart';
|
||||||
|
|
||||||
class EditSettingsScreen extends StatefulWidget {
|
class EditSettingsScreen extends StatefulWidget {
|
||||||
@@ -18,35 +23,216 @@ class EditSettingsScreen extends StatefulWidget {
|
|||||||
class _EditSettingsScreenState extends State<EditSettingsScreen> {
|
class _EditSettingsScreenState extends State<EditSettingsScreen> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
final scheme = Theme.of(context).colorScheme;
|
||||||
appBar: AppBar(
|
final textTheme = Theme.of(context).textTheme;
|
||||||
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
|
||||||
title: Text(widget.title),
|
|
||||||
actions: [
|
|
||||||
Consumer<Auth>(
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
return UISettingsWidget(
|
return Scaffold(
|
||||||
origSettings: jsonDecode(jsonEncode(snapshot.requireData)),
|
body: SafeArea(
|
||||||
settings: snapshot.requireData,
|
child: Stack(
|
||||||
showAdvanced: false,
|
children: [
|
||||||
);
|
// Background
|
||||||
},
|
Container(
|
||||||
future: _grabSettings(),
|
width: double.infinity,
|
||||||
initialData: <String, dynamic>{},
|
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<Auth>(
|
||||||
|
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<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,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: constants.padding),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -81,7 +267,6 @@ class _EditSettingsScreenState extends State<EditSettingsScreen> {
|
|||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
super.setState(fn);
|
super.setState(fn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,12 @@
|
|||||||
|
// home_screen.dart
|
||||||
|
|
||||||
|
import 'dart:ui';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:repertory/constants.dart' as constants;
|
import 'package:repertory/constants.dart' as constants;
|
||||||
import 'package:repertory/models/auth.dart';
|
import 'package:repertory/models/auth.dart';
|
||||||
import 'package:repertory/models/settings.dart';
|
|
||||||
import 'package:repertory/widgets/mount_list_widget.dart';
|
import 'package:repertory/widgets/mount_list_widget.dart';
|
||||||
|
import 'package:repertory/widgets/aurora_sweep.dart';
|
||||||
|
|
||||||
class HomeScreen extends StatefulWidget {
|
class HomeScreen extends StatefulWidget {
|
||||||
final String title;
|
final String title;
|
||||||
@@ -16,55 +19,189 @@ class HomeScreen extends StatefulWidget {
|
|||||||
class _HomeScreeState extends State<HomeScreen> {
|
class _HomeScreeState extends State<HomeScreen> {
|
||||||
@override
|
@override
|
||||||
Widget build(context) {
|
Widget build(context) {
|
||||||
|
final scheme = Theme.of(context).colorScheme;
|
||||||
|
final textTheme = Theme.of(context).textTheme;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
body: SafeArea(
|
||||||
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
child: Stack(
|
||||||
leading: IconButton(
|
children: [
|
||||||
onPressed: () => Navigator.pushNamed(context, '/settings'),
|
Container(
|
||||||
icon: const Icon(Icons.storage),
|
width: double.infinity,
|
||||||
),
|
height: double.infinity,
|
||||||
title: Text(widget.title),
|
decoration: const BoxDecoration(
|
||||||
actions: [
|
gradient: LinearGradient(
|
||||||
const Text("Auto-start"),
|
begin: Alignment.topLeft,
|
||||||
Consumer<Settings>(
|
end: Alignment.bottomRight,
|
||||||
builder: (context, settings, _) {
|
colors: constants.gradientColors,
|
||||||
return IconButton(
|
|
||||||
icon: Icon(
|
|
||||||
settings.autoStart ? Icons.toggle_on : Icons.toggle_off,
|
|
||||||
),
|
),
|
||||||
onPressed: () => settings.setAutoStart(!settings.autoStart),
|
),
|
||||||
);
|
),
|
||||||
},
|
const AuroraSweep(
|
||||||
),
|
enabled: true,
|
||||||
const SizedBox(width: constants.padding),
|
duration: Duration(seconds: 28),
|
||||||
Consumer<Auth>(
|
primaryAlphaA: 0.04,
|
||||||
builder: (context, auth, _) {
|
primaryAlphaB: 0.03,
|
||||||
return IconButton(
|
),
|
||||||
icon: const Icon(Icons.logout),
|
Positioned.fill(
|
||||||
onPressed: () => auth.logoff(),
|
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<Auth>(
|
||||||
|
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),
|
padding: const EdgeInsets.all(constants.padding),
|
||||||
child: MountListWidget(),
|
child: Hero(
|
||||||
),
|
tag: 'add_mount_fab',
|
||||||
floatingActionButton: FloatingActionButton(
|
child: Material(
|
||||||
onPressed: () => Navigator.pushNamed(context, '/add'),
|
color: Colors.transparent,
|
||||||
tooltip: 'Add Mount',
|
elevation: 12, // match card depth
|
||||||
child: const Icon(Icons.add),
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,8 @@ import 'package:collection/collection.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:repertory/constants.dart' as constants;
|
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';
|
import 'package:settings_ui/settings_ui.dart';
|
||||||
|
|
||||||
void createBooleanSetting(
|
void createBooleanSetting(
|
||||||
@@ -57,16 +58,14 @@ void createIntListSetting(
|
|||||||
value: value.toString(),
|
value: value.toString(),
|
||||||
onChanged: (newValue) {
|
onChanged: (newValue) {
|
||||||
setState(
|
setState(
|
||||||
() =>
|
() => settings[key] = int.parse(
|
||||||
settings[key] = int.parse(
|
newValue ?? defaultValue.toString(),
|
||||||
newValue ?? defaultValue.toString(),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
items:
|
items: valueList.map<DropdownMenuItem<String>>((item) {
|
||||||
valueList.map<DropdownMenuItem<String>>((item) {
|
return DropdownMenuItem<String>(value: item, child: Text(item));
|
||||||
return DropdownMenuItem<String>(value: item, child: Text(item));
|
}).toList(),
|
||||||
}).toList(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -95,42 +94,40 @@ void createIntSetting(
|
|||||||
value: Text(value.toString()),
|
value: Text(value.toString()),
|
||||||
onPressed: (_) {
|
onPressed: (_) {
|
||||||
String updatedValue = value.toString();
|
String updatedValue = value.toString();
|
||||||
showDialog(
|
doShowDialog(
|
||||||
context: context,
|
context,
|
||||||
builder: (context) {
|
AlertDialog(
|
||||||
return AlertDialog(
|
actions: [
|
||||||
actions: [
|
TextButton(
|
||||||
TextButton(
|
child: const Text('Cancel'),
|
||||||
child: const Text('Cancel'),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
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,
|
|
||||||
),
|
),
|
||||||
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;
|
String updatedValue2 = value;
|
||||||
bool hidePassword1 = true;
|
bool hidePassword1 = true;
|
||||||
bool hidePassword2 = true;
|
bool hidePassword2 = true;
|
||||||
showDialog(
|
doShowDialog(
|
||||||
context: context,
|
context,
|
||||||
builder: (context) {
|
StatefulBuilder(
|
||||||
return StatefulBuilder(
|
builder: (context, setDialogState) {
|
||||||
builder: (context, setDialogState) {
|
return AlertDialog(
|
||||||
return AlertDialog(
|
actions: [
|
||||||
actions: [
|
TextButton(
|
||||||
TextButton(
|
child: const Text('Cancel'),
|
||||||
child: const Text('Cancel'),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
),
|
||||||
),
|
TextButton(
|
||||||
TextButton(
|
child: const Text('OK'),
|
||||||
child: const Text('OK'),
|
onPressed: () {
|
||||||
onPressed: () {
|
if (updatedValue1 != updatedValue2) {
|
||||||
if (updatedValue1 != updatedValue2) {
|
return displayErrorMessage(
|
||||||
return displayErrorMessage(
|
context,
|
||||||
context,
|
"Setting '$key' does not match",
|
||||||
"Setting '$key' does not match",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final result = validators.firstWhereOrNull(
|
|
||||||
(validator) => !validator(updatedValue1),
|
|
||||||
);
|
);
|
||||||
if (result != null) {
|
}
|
||||||
return displayErrorMessage(
|
|
||||||
context,
|
|
||||||
"Setting '$key' is not valid",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() => settings[key] = updatedValue1);
|
final result = validators.firstWhereOrNull(
|
||||||
Navigator.of(context).pop();
|
(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,
|
title: createSettingTitle(context, key, description),
|
||||||
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),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -313,10 +306,9 @@ void createStringListSetting(
|
|||||||
value: DropdownButton<String>(
|
value: DropdownButton<String>(
|
||||||
value: value,
|
value: value,
|
||||||
onChanged: (newValue) => setState(() => settings[key] = newValue),
|
onChanged: (newValue) => setState(() => settings[key] = newValue),
|
||||||
items:
|
items: valueList.map<DropdownMenuItem<String>>((item) {
|
||||||
valueList.map<DropdownMenuItem<String>>((item) {
|
return DropdownMenuItem<String>(value: item, child: Text(item));
|
||||||
return DropdownMenuItem<String>(value: item, child: Text(item));
|
}).toList(),
|
||||||
}).toList(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -345,43 +337,41 @@ void createStringSetting(
|
|||||||
value: Text(value),
|
value: Text(value),
|
||||||
onPressed: (_) {
|
onPressed: (_) {
|
||||||
String updatedValue = value;
|
String updatedValue = value;
|
||||||
showDialog(
|
doShowDialog(
|
||||||
context: context,
|
context,
|
||||||
builder: (context) {
|
AlertDialog(
|
||||||
return AlertDialog(
|
actions: [
|
||||||
actions: [
|
TextButton(
|
||||||
TextButton(
|
child: const Text('Cancel'),
|
||||||
child: const Text('Cancel'),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
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,
|
|
||||||
),
|
),
|
||||||
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),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
122
web/repertory/lib/widgets/aurora_sweep.dart
Normal file
122
web/repertory/lib/widgets/aurora_sweep.dart
Normal file
@@ -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<AuroraSweep> createState() => _AuroraSweepState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AuroraSweepState extends State<AuroraSweep>
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,3 +1,5 @@
|
|||||||
|
// mount_settings.dart
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:repertory/constants.dart' as constants;
|
import 'package:repertory/constants.dart' as constants;
|
||||||
@@ -35,6 +37,20 @@ class MountSettingsWidget extends StatefulWidget {
|
|||||||
class _MountSettingsWidgetState extends State<MountSettingsWidget> {
|
class _MountSettingsWidgetState extends State<MountSettingsWidget> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
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<SettingsTile> commonSettings = [];
|
List<SettingsTile> commonSettings = [];
|
||||||
List<SettingsTile> encryptConfigSettings = [];
|
List<SettingsTile> encryptConfigSettings = [];
|
||||||
List<SettingsTile> hostConfigSettings = [];
|
List<SettingsTile> hostConfigSettings = [];
|
||||||
@@ -363,11 +379,8 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
|
|||||||
description: getSettingDescription('$key.$subKey'),
|
description: getSettingDescription('$key.$subKey'),
|
||||||
validators: [
|
validators: [
|
||||||
...getSettingValidators('$key.$subKey'),
|
...getSettingValidators('$key.$subKey'),
|
||||||
(value) =>
|
(value) => !Provider.of<MountList>(context, listen: false)
|
||||||
!Provider.of<MountList>(
|
.hasBucketName(
|
||||||
context,
|
|
||||||
listen: false,
|
|
||||||
).hasBucketName(
|
|
||||||
widget.mount.type,
|
widget.mount.type,
|
||||||
value,
|
value,
|
||||||
excludeName: widget.mount.name,
|
excludeName: widget.mount.name,
|
||||||
@@ -383,6 +396,9 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
|
|||||||
|
|
||||||
return SettingsList(
|
return SettingsList(
|
||||||
shrinkWrap: false,
|
shrinkWrap: false,
|
||||||
|
platform: DevicePlatform.device,
|
||||||
|
lightTheme: theme,
|
||||||
|
darkTheme: theme,
|
||||||
sections: [
|
sections: [
|
||||||
if (encryptConfigSettings.isNotEmpty)
|
if (encryptConfigSettings.isNotEmpty)
|
||||||
SettingsSection(
|
SettingsSection(
|
||||||
@@ -412,10 +428,9 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
|
|||||||
if (remoteMountSettings.isNotEmpty)
|
if (remoteMountSettings.isNotEmpty)
|
||||||
SettingsSection(
|
SettingsSection(
|
||||||
title: const Text('Remote Mount'),
|
title: const Text('Remote Mount'),
|
||||||
tiles:
|
tiles: (widget.settings['RemoteMount']['Enable'] as bool)
|
||||||
widget.settings['RemoteMount']['Enable'] as bool
|
? remoteMountSettings
|
||||||
? remoteMountSettings
|
: [remoteMountSettings[0]],
|
||||||
: [remoteMountSettings[0]],
|
|
||||||
),
|
),
|
||||||
if (commonSettings.isNotEmpty)
|
if (commonSettings.isNotEmpty)
|
||||||
SettingsSection(title: const Text('Settings'), tiles: commonSettings),
|
SettingsSection(title: const Text('Settings'), tiles: commonSettings),
|
||||||
@@ -617,38 +632,6 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@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<Auth>(
|
|
||||||
constants.navigatorKey.currentContext!,
|
|
||||||
listen: false,
|
|
||||||
).key;
|
|
||||||
convertAllToString(settings, key).then((map) {
|
|
||||||
map.forEach((key, value) {
|
|
||||||
if (value is Map<String, dynamic>) {
|
|
||||||
value.forEach((subKey, subValue) {
|
|
||||||
mount.setValue('$key.$subKey', subValue);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mount.setValue(key, value);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _parseS3Config(List<SettingsTile> s3ConfigSettings, String key, value) {
|
void _parseS3Config(List<SettingsTile> s3ConfigSettings, String key, value) {
|
||||||
value.forEach((subKey, subValue) {
|
value.forEach((subKey, subValue) {
|
||||||
switch (subKey) {
|
switch (subKey) {
|
||||||
@@ -686,11 +669,8 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
|
|||||||
description: getSettingDescription('$key.$subKey'),
|
description: getSettingDescription('$key.$subKey'),
|
||||||
validators: [
|
validators: [
|
||||||
...getSettingValidators('$key.$subKey'),
|
...getSettingValidators('$key.$subKey'),
|
||||||
(value) =>
|
(value) => !Provider.of<MountList>(context, listen: false)
|
||||||
!Provider.of<MountList>(
|
.hasBucketName(
|
||||||
context,
|
|
||||||
listen: false,
|
|
||||||
).hasBucketName(
|
|
||||||
widget.mount.type,
|
widget.mount.type,
|
||||||
value,
|
value,
|
||||||
excludeName: widget.mount.name,
|
excludeName: widget.mount.name,
|
||||||
@@ -1016,16 +996,45 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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<Auth>(
|
||||||
|
constants.navigatorKey.currentContext!,
|
||||||
|
listen: false,
|
||||||
|
).key;
|
||||||
|
convertAllToString(settings, key).then((map) {
|
||||||
|
map.forEach((key, value) {
|
||||||
|
if (value is Map<String, dynamic>) {
|
||||||
|
value.forEach((subKey, subValue) {
|
||||||
|
mount.setValue('$key.$subKey', subValue);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mount.setValue(key, value);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void setState(VoidCallback fn) {
|
void setState(VoidCallback fn) {
|
||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (widget.onChanged != null) {
|
if (widget.onChanged != null) {
|
||||||
widget.onChanged!();
|
widget.onChanged!();
|
||||||
}
|
}
|
||||||
|
|
||||||
super.setState(fn);
|
super.setState(fn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:repertory/constants.dart' as constants;
|
import 'package:repertory/constants.dart' as constants;
|
||||||
import 'package:repertory/helpers.dart';
|
import 'package:repertory/helpers.dart';
|
||||||
@@ -22,98 +21,165 @@ class _MountWidgetState extends State<MountWidget> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final scheme = Theme.of(context).colorScheme;
|
||||||
|
final textTheme = Theme.of(context).textTheme;
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
margin: const EdgeInsets.all(0.0),
|
margin: const EdgeInsets.all(0.0),
|
||||||
child: Consumer<Mount>(
|
elevation: 12,
|
||||||
builder: (context, Mount mount, _) {
|
color: scheme.primary.withValues(alpha: 0.15),
|
||||||
final textColor = Theme.of(context).colorScheme.onSurface;
|
shape: RoundedRectangleBorder(
|
||||||
final subTextColor = Theme.of(context).brightness == Brightness.dark
|
borderRadius: BorderRadius.circular(constants.borderRadiusSmall),
|
||||||
? Colors.white38
|
side: BorderSide(
|
||||||
: Colors.black87;
|
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<Mount>(
|
||||||
|
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(
|
final nameText = SelectableText(
|
||||||
formatMountName(mount.type, mount.name),
|
formatMountName(mount.type, mount.name),
|
||||||
style: TextStyle(color: subTextColor),
|
style: subStyle,
|
||||||
);
|
);
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
isThreeLine: true,
|
isThreeLine: true,
|
||||||
leading: IconButton(
|
contentPadding: const EdgeInsets.all(constants.paddingSmall),
|
||||||
icon: Icon(Icons.settings, color: textColor),
|
leading: Container(
|
||||||
onPressed: () =>
|
width: 46,
|
||||||
Navigator.pushNamed(context, '/edit', arguments: mount),
|
height: 46,
|
||||||
),
|
decoration: BoxDecoration(
|
||||||
subtitle: Column(
|
color: scheme.primary.withValues(alpha: 0.12),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
borderRadius: BorderRadius.circular(
|
||||||
children: [
|
constants.borderRadiusSmall,
|
||||||
nameText,
|
|
||||||
SelectableText(
|
|
||||||
mount.path.isEmpty && mount.mounted == null
|
|
||||||
? 'loading...'
|
|
||||||
: mount.path.isEmpty
|
|
||||||
? '<mount location not set>'
|
|
||||||
: 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);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
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(
|
icon: Icon(
|
||||||
mount.mounted == null
|
Icons.settings,
|
||||||
? Icons.hourglass_top
|
color: scheme.onSurface.withValues(alpha: 0.92),
|
||||||
: mount.mounted!
|
size: 22,
|
||||||
? Icons.toggle_on
|
|
||||||
: Icons.toggle_off,
|
|
||||||
color: mount.mounted ?? false
|
|
||||||
? Theme.of(context).colorScheme.primary
|
|
||||||
: subTextColor,
|
|
||||||
),
|
),
|
||||||
tooltip: mount.mounted == null
|
onPressed: () {
|
||||||
? ''
|
Navigator.pushNamed(context, '/edit', arguments: mount);
|
||||||
: mount.mounted!
|
},
|
||||||
? 'Unmount'
|
|
||||||
: 'Mount',
|
|
||||||
onPressed: _createMountHandler(context, 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 location not set>'
|
||||||
|
: 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
|
return _enabled && mount.mounted != null
|
||||||
? () async {
|
? () async {
|
||||||
if (mount.mounted == null) {
|
if (mount.mounted == null) {
|
||||||
@@ -128,15 +194,16 @@ class _MountWidgetState extends State<MountWidget> {
|
|||||||
|
|
||||||
final location = await _getMountLocation(context, mount);
|
final location = await _getMountLocation(context, mount);
|
||||||
|
|
||||||
cleanup() {
|
void cleanup() {
|
||||||
setState(() {
|
setState(() {
|
||||||
_enabled = true;
|
_enabled = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mounted && location == null) {
|
if (!context.mounted && location == null) {
|
||||||
displayErrorMessage(context, "Mount location is not set");
|
displayErrorMessage(context, "Mount location is not set");
|
||||||
return cleanup();
|
cleanup();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final success = await mount.mount(mounted, location: location);
|
final success = await mount.mount(mounted, location: location);
|
||||||
@@ -144,7 +211,8 @@ class _MountWidgetState extends State<MountWidget> {
|
|||||||
mounted ||
|
mounted ||
|
||||||
constants.navigatorKey.currentContext == null ||
|
constants.navigatorKey.currentContext == null ||
|
||||||
!constants.navigatorKey.currentContext!.mounted) {
|
!constants.navigatorKey.currentContext!.mounted) {
|
||||||
return cleanup();
|
cleanup();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
displayErrorMessage(
|
displayErrorMessage(
|
||||||
@@ -163,7 +231,7 @@ class _MountWidgetState extends State<MountWidget> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> _getMountLocation(context, Mount mount) async {
|
Future<String?> _getMountLocation(BuildContext context, Mount mount) async {
|
||||||
if (mount.mounted ?? false) {
|
if (mount.mounted ?? false) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -197,7 +265,6 @@ class _MountWidgetState extends State<MountWidget> {
|
|||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
super.setState(fn);
|
super.setState(fn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
// ui_settings.dart
|
||||||
|
|
||||||
import 'dart:convert' show jsonEncode;
|
import 'dart:convert' show jsonEncode;
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -35,6 +37,22 @@ class UISettingsWidget extends StatefulWidget {
|
|||||||
class _UISettingsWidgetState extends State<UISettingsWidget> {
|
class _UISettingsWidgetState extends State<UISettingsWidget> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
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<SettingsTile> commonSettings = [];
|
List<SettingsTile> commonSettings = [];
|
||||||
|
|
||||||
widget.settings.forEach((key, value) {
|
widget.settings.forEach((key, value) {
|
||||||
@@ -56,6 +74,7 @@ class _UISettingsWidgetState extends State<UISettingsWidget> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'ApiPort':
|
case 'ApiPort':
|
||||||
{
|
{
|
||||||
createIntSetting(
|
createIntSetting(
|
||||||
@@ -73,6 +92,7 @@ class _UISettingsWidgetState extends State<UISettingsWidget> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'ApiUser':
|
case 'ApiUser':
|
||||||
{
|
{
|
||||||
createStringSetting(
|
createStringSetting(
|
||||||
@@ -96,6 +116,9 @@ class _UISettingsWidgetState extends State<UISettingsWidget> {
|
|||||||
|
|
||||||
return SettingsList(
|
return SettingsList(
|
||||||
shrinkWrap: false,
|
shrinkWrap: false,
|
||||||
|
platform: DevicePlatform.device,
|
||||||
|
lightTheme: theme,
|
||||||
|
darkTheme: theme,
|
||||||
sections: [
|
sections: [
|
||||||
SettingsSection(title: const Text('Settings'), tiles: commonSettings),
|
SettingsSection(title: const Text('Settings'), tiles: commonSettings),
|
||||||
],
|
],
|
||||||
@@ -106,11 +129,10 @@ class _UISettingsWidgetState extends State<UISettingsWidget> {
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
final settings = getChanged(widget.origSettings, widget.settings);
|
final settings = getChanged(widget.origSettings, widget.settings);
|
||||||
if (settings.isNotEmpty) {
|
if (settings.isNotEmpty) {
|
||||||
final key =
|
final key = Provider.of<Auth>(
|
||||||
Provider.of<Auth>(
|
constants.navigatorKey.currentContext!,
|
||||||
constants.navigatorKey.currentContext!,
|
listen: false,
|
||||||
listen: false,
|
).key;
|
||||||
).key;
|
|
||||||
convertAllToString(settings, key)
|
convertAllToString(settings, key)
|
||||||
.then((map) async {
|
.then((map) async {
|
||||||
try {
|
try {
|
||||||
@@ -149,7 +171,6 @@ class _UISettingsWidgetState extends State<UISettingsWidget> {
|
|||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
super.setState(fn);
|
super.setState(fn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -38,7 +38,7 @@ dependencies:
|
|||||||
http: ^1.3.0
|
http: ^1.3.0
|
||||||
provider: ^6.1.2
|
provider: ^6.1.2
|
||||||
settings_ui: ^2.0.2
|
settings_ui: ^2.0.2
|
||||||
sodium_libs: ^3.4.4+1
|
sodium_libs: ^3.4.6+1
|
||||||
convert: ^3.1.2
|
convert: ^3.1.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 917 B After Width: | Height: | Size: 368 B |
@@ -1,44 +1,37 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html><html><head>
|
||||||
<html>
|
<!--
|
||||||
<head>
|
If you are serving your web app in a path other than the root, change the
|
||||||
<!--
|
href value below to reflect the base path you are serving from.
|
||||||
If you are serving your web app in a path other than the root, change the
|
|
||||||
href value below to reflect the base path you are serving from.
|
|
||||||
|
|
||||||
The path provided below has to start and end with a slash "/" in order for
|
The path provided below has to start and end with a slash "/" in order for
|
||||||
it to work correctly.
|
it to work correctly.
|
||||||
|
|
||||||
For more details:
|
For more details:
|
||||||
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
|
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
|
||||||
|
|
||||||
This is a placeholder for base href that will be replaced by the value of
|
This is a placeholder for base href that will be replaced by the value of
|
||||||
the `--base-href` argument provided to `flutter build`.
|
the `--base-href` argument provided to `flutter build`.
|
||||||
-->
|
-->
|
||||||
<base href="$FLUTTER_BASE_HREF">
|
<base href="$FLUTTER_BASE_HREF">
|
||||||
|
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||||
<meta name="description" content="A new Flutter project.">
|
<meta name="description" content="A new Flutter project.">
|
||||||
|
|
||||||
<!-- iOS meta tags & icons -->
|
<!-- iOS meta tags & icons -->
|
||||||
<meta name="mobile-web-app-capable" content="yes">
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||||
<meta name="apple-mobile-web-app-title" content="repertory">
|
<meta name="apple-mobile-web-app-title" content="repertory">
|
||||||
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
||||||
|
|
||||||
<!-- Favicon -->
|
<!-- Favicon -->
|
||||||
<link rel="icon" type="image/png" href="favicon.png">
|
<link rel="icon" type="image/png" href="favicon.png">
|
||||||
|
|
||||||
<title>repertory</title>
|
<title>repertory</title>
|
||||||
<link rel="manifest" href="manifest.json">
|
<link rel="manifest" href="manifest.json">
|
||||||
<script type="text/javascript" src="sodium.js" async="true"></script>
|
<script type="text/javascript" src="sodium.js" async="true"></script></head>
|
||||||
</head>
|
<body>
|
||||||
<body>
|
<script src="flutter_bootstrap.js" async=""></script>
|
||||||
<script>
|
|
||||||
window.flutterConfiguration = {
|
|
||||||
canvasKitBaseUrl: "/canvaskit/"
|
</body></html>
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<script src="flutter_bootstrap.js" async=""></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
Reference in New Issue
Block a user