Guest User

Untitled

a guest
Apr 8th, 2019
48
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.21 KB | None | 0 0
  1. import 'package:flutter/foundation.dart' show ValueListenable; // should be exported by widgets
  2. import 'package:flutter/material.dart';
  3. import 'package:provider/provider.dart';
  4.  
  5. void main() {
  6. runApp(Provider<LoginApi>(
  7. value: LoginApiImpl(),
  8. child: TestApp(),
  9. ));
  10. }
  11.  
  12. class TestApp extends StatelessWidget {
  13. @override
  14. Widget build(BuildContext context) {
  15. return MaterialApp(
  16. theme: ThemeData(
  17. primarySwatch: Colors.indigo,
  18. accentColor: Colors.pink,
  19. ),
  20. home: LoginScreen(),
  21. );
  22. }
  23. }
  24.  
  25. abstract class LoginApi {
  26. Future<void> performLogin(String username, String password);
  27. }
  28.  
  29. class LoginApiImpl extends LoginApi {
  30. @override
  31. Future<void> performLogin(String username, String password) async {
  32. print('Username: $username\nPassword: $password');
  33. await Future.delayed(const Duration(seconds: 3));
  34. }
  35. }
  36.  
  37. abstract class LoginLogic {
  38. LoginLogic();
  39.  
  40. LoginApi api;
  41.  
  42. Key get formKey;
  43.  
  44. ValueListenable<bool> get formEnabled;
  45.  
  46. void onFormChanged();
  47.  
  48. void saveFullName(String value);
  49.  
  50. void saveUsername(String value);
  51.  
  52. void savePassword(String value);
  53.  
  54. String validateFullNameField(String value);
  55.  
  56. String validateUsernameField(String value);
  57.  
  58. String validatePasswordField(String value);
  59.  
  60. Future<void> onNextPressed();
  61. }
  62.  
  63. class LoginLogicImpl extends LoginLogic {
  64. LoginLogicImpl() : super();
  65.  
  66. final _formKey = GlobalKey<FormState>();
  67. final _formEnabled = ValueNotifier<bool>(true);
  68.  
  69. String _fullName;
  70. String _username;
  71. String _password;
  72.  
  73. Key get formKey => _formKey;
  74.  
  75. ValueListenable<bool> get formEnabled => _formEnabled;
  76.  
  77. void onFormChanged() => _formKey.currentState.validate();
  78.  
  79. void saveFullName(String value) => _fullName = value;
  80.  
  81. void saveUsername(String value) => _username = value;
  82.  
  83. void savePassword(String value) => _password = value;
  84.  
  85. @override
  86. String validateFullNameField(String value) {
  87. return value.isEmpty ? 'Full Name required' : null;
  88. }
  89.  
  90. @override
  91. String validateUsernameField(String value) {
  92. return value.isEmpty ? 'Username required' : null;
  93. }
  94.  
  95. @override
  96. String validatePasswordField(String value) {
  97. if (value?.isEmpty ?? true) {
  98. return 'Password required';
  99. }
  100. if (value.length < 5) {
  101. return 'Password must be longer than 5 characters.';
  102. }
  103. return null;
  104. }
  105.  
  106. @override
  107. Future<void> onNextPressed() async {
  108. final state = _formKey.currentState;
  109. if (!state.validate()) {
  110. return;
  111. }
  112. state.save();
  113. _formEnabled.value = false;
  114. print('Fullname: $_fullName');
  115. await api.performLogin(_username, _password);
  116. _formEnabled.value = true;
  117. }
  118. }
  119.  
  120. class LoginScreen extends StatefulWidget {
  121. @override
  122. _LoginScreenState createState() => _LoginScreenState();
  123. }
  124.  
  125. class _LoginScreenState extends State<LoginScreen> {
  126. final logic = LoginLogicImpl();
  127. final _focusFullName = FocusNode();
  128. final _focusUsername = FocusNode();
  129. final _focusPassword = FocusNode();
  130.  
  131. @override
  132. void initState() {
  133. super.initState();
  134. //
  135. }
  136.  
  137. @override
  138. void didChangeDependencies() {
  139. super.didChangeDependencies();
  140. logic.api = Provider.of<LoginApi>(context);
  141. }
  142.  
  143. @override
  144. void dispose() {
  145. _focusPassword.dispose();
  146. _focusUsername.dispose();
  147. _focusFullName.dispose();
  148. super.dispose();
  149. }
  150.  
  151. @override
  152. Widget build(BuildContext context) {
  153. return Scaffold(
  154. resizeToAvoidBottomInset: false,
  155. appBar: AppBar(
  156. title: Row(
  157. mainAxisSize: MainAxisSize.min,
  158. children: <Widget>[
  159. Text('Input Password Toggle'),
  160. SizedBox(width: 12.0),
  161. Icon(Icons.visibility_off),
  162. ],
  163. ),
  164. ),
  165. body: Form(
  166. key: logic.formKey,
  167. onChanged: logic.onFormChanged,
  168. child: PageScrollContent(
  169. aboveKeyboard: (BuildContext context) {
  170. return BottomKeyboardBar(
  171. child: FlatButton(
  172. onPressed: logic.onNextPressed,
  173. textColor: Theme.of(context).primaryColor,
  174. child: const Text('NEXT'),
  175. padding: EdgeInsets.symmetric(vertical: 12.0, horizontal: 8.0),
  176. materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
  177. ),
  178. );
  179. },
  180. child: ValueListenableBuilder<bool>(
  181. valueListenable: logic.formEnabled,
  182. builder: (BuildContext context, bool formEnabled, Widget child) {
  183. return Center(
  184. child: Column(
  185. mainAxisAlignment: MainAxisAlignment.center,
  186. crossAxisAlignment: CrossAxisAlignment.stretch,
  187. children: <Widget>[
  188. TextFormField(
  189. key: Key('fullname'),
  190. focusNode: _focusFullName,
  191. enabled: formEnabled,
  192. keyboardType: TextInputType.text,
  193. textInputAction: TextInputAction.next,
  194. onSaved: logic.saveFullName,
  195. validator: logic.validateFullNameField,
  196. onFieldSubmitted: (_) => FocusScope.of(context).requestFocus(_focusUsername),
  197. decoration: InputDecoration(
  198. labelText: 'Your name',
  199. border: OutlineInputBorder(),
  200. ),
  201. ),
  202. SizedBox(height: 16.0),
  203. TextFormField(
  204. key: Key('username'),
  205. focusNode: _focusUsername,
  206. enabled: formEnabled,
  207. keyboardType: TextInputType.text,
  208. textInputAction: TextInputAction.next,
  209. onSaved: logic.saveUsername,
  210. validator: logic.validateUsernameField,
  211. onFieldSubmitted: (_) => FocusScope.of(context).requestFocus(_focusPassword),
  212. decoration: InputDecoration(
  213. labelText: 'Your username',
  214. border: OutlineInputBorder(),
  215. ),
  216. ),
  217. SizedBox(height: 16.0),
  218. PasswordFormField(
  219. key: Key('password'),
  220. focusNode: _focusPassword,
  221. enabled: formEnabled,
  222. onSaved: logic.savePassword,
  223. validator: logic.validatePasswordField,
  224. onFieldSubmitted: (_) => logic.onNextPressed(),
  225. initialVisible: true,
  226. ),
  227. SizedBox(height: 32.0),
  228. ProgressButton(
  229. onPressed: logic.onNextPressed,
  230. showProgress: !formEnabled,
  231. child: Text('NEXT'),
  232. ),
  233. ],
  234. ),
  235. );
  236. },
  237. ),
  238. ),
  239. ),
  240. );
  241. }
  242. }
  243.  
  244. class BottomKeyboardBar extends StatelessWidget {
  245. const BottomKeyboardBar({
  246. Key key,
  247. this.child,
  248. }) : super(key: key);
  249.  
  250. final Widget child;
  251.  
  252. @override
  253. Widget build(BuildContext context) {
  254. return BottomAppBar(
  255. child: Align(
  256. alignment: Alignment.bottomRight,
  257. child: child,
  258. ),
  259. );
  260. }
  261. }
  262.  
  263. class PageScrollContent extends StatelessWidget {
  264. const PageScrollContent({
  265. Key key,
  266. this.padding = const EdgeInsets.symmetric(horizontal: 16.0, vertical: 32.0),
  267. this.aboveKeyboard,
  268. this.child,
  269. }) : assert(padding != null),
  270. super(key: key);
  271.  
  272. final EdgeInsets padding;
  273. final WidgetBuilder aboveKeyboard;
  274. final Widget child;
  275.  
  276. @override
  277. Widget build(BuildContext context) {
  278. return LayoutBuilder(
  279. builder: (BuildContext context, BoxConstraints constraints) {
  280. final mediaQuery = MediaQuery.of(context);
  281. return Padding(
  282. padding: EdgeInsets.only(bottom: mediaQuery.viewInsets.bottom),
  283. child: Stack(
  284. children: <Widget>[
  285. SingleChildScrollView(
  286. padding: padding,
  287. child: ConstrainedBox(
  288. constraints: constraints.copyWith(
  289. minHeight: constraints.maxHeight - padding.vertical - mediaQuery.viewInsets.bottom,
  290. ),
  291. child: IntrinsicHeight(
  292. child: child,
  293. ),
  294. ),
  295. ),
  296. (mediaQuery.viewInsets.bottom > 0.0)
  297. ? Positioned(
  298. left: 0.0,
  299. right: 0.0,
  300. bottom: 0.0,
  301. child: aboveKeyboard(context),
  302. )
  303. : const SizedBox(),
  304. ],
  305. ),
  306. );
  307. },
  308. );
  309. }
  310. }
  311.  
  312. class ProgressButton extends StatelessWidget {
  313. const ProgressButton({
  314. Key key,
  315. this.onPressed,
  316. this.child,
  317. this.showProgress = false,
  318. }) : assert(showProgress != null),
  319. super(key: key);
  320.  
  321. final VoidCallback onPressed;
  322. final Widget child;
  323. final bool showProgress;
  324.  
  325. @override
  326. Widget build(BuildContext context) {
  327. final theme = Theme.of(context);
  328. final buttonTheme = ButtonTheme.of(context);
  329. return RaisedButton(
  330. color: theme.primaryColor,
  331. disabledColor: theme.primaryColor,
  332. textColor: theme.primaryTextTheme.button.color,
  333. onPressed: showProgress ? null : onPressed,
  334. shape: showProgress ? const CircleBorder() : buttonTheme.shape,
  335. child: Padding(
  336. padding: const EdgeInsets.all(2.0),
  337. child: showProgress ? const CircularProgressIndicator() : child,
  338. ),
  339. );
  340. }
  341. }
  342.  
  343. class PasswordFormField extends FormField<String> {
  344. PasswordFormField({
  345. Key key,
  346. bool enabled = true,
  347. this.initialVisible = false,
  348. FocusNode focusNode,
  349. ValueChanged<String> onFieldSubmitted,
  350. FormFieldSetter<String> onSaved,
  351. FormFieldValidator<String> validator,
  352. }) : assert(initialVisible != null),
  353. super(
  354. key: key,
  355. enabled: enabled,
  356. onSaved: onSaved,
  357. validator: validator,
  358. builder: (FormFieldState<String> field) {
  359. final _PasswordFormFieldState state = field;
  360. return TextField(
  361. focusNode: focusNode,
  362. decoration: InputDecoration(
  363. labelText: 'Your password',
  364. errorText: field.errorText,
  365. border: OutlineInputBorder(),
  366. suffixIcon: GestureDetector(
  367. onTap: state._togglePasswordVisibility,
  368. child: Icon(state._visible ? Icons.visibility : Icons.visibility_off),
  369. ),
  370. ),
  371. keyboardType: TextInputType.text,
  372. textInputAction: TextInputAction.next,
  373. obscureText: state._visible,
  374. enabled: enabled,
  375. maxLines: 1,
  376. onChanged: field.didChange,
  377. onSubmitted: onFieldSubmitted,
  378. );
  379. },
  380. );
  381.  
  382. final bool initialVisible;
  383.  
  384. @override
  385. _PasswordFormFieldState createState() => _PasswordFormFieldState();
  386. }
  387.  
  388. class _PasswordFormFieldState extends FormFieldState<String> {
  389. bool _visible;
  390.  
  391. @override
  392. PasswordFormField get widget => super.widget;
  393.  
  394. @override
  395. void initState() {
  396. super.initState();
  397. _visible = widget.initialVisible;
  398. }
  399.  
  400. void _togglePasswordVisibility() {
  401. setState(() => _visible = !_visible);
  402. }
  403. }
Add Comment
Please, Sign In to add comment