description_input.dart 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  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. useEffect(
  31. () {
  32. controller.text = description;
  33. isTextEmpty.value = description.isEmpty;
  34. return null;
  35. },
  36. [description],
  37. );
  38. submitDescription(String description) async {
  39. hasError.value = false;
  40. try {
  41. await descriptionProvider.setDescription(
  42. description,
  43. );
  44. } catch (error, stack) {
  45. hasError.value = true;
  46. _log.severe("Error updating description $error", error, stack);
  47. ImmichToast.show(
  48. context: context,
  49. msg: "description_input_submit_error".tr(),
  50. toastType: ToastType.error,
  51. );
  52. }
  53. }
  54. Widget? suffixIcon;
  55. if (hasError.value) {
  56. suffixIcon = const Icon(Icons.warning_outlined);
  57. } else if (!isTextEmpty.value && isFocus.value) {
  58. suffixIcon = IconButton(
  59. onPressed: () {
  60. controller.clear();
  61. isTextEmpty.value = true;
  62. },
  63. icon: Icon(
  64. Icons.cancel_rounded,
  65. color: Colors.grey[500],
  66. ),
  67. splashRadius: 10,
  68. );
  69. }
  70. return TextField(
  71. enabled: owner?.isarId == asset.ownerId,
  72. focusNode: focusNode,
  73. onTap: () => isFocus.value = true,
  74. onChanged: (value) {
  75. isTextEmpty.value = false;
  76. },
  77. onTapOutside: (a) async {
  78. isFocus.value = false;
  79. focusNode.unfocus();
  80. if (description != controller.text) {
  81. await submitDescription(controller.text);
  82. }
  83. },
  84. autofocus: false,
  85. maxLines: null,
  86. keyboardType: TextInputType.multiline,
  87. controller: controller,
  88. style: const TextStyle(
  89. fontSize: 14,
  90. ),
  91. decoration: InputDecoration(
  92. hintText: 'description_input_hint_text'.tr(),
  93. border: InputBorder.none,
  94. hintStyle: TextStyle(
  95. fontWeight: FontWeight.normal,
  96. fontSize: 12,
  97. color: textColor.withOpacity(0.5),
  98. ),
  99. suffixIcon: suffixIcon,
  100. ),
  101. );
  102. }
  103. }