folder_widget.dart 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. import 'dart:io';
  2. import 'package:appflowy/util/file_picker/file_picker_service.dart';
  3. import 'package:easy_localization/easy_localization.dart';
  4. import 'package:flowy_infra_ui/flowy_infra_ui.dart';
  5. import 'package:flutter/material.dart';
  6. import 'package:fluttertoast/fluttertoast.dart';
  7. import '../../../generated/locale_keys.g.dart';
  8. import '../../../startup/startup.dart';
  9. import '../../../workspace/application/settings/settings_location_cubit.dart';
  10. import '../../../workspace/presentation/home/toast.dart';
  11. enum _FolderPage {
  12. options,
  13. create,
  14. open,
  15. }
  16. class FolderWidget extends StatefulWidget {
  17. const FolderWidget({
  18. Key? key,
  19. required this.createFolderCallback,
  20. }) : super(key: key);
  21. final Future<void> Function() createFolderCallback;
  22. @override
  23. State<FolderWidget> createState() => _FolderWidgetState();
  24. }
  25. class _FolderWidgetState extends State<FolderWidget> {
  26. var page = _FolderPage.options;
  27. @override
  28. Widget build(BuildContext context) {
  29. return _mapIndexToWidget(context);
  30. }
  31. Widget _mapIndexToWidget(BuildContext context) {
  32. switch (page) {
  33. case _FolderPage.options:
  34. return FolderOptionsWidget(
  35. onPressedCreate: () {
  36. setState(() => page = _FolderPage.create);
  37. },
  38. onPressedOpen: () {
  39. _openFolder();
  40. },
  41. );
  42. case _FolderPage.create:
  43. return CreateFolderWidget(
  44. onPressedBack: () {
  45. setState(() => page = _FolderPage.options);
  46. },
  47. onPressedCreate: widget.createFolderCallback,
  48. );
  49. case _FolderPage.open:
  50. return Container();
  51. }
  52. }
  53. Future<void> _openFolder() async {
  54. final directory = await getIt<FilePickerService>().getDirectoryPath();
  55. if (directory != null) {
  56. await getIt<SettingsLocationCubit>().setLocation(directory);
  57. await widget.createFolderCallback();
  58. }
  59. }
  60. }
  61. class FolderOptionsWidget extends StatelessWidget {
  62. const FolderOptionsWidget({
  63. Key? key,
  64. required this.onPressedCreate,
  65. required this.onPressedOpen,
  66. }) : super(key: key);
  67. final VoidCallback onPressedCreate;
  68. final VoidCallback onPressedOpen;
  69. @override
  70. Widget build(BuildContext context) {
  71. return Column(
  72. children: [
  73. _FolderCard(
  74. title: LocaleKeys.settings_files_createNewFolder.tr(),
  75. subtitle: LocaleKeys.settings_files_createNewFolderDesc.tr(),
  76. trailing: _buildTextButton(
  77. context,
  78. LocaleKeys.settings_files_create.tr(),
  79. onPressedCreate,
  80. ),
  81. ),
  82. _FolderCard(
  83. title: LocaleKeys.settings_files_openFolder.tr(),
  84. subtitle: LocaleKeys.settings_files_openFolderDesc.tr(),
  85. trailing: _buildTextButton(
  86. context,
  87. LocaleKeys.settings_files_open.tr(),
  88. onPressedOpen,
  89. ),
  90. ),
  91. ],
  92. );
  93. }
  94. }
  95. class CreateFolderWidget extends StatefulWidget {
  96. const CreateFolderWidget({
  97. Key? key,
  98. required this.onPressedBack,
  99. required this.onPressedCreate,
  100. }) : super(key: key);
  101. final VoidCallback onPressedBack;
  102. final Future<void> Function() onPressedCreate;
  103. @override
  104. State<CreateFolderWidget> createState() => CreateFolderWidgetState();
  105. }
  106. @visibleForTesting
  107. class CreateFolderWidgetState extends State<CreateFolderWidget> {
  108. var _folderName = 'appflowy';
  109. @visibleForTesting
  110. var directory = '';
  111. final _fToast = FToast();
  112. @override
  113. void initState() {
  114. super.initState();
  115. _fToast.init(context);
  116. }
  117. @override
  118. Widget build(BuildContext context) {
  119. return Column(
  120. children: [
  121. Align(
  122. alignment: Alignment.centerLeft,
  123. child: TextButton.icon(
  124. onPressed: widget.onPressedBack,
  125. icon: const Icon(Icons.arrow_back_rounded),
  126. label: const Text('Back'),
  127. ),
  128. ),
  129. _FolderCard(
  130. title: LocaleKeys.settings_files_location.tr(),
  131. subtitle: LocaleKeys.settings_files_locationDesc.tr(),
  132. trailing: SizedBox(
  133. width: 120,
  134. child: FlowyTextField(
  135. hintText: LocaleKeys.settings_files_folderHintText.tr(),
  136. onChanged: (name) => _folderName = name,
  137. onSubmitted: (name) => setState(
  138. () => _folderName = name,
  139. ),
  140. ),
  141. ),
  142. ),
  143. _FolderCard(
  144. title: LocaleKeys.settings_files_folderPath.tr(),
  145. subtitle: _path,
  146. trailing: _buildTextButton(
  147. context,
  148. LocaleKeys.settings_files_browser.tr(),
  149. () async {
  150. final dir = await getIt<FilePickerService>().getDirectoryPath();
  151. if (dir != null) {
  152. setState(() => directory = dir);
  153. }
  154. },
  155. ),
  156. ),
  157. const VSpace(4.0),
  158. Padding(
  159. padding: const EdgeInsets.symmetric(horizontal: 4.0),
  160. child: Row(
  161. children: [
  162. _buildTextButton(
  163. context,
  164. LocaleKeys.settings_files_create.tr(),
  165. () async {
  166. if (_path.isEmpty) {
  167. _showToast(
  168. LocaleKeys.settings_files_locationCannotBeEmpty.tr(),
  169. );
  170. } else {
  171. await getIt<SettingsLocationCubit>().setLocation(_path);
  172. await widget.onPressedCreate();
  173. }
  174. },
  175. ),
  176. ],
  177. ),
  178. )
  179. ],
  180. );
  181. }
  182. String get _path {
  183. if (directory.isEmpty) return '';
  184. final String path;
  185. if (Platform.isMacOS) {
  186. path = directory.replaceAll('/Volumes/Macintosh HD', '');
  187. } else {
  188. path = directory;
  189. }
  190. return '$path/$_folderName';
  191. }
  192. void _showToast(String message) {
  193. _fToast.showToast(
  194. child: FlowyMessageToast(message: message),
  195. gravity: ToastGravity.CENTER,
  196. );
  197. }
  198. }
  199. Widget _buildTextButton(
  200. BuildContext context, String title, VoidCallback onPressed) {
  201. return FlowyTextButton(
  202. title,
  203. onPressed: onPressed,
  204. fillColor: Theme.of(context).colorScheme.primary,
  205. fontColor: Theme.of(context).colorScheme.onPrimary,
  206. hoverColor: Theme.of(context).colorScheme.primaryContainer,
  207. );
  208. }
  209. class _FolderCard extends StatelessWidget {
  210. const _FolderCard({
  211. Key? key,
  212. required this.title,
  213. required this.subtitle,
  214. this.trailing,
  215. }) : super(key: key);
  216. final String title;
  217. final String subtitle;
  218. final Widget? trailing;
  219. @override
  220. Widget build(BuildContext context) {
  221. return Card(
  222. child: Padding(
  223. padding: const EdgeInsets.symmetric(
  224. vertical: 4.0,
  225. horizontal: 16.0,
  226. ),
  227. child: Row(
  228. children: [
  229. Expanded(
  230. child: Column(
  231. crossAxisAlignment: CrossAxisAlignment.start,
  232. children: [
  233. FlowyText.medium(
  234. title,
  235. ),
  236. Row(
  237. children: [
  238. Flexible(
  239. child: Text(
  240. subtitle,
  241. overflow: TextOverflow.ellipsis,
  242. style:
  243. Theme.of(context).textTheme.bodyMedium!.copyWith(
  244. fontWeight: FontWeight.w400,
  245. ),
  246. ),
  247. ),
  248. ],
  249. ),
  250. ],
  251. ),
  252. ),
  253. if (trailing != null) ...[
  254. const HSpace(40),
  255. trailing!,
  256. ],
  257. ],
  258. ),
  259. ),
  260. );
  261. }
  262. }