1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-01-06 11:56:46 +01:00
immich/mobile/lib/widgets/settings/networking_settings/endpoint_input.dart

155 lines
4.7 KiB
Dart

import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
import 'package:immich_mobile/providers/auth.provider.dart';
import 'package:immich_mobile/widgets/settings/networking_settings/networking_settings.dart';
class EndpointInput extends StatefulHookConsumerWidget {
const EndpointInput({
super.key,
required this.initialValue,
required this.index,
required this.onValidated,
required this.onDismissed,
this.enabled = true,
});
final AuxilaryEndpoint initialValue;
final int index;
final Function(String url, int index, AuxCheckStatus status) onValidated;
final Function(int index) onDismissed;
final bool enabled;
@override
EndpointInputState createState() => EndpointInputState();
}
class EndpointInputState extends ConsumerState<EndpointInput> {
late final TextEditingController controller;
late final FocusNode focusNode;
late AuxCheckStatus auxCheckStatus;
bool isInputValid = false;
@override
void initState() {
super.initState();
controller = TextEditingController(text: widget.initialValue.url);
focusNode = FocusNode()..addListener(_onOutFocus);
setState(() {
auxCheckStatus = widget.initialValue.status;
});
}
@override
void dispose() {
focusNode.removeListener(_onOutFocus);
focusNode.dispose();
controller.dispose();
super.dispose();
}
void _onOutFocus() {
if (!focusNode.hasFocus && isInputValid) {
validateAuxilaryServerUrl();
}
}
Future<void> validateAuxilaryServerUrl() async {
final url = controller.text;
setState(() => auxCheckStatus = AuxCheckStatus.loading);
final isValid =
await ref.read(authProvider.notifier).validateAuxilaryServerUrl(url);
setState(() {
if (mounted) {
auxCheckStatus = isValid ? AuxCheckStatus.valid : AuxCheckStatus.error;
}
});
widget.onValidated(url, widget.index, auxCheckStatus);
}
String? validateUrl(String? url) {
try {
if (url == null || url.isEmpty || !Uri.parse(url).isAbsolute) {
isInputValid = false;
return 'validate_endpoint_error'.tr();
}
} catch (_) {
isInputValid = false;
return 'validate_endpoint_error'.tr();
}
isInputValid = true;
return null;
}
@override
Widget build(BuildContext context) {
return Dismissible(
key: ValueKey(widget.index.toString()),
direction: DismissDirection.endToStart,
onDismissed: (_) => widget.onDismissed(widget.index),
background: Container(
color: Colors.red,
alignment: Alignment.centerRight,
padding: const EdgeInsets.only(right: 16),
child: const Icon(
Icons.delete,
color: Colors.white,
),
),
child: ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
trailing: ReorderableDragStartListener(
enabled: widget.enabled,
index: widget.index,
child: const Icon(Icons.drag_handle_rounded),
),
leading: NetworkStatusIcon(
key: ValueKey('status_$auxCheckStatus'),
status: auxCheckStatus,
enabled: widget.enabled,
),
subtitle: TextFormField(
enabled: widget.enabled,
onTapOutside: (_) => focusNode.unfocus(),
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: validateUrl,
keyboardType: TextInputType.url,
style: const TextStyle(
fontFamily: 'Inconsolata',
fontWeight: FontWeight.w600,
fontSize: 14,
),
decoration: InputDecoration(
hintText: 'http(s)://immich.domain.com',
contentPadding: const EdgeInsets.all(16),
filled: true,
fillColor: context.colorScheme.surfaceContainer,
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.red[300]!),
borderRadius: const BorderRadius.all(Radius.circular(16)),
),
disabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color:
context.isDarkTheme ? Colors.grey[900]! : Colors.grey[300]!,
),
borderRadius: const BorderRadius.all(Radius.circular(16)),
),
),
controller: controller,
focusNode: focusNode,
),
),
);
}
}