You've already forked immich
mirror of
https://github.com/immich-app/immich.git
synced 2025-07-16 07:24:40 +02:00
feat(mobile): Auto switching server URLs (#14437)
This commit is contained in:
@ -0,0 +1,155 @@
|
||||
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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user