|
1 | 1 | import 'package:flutter/material.dart'; |
| 2 | +import 'package:flutter/scheduler.dart'; |
| 3 | +import 'package:flutter/services.dart'; |
2 | 4 | import 'package:flutter_bloc/flutter_bloc.dart'; |
| 5 | +import 'package:flutter_spinkit/flutter_spinkit.dart'; |
3 | 6 | import 'package:flutter_translate/flutter_translate.dart'; |
4 | 7 | import 'package:nextcloud_cookbook_flutter/src/blocs/recipe/recipe_bloc.dart'; |
5 | | -import 'package:nextcloud_cookbook_flutter/src/screens/form/recipe_import_form.dart'; |
6 | 8 | import 'package:nextcloud_cookbook_flutter/src/screens/recipe_screen.dart'; |
7 | 9 | import 'package:nextcloud_cookbook_flutter/src/util/theme_data.dart'; |
| 10 | +import 'package:nextcloud_cookbook_flutter/src/util/url_validator.dart'; |
8 | 11 |
|
9 | | -class RecipeImportScreen extends StatelessWidget { |
| 12 | +class RecipeImportScreen extends StatefulWidget { |
10 | 13 | final String importUrl; |
11 | 14 |
|
12 | 15 | const RecipeImportScreen([this.importUrl = '']); |
13 | 16 |
|
| 17 | + @override |
| 18 | + State<RecipeImportScreen> createState() => _RecipeImportScreenState(); |
| 19 | +} |
| 20 | + |
| 21 | +class _RecipeImportScreenState extends State<RecipeImportScreen> { |
| 22 | + late TextEditingController _importUrlController; |
| 23 | + final _formKey = GlobalKey<FormState>(); |
| 24 | + |
| 25 | + @override |
| 26 | + void initState() { |
| 27 | + super.initState(); |
| 28 | + |
| 29 | + _importUrlController = TextEditingController(text: widget.importUrl); |
| 30 | + if (widget.importUrl.isNotEmpty) { |
| 31 | + SchedulerBinding.instance |
| 32 | + .addPostFrameCallback((_) => import(widget.importUrl)); |
| 33 | + } |
| 34 | + } |
| 35 | + |
| 36 | + @override |
| 37 | + void dispose() { |
| 38 | + _importUrlController.dispose(); |
| 39 | + super.dispose(); |
| 40 | + } |
| 41 | + |
| 42 | + void import(String? url) { |
| 43 | + if (url != null) { |
| 44 | + BlocProvider.of<RecipeBloc>(context).add(RecipeImported(url)); |
| 45 | + } |
| 46 | + } |
| 47 | + |
| 48 | + void onSubmit() { |
| 49 | + if (_formKey.currentState!.validate()) { |
| 50 | + _formKey.currentState!.save(); |
| 51 | + } |
| 52 | + } |
| 53 | + |
| 54 | + String? validate(String? value) { |
| 55 | + if (value == null || value.isEmpty) { |
| 56 | + return translate( |
| 57 | + 'login.server_url.validator.pattern', |
| 58 | + ); |
| 59 | + } |
| 60 | + |
| 61 | + if (!URLUtils.isValid(value)) { |
| 62 | + return translate( |
| 63 | + 'login.server_url.validator.pattern', |
| 64 | + ); |
| 65 | + } |
| 66 | + |
| 67 | + return null; |
| 68 | + } |
| 69 | + |
| 70 | + Future<void> pasteClipboard() async { |
| 71 | + final clipboard = await Clipboard.getData('text/plain'); |
| 72 | + final text = clipboard?.text; |
| 73 | + if (text != null) { |
| 74 | + _importUrlController.text = text; |
| 75 | + } |
| 76 | + |
| 77 | + _formKey.currentState!.validate(); |
| 78 | + } |
| 79 | + |
14 | 80 | @override |
15 | 81 | Widget build(BuildContext context) { |
16 | 82 | return BlocProvider<RecipeBloc>( |
17 | 83 | create: (context) => RecipeBloc(), |
18 | 84 | child: Scaffold( |
19 | 85 | appBar: AppBar( |
20 | | - title: BlocListener<RecipeBloc, RecipeState>( |
21 | | - child: Text(translate("recipe_import.title")), |
22 | | - listener: (context, state) { |
23 | | - if (state.status == RecipeStatus.importFailure) { |
24 | | - final theme = Theme.of(context) |
25 | | - .extension<SnackBarThemes>()! |
26 | | - .errorSnackBar; |
27 | | - ScaffoldMessenger.of(context).showSnackBar( |
28 | | - SnackBar( |
29 | | - content: Text( |
30 | | - translate( |
31 | | - 'recipe_import.errors.import_failed', |
32 | | - args: {"error_msg": state.error}, |
33 | | - ), |
34 | | - style: theme.contentTextStyle, |
35 | | - ), |
36 | | - backgroundColor: theme.backgroundColor, |
37 | | - ), |
38 | | - ); |
39 | | - } else if (state.status == RecipeStatus.importSuccess) { |
40 | | - Navigator.push( |
41 | | - context, |
42 | | - MaterialPageRoute( |
43 | | - builder: (context) { |
44 | | - return RecipeScreen(recipeId: state.recipe!.id!); |
45 | | - }, |
| 86 | + title: Text(translate("recipe_import.title")), |
| 87 | + ), |
| 88 | + body: BlocConsumer<RecipeBloc, RecipeState>( |
| 89 | + builder: builder, |
| 90 | + listener: listener, |
| 91 | + ), |
| 92 | + ), |
| 93 | + ); |
| 94 | + } |
| 95 | + |
| 96 | + void listener(BuildContext context, RecipeState state) { |
| 97 | + if (state.status == RecipeStatus.importFailure) { |
| 98 | + final theme = |
| 99 | + Theme.of(context).extension<SnackBarThemes>()!.errorSnackBar; |
| 100 | + ScaffoldMessenger.of(context).showSnackBar( |
| 101 | + SnackBar( |
| 102 | + content: Text( |
| 103 | + translate( |
| 104 | + 'recipe_import.errors.import_failed', |
| 105 | + args: {"error_msg": state.error}, |
| 106 | + ), |
| 107 | + style: theme.contentTextStyle, |
| 108 | + ), |
| 109 | + backgroundColor: theme.backgroundColor, |
| 110 | + ), |
| 111 | + ); |
| 112 | + } else if (state.status == RecipeStatus.importSuccess) { |
| 113 | + Navigator.push( |
| 114 | + context, |
| 115 | + MaterialPageRoute( |
| 116 | + builder: (context) { |
| 117 | + return RecipeScreen(recipeId: state.recipe!.id!); |
| 118 | + }, |
| 119 | + ), |
| 120 | + ); |
| 121 | + } |
| 122 | + } |
| 123 | + |
| 124 | + Widget builder(BuildContext context, RecipeState state) { |
| 125 | + final enabled = state.status != RecipeStatus.updateInProgress; |
| 126 | + |
| 127 | + return SingleChildScrollView( |
| 128 | + child: Padding( |
| 129 | + padding: const EdgeInsets.all(10.0), |
| 130 | + child: Form( |
| 131 | + key: _formKey, |
| 132 | + child: Column( |
| 133 | + children: [ |
| 134 | + TextFormField( |
| 135 | + enabled: enabled, |
| 136 | + controller: _importUrlController, |
| 137 | + validator: enabled ? validate : null, |
| 138 | + onSaved: import, |
| 139 | + onEditingComplete: onSubmit, |
| 140 | + decoration: InputDecoration( |
| 141 | + hintText: translate("recipe_import.field"), |
| 142 | + suffixIcon: IconButton( |
| 143 | + tooltip: MaterialLocalizations.of(context).pasteButtonLabel, |
| 144 | + onPressed: pasteClipboard, |
| 145 | + icon: const Icon(Icons.content_copy_outlined), |
46 | 146 | ), |
47 | | - ); |
48 | | - } |
49 | | - }, |
| 147 | + ), |
| 148 | + ), |
| 149 | + const SizedBox(height: 10), |
| 150 | + Center( |
| 151 | + child: enabled |
| 152 | + ? OutlinedButton.icon( |
| 153 | + onPressed: enabled ? onSubmit : null, |
| 154 | + icon: const Icon(Icons.cloud_download_outlined), |
| 155 | + label: Text(translate("recipe_import.button")), |
| 156 | + style: OutlinedButton.styleFrom( |
| 157 | + padding: const EdgeInsets.symmetric(horizontal: 25), |
| 158 | + side: BorderSide( |
| 159 | + color: Theme.of(context).colorScheme.onSurface, |
| 160 | + ), |
| 161 | + ), |
| 162 | + ) |
| 163 | + : SpinKitWave( |
| 164 | + color: Theme.of(context).colorScheme.primary, |
| 165 | + size: 30.0, |
| 166 | + ), |
| 167 | + ), |
| 168 | + ], |
50 | 169 | ), |
51 | 170 | ), |
52 | | - body: RecipeImportForm(importUrl), |
53 | 171 | ), |
54 | 172 | ); |
55 | 173 | } |
|
0 commit comments