file_caption_widget.dart 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. import 'package:flutter/material.dart';
  2. import 'package:photos/core/constants.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. String hintText = fileCaptionDefaultHint;
  60. Widget? keyboardTopButtoms;
  61. @override
  62. void initState() {
  63. _focusNode.addListener(_focusNodeListener);
  64. editedCaption = widget.file.caption;
  65. if (editedCaption != null && editedCaption!.isNotEmpty) {
  66. hintText = editedCaption!;
  67. }
  68. super.initState();
  69. }
  70. @override
  71. void dispose() {
  72. if (editedCaption != null) {
  73. editFileCaption(null, widget.file, editedCaption!);
  74. }
  75. _textController.dispose();
  76. _focusNode.removeListener(_focusNodeListener);
  77. super.dispose();
  78. }
  79. @override
  80. Widget build(BuildContext context) {
  81. final colorScheme = getEnteColorScheme(context);
  82. final textTheme = getEnteTextTheme(context);
  83. return TextField(
  84. onSubmitted: (value) async {
  85. await _onDoneClick(context);
  86. },
  87. controller: _textController,
  88. focusNode: _focusNode,
  89. decoration: InputDecoration(
  90. counterStyle: textTheme.mini.copyWith(color: colorScheme.textMuted),
  91. counterText: currentLength >= counterThreshold
  92. ? currentLength.toString() + " / " + maxLength.toString()
  93. : "",
  94. contentPadding: const EdgeInsets.all(16),
  95. border: OutlineInputBorder(
  96. borderRadius: BorderRadius.circular(2),
  97. borderSide: const BorderSide(
  98. width: 0,
  99. style: BorderStyle.none,
  100. ),
  101. ),
  102. focusedBorder: OutlineInputBorder(
  103. borderRadius: BorderRadius.circular(2),
  104. borderSide: const BorderSide(
  105. width: 0,
  106. style: BorderStyle.none,
  107. ),
  108. ),
  109. filled: true,
  110. fillColor: colorScheme.fillFaint,
  111. hintText: hintText,
  112. hintStyle: hintText == fileCaptionDefaultHint
  113. ? textTheme.small.copyWith(color: colorScheme.textMuted)
  114. : textTheme.small,
  115. ),
  116. style: textTheme.small,
  117. cursorWidth: 1.5,
  118. maxLength: maxLength,
  119. minLines: 1,
  120. maxLines: 10,
  121. textCapitalization: TextCapitalization.sentences,
  122. keyboardType: TextInputType.multiline,
  123. onChanged: (value) {
  124. setState(() {
  125. hintText = fileCaptionDefaultHint;
  126. currentLength = value.length;
  127. editedCaption = value;
  128. });
  129. },
  130. );
  131. }
  132. Future<void> _onDoneClick(BuildContext context) async {
  133. if (editedCaption != null) {
  134. final isSuccesful =
  135. await editFileCaption(context, widget.file, editedCaption!);
  136. if (isSuccesful) {
  137. if (mounted) {
  138. Navigator.pop(context);
  139. }
  140. }
  141. }
  142. }
  143. void onCancelTap() {
  144. _textController.text = widget.file.caption ?? '';
  145. _focusNode.unfocus();
  146. editedCaption = null;
  147. }
  148. void onDoneTap() {
  149. _focusNode.unfocus();
  150. _onDoneClick(context);
  151. }
  152. void _focusNodeListener() {
  153. final caption = widget.file.caption;
  154. if (_focusNode.hasFocus && caption != null) {
  155. _textController.text = caption;
  156. editedCaption = caption;
  157. }
  158. final bool hasFocus = _focusNode.hasFocus;
  159. keyboardTopButtoms ??= KeyboardTopButton(
  160. onDoneTap: onDoneTap,
  161. onCancelTap: onCancelTap,
  162. );
  163. if (hasFocus) {
  164. KeyboardOverlay.showOverlay(context, keyboardTopButtoms!);
  165. } else {
  166. KeyboardOverlay.removeOverlay();
  167. }
  168. }
  169. }