description_input.dart 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. import 'package:easy_localization/easy_localization.dart';
  2. import 'package:flutter/material.dart';
  3. import 'package:flutter_hooks/flutter_hooks.dart';
  4. import 'package:hooks_riverpod/hooks_riverpod.dart';
  5. import 'package:immich_mobile/modules/asset_viewer/providers/asset_description.provider.dart';
  6. import 'package:immich_mobile/shared/models/asset.dart';
  7. import 'package:immich_mobile/shared/providers/user.provider.dart';
  8. import 'package:immich_mobile/shared/ui/immich_toast.dart';
  9. import 'package:logging/logging.dart';
  10. class DescriptionInput extends HookConsumerWidget {
  11. DescriptionInput({
  12. super.key,
  13. required this.asset,
  14. });
  15. final Asset asset;
  16. final Logger _log = Logger('DescriptionInput');
  17. @override
  18. Widget build(BuildContext context, WidgetRef ref) {
  19. final isDarkTheme = Theme.of(context).brightness == Brightness.dark;
  20. final textColor = isDarkTheme ? Colors.white : Colors.black;
  21. final controller = useTextEditingController();
  22. final focusNode = useFocusNode();
  23. final isFocus = useState(false);
  24. final isTextEmpty = useState(controller.text.isEmpty);
  25. final descriptionProvider =
  26. ref.watch(assetDescriptionProvider(asset).notifier);
  27. final description = ref.watch(assetDescriptionProvider(asset));
  28. final owner = ref.watch(currentUserProvider);
  29. final hasError = useState(false);
  30. controller.text = description;
  31. submitDescription(String description) async {
  32. hasError.value = false;
  33. try {
  34. await descriptionProvider.setDescription(
  35. description,
  36. );
  37. } catch (error, stack) {
  38. hasError.value = true;
  39. _log.severe("Error updating description $error", error, stack);
  40. ImmichToast.show(
  41. context: context,
  42. msg: "description_input_submit_error".tr(),
  43. toastType: ToastType.error,
  44. );
  45. }
  46. }
  47. Widget? suffixIcon;
  48. if (hasError.value) {
  49. suffixIcon = const Icon(Icons.warning_outlined);
  50. } else if (!isTextEmpty.value && isFocus.value) {
  51. suffixIcon = IconButton(
  52. onPressed: () {
  53. controller.clear();
  54. isTextEmpty.value = true;
  55. },
  56. icon: Icon(
  57. Icons.cancel_rounded,
  58. color: Colors.grey[500],
  59. ),
  60. splashRadius: 10,
  61. );
  62. }
  63. return TextField(
  64. enabled: owner?.isarId == asset.ownerId,
  65. focusNode: focusNode,
  66. onTap: () => isFocus.value = true,
  67. onChanged: (value) {
  68. isTextEmpty.value = false;
  69. },
  70. onTapOutside: (a) async {
  71. isFocus.value = false;
  72. focusNode.unfocus();
  73. if (description != controller.text) {
  74. await submitDescription(controller.text);
  75. }
  76. },
  77. autofocus: false,
  78. maxLines: null,
  79. keyboardType: TextInputType.multiline,
  80. controller: controller,
  81. style: const TextStyle(
  82. fontSize: 14,
  83. ),
  84. decoration: InputDecoration(
  85. hintText: 'description_input_hint_text'.tr(),
  86. border: InputBorder.none,
  87. hintStyle: TextStyle(
  88. fontWeight: FontWeight.normal,
  89. fontSize: 12,
  90. color: textColor.withOpacity(0.5),
  91. ),
  92. suffixIcon: suffixIcon,
  93. ),
  94. );
  95. }
  96. }