file_caption_widget.dart 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. import 'package:flutter/material.dart';
  2. import "package:photos/generated/l10n.dart";
  3. import 'package:photos/models/file.dart';
  4. import 'package:photos/theme/ente_theme.dart';
  5. import 'package:photos/ui/components/keyboard/keybiard_oveylay.dart';
  6. import 'package:photos/ui/components/keyboard/keyboard_top_button.dart';
  7. import 'package:photos/utils/magic_util.dart';
  8. class FileCaptionReadyOnly extends StatelessWidget {
  9. final String caption;
  10. const FileCaptionReadyOnly({super.key, required this.caption});
  11. @override
  12. Widget build(BuildContext context) {
  13. final colorScheme = getEnteColorScheme(context);
  14. final textTheme = getEnteTextTheme(context);
  15. return Padding(
  16. padding: const EdgeInsets.all(4.0),
  17. child: ConstrainedBox(
  18. constraints: const BoxConstraints(
  19. minHeight: 32.0,
  20. minWidth: double.infinity,
  21. maxHeight: 200.0,
  22. maxWidth: double.infinity,
  23. ),
  24. child: DecoratedBox(
  25. decoration: BoxDecoration(
  26. color: colorScheme.fillFaint,
  27. borderRadius: BorderRadius.circular(4),
  28. ),
  29. child: SingleChildScrollView(
  30. scrollDirection: Axis.vertical,
  31. child: Padding(
  32. padding: const EdgeInsets.all(12.0),
  33. child: Text(
  34. caption,
  35. style: textTheme.small,
  36. ),
  37. ),
  38. ),
  39. ),
  40. ),
  41. );
  42. }
  43. }
  44. class FileCaptionWidget extends StatefulWidget {
  45. final File file;
  46. const FileCaptionWidget({required this.file, super.key});
  47. @override
  48. State<FileCaptionWidget> createState() => _FileCaptionWidgetState();
  49. }
  50. class _FileCaptionWidgetState extends State<FileCaptionWidget> {
  51. static const int maxLength = 5000;
  52. // counterThreshold represents the nun of char after which
  53. // currentLength/maxLength will show up
  54. static const int counterThreshold = 1000;
  55. int currentLength = 0;
  56. final _textController = TextEditingController();
  57. final _focusNode = FocusNode();
  58. String? editedCaption;
  59. late String defaultHintText = S.of(context).fileInfoAddDescHint;
  60. String hintText = '';
  61. Widget? keyboardTopButtons;
  62. @override
  63. void initState() {
  64. _focusNode.addListener(_focusNodeListener);
  65. editedCaption = widget.file.caption;
  66. if (editedCaption != null && editedCaption!.isNotEmpty) {
  67. hintText = editedCaption!;
  68. }
  69. super.initState();
  70. }
  71. @override
  72. void dispose() {
  73. if (editedCaption != null) {
  74. editFileCaption(null, widget.file, editedCaption!);
  75. }
  76. _textController.dispose();
  77. _focusNode.removeListener(_focusNodeListener);
  78. super.dispose();
  79. }
  80. @override
  81. Widget build(BuildContext context) {
  82. if (hintText.isEmpty) {
  83. hintText = defaultHintText;
  84. }
  85. final colorScheme = getEnteColorScheme(context);
  86. final textTheme = getEnteTextTheme(context);
  87. return TextField(
  88. onSubmitted: (value) async {
  89. await _onDoneClick(context);
  90. },
  91. controller: _textController,
  92. focusNode: _focusNode,
  93. decoration: InputDecoration(
  94. counterStyle: textTheme.mini.copyWith(color: colorScheme.textMuted),
  95. counterText: currentLength >= counterThreshold
  96. ? currentLength.toString() + " / " + maxLength.toString()
  97. : "",
  98. contentPadding: const EdgeInsets.all(16),
  99. border: OutlineInputBorder(
  100. borderRadius: BorderRadius.circular(2),
  101. borderSide: const BorderSide(
  102. width: 0,
  103. style: BorderStyle.none,
  104. ),
  105. ),
  106. focusedBorder: OutlineInputBorder(
  107. borderRadius: BorderRadius.circular(2),
  108. borderSide: const BorderSide(
  109. width: 0,
  110. style: BorderStyle.none,
  111. ),
  112. ),
  113. filled: true,
  114. fillColor: colorScheme.fillFaint,
  115. hintText: hintText,
  116. hintStyle:
  117. hintText == defaultHintText ? textTheme.miniMuted : textTheme.mini,
  118. ),
  119. style: textTheme.mini,
  120. cursorWidth: 1.5,
  121. maxLength: maxLength,
  122. minLines: 1,
  123. maxLines: 10,
  124. textCapitalization: TextCapitalization.sentences,
  125. keyboardType: TextInputType.multiline,
  126. onChanged: (value) {
  127. setState(() {
  128. hintText = defaultHintText;
  129. currentLength = value.length;
  130. editedCaption = value;
  131. });
  132. },
  133. );
  134. }
  135. Future<void> _onDoneClick(BuildContext context) async {
  136. if (editedCaption != null) {
  137. final isSuccesful =
  138. await editFileCaption(context, widget.file, editedCaption!);
  139. if (isSuccesful) {
  140. if (mounted) {
  141. Navigator.pop(context);
  142. }
  143. }
  144. }
  145. }
  146. void onCancelTap() {
  147. _textController.text = widget.file.caption ?? '';
  148. _focusNode.unfocus();
  149. editedCaption = null;
  150. }
  151. void onDoneTap() {
  152. _focusNode.unfocus();
  153. _onDoneClick(context);
  154. }
  155. void _focusNodeListener() {
  156. final caption = widget.file.caption;
  157. if (_focusNode.hasFocus && caption != null) {
  158. _textController.text = caption;
  159. editedCaption = caption;
  160. }
  161. final bool hasFocus = _focusNode.hasFocus;
  162. keyboardTopButtons ??= KeyboardTopButton(
  163. onDoneTap: onDoneTap,
  164. onCancelTap: onCancelTap,
  165. );
  166. if (hasFocus) {
  167. KeyboardOverlay.showOverlay(context, keyboardTopButtons!);
  168. } else {
  169. KeyboardOverlay.removeOverlay();
  170. }
  171. }
  172. }