Skip to content
This repository was archived by the owner on Sep 14, 2024. It is now read-only.

Commit 5438b40

Browse files
committed
rework import screen
- validate input to be a url - enable submitting with the keyboard
1 parent f02b8ee commit 5438b40

File tree

3 files changed

+150
-135
lines changed

3 files changed

+150
-135
lines changed

assets/i18n/en.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,6 @@
164164
"title": "Import Recipe",
165165
"button": "Import",
166166
"field": "URL to Recipe",
167-
"clipboard": "Paste Clipboard",
168167
"errors": {
169168
"import_failed": "Import Failed {error_msg}"
170169
}

lib/src/screens/form/recipe_import_form.dart

Lines changed: 0 additions & 102 deletions
This file was deleted.

lib/src/screens/recipe_import_screen.dart

Lines changed: 150 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,173 @@
11
import 'package:flutter/material.dart';
2+
import 'package:flutter/scheduler.dart';
3+
import 'package:flutter/services.dart';
24
import 'package:flutter_bloc/flutter_bloc.dart';
5+
import 'package:flutter_spinkit/flutter_spinkit.dart';
36
import 'package:flutter_translate/flutter_translate.dart';
47
import 'package:nextcloud_cookbook_flutter/src/blocs/recipe/recipe_bloc.dart';
5-
import 'package:nextcloud_cookbook_flutter/src/screens/form/recipe_import_form.dart';
68
import 'package:nextcloud_cookbook_flutter/src/screens/recipe_screen.dart';
79
import 'package:nextcloud_cookbook_flutter/src/util/theme_data.dart';
10+
import 'package:nextcloud_cookbook_flutter/src/util/url_validator.dart';
811

9-
class RecipeImportScreen extends StatelessWidget {
12+
class RecipeImportScreen extends StatefulWidget {
1013
final String importUrl;
1114

1215
const RecipeImportScreen([this.importUrl = '']);
1316

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+
1480
@override
1581
Widget build(BuildContext context) {
1682
return BlocProvider<RecipeBloc>(
1783
create: (context) => RecipeBloc(),
1884
child: Scaffold(
1985
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),
46146
),
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+
],
50169
),
51170
),
52-
body: RecipeImportForm(importUrl),
53171
),
54172
);
55173
}

0 commit comments

Comments
 (0)