From cbbd95ab239db6becc854ea503b4dcdaee477e3f Mon Sep 17 00:00:00 2001 From: Oksana Pokrovskaya Date: Wed, 21 Mar 2018 17:29:21 +0300 Subject: [PATCH] save navigation --- .../ru/touchin/flutterapp/MainActivity.kt | 16 +-- lib/main.dart | 105 +++++++++++++++++- lib/model.dart | 4 +- lib/random_words.dart | 43 ++++--- lib/random_words_input.dart | 44 ++++++++ lib/random_words_input.g.dart | 17 +++ lib/random_words_model.dart | 31 ++++-- lib/random_words_model.g.dart | 15 ++- pubspec.lock | 10 +- pubspec.yaml | 1 + 10 files changed, 244 insertions(+), 42 deletions(-) create mode 100644 lib/random_words_input.dart create mode 100644 lib/random_words_input.g.dart diff --git a/android/app/src/main/kotlin/ru/touchin/flutterapp/MainActivity.kt b/android/app/src/main/kotlin/ru/touchin/flutterapp/MainActivity.kt index d6e6d3a..f5ee51d 100644 --- a/android/app/src/main/kotlin/ru/touchin/flutterapp/MainActivity.kt +++ b/android/app/src/main/kotlin/ru/touchin/flutterapp/MainActivity.kt @@ -12,29 +12,31 @@ import android.os.Parcelable class MainActivity() : FlutterActivity() { - var savedModels: MutableMap = mutableMapOf() + var savedFromFlutter: MutableMap = mutableMapOf() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) GeneratedPluginRegistrant.registerWith(this) MethodChannel(getFlutterView(), "app.channel.shared.data").setMethodCallHandler { call, result -> - if (call.method.contentEquals("saveModel")) { - savedModels.put(call.argument("key"), call.argument("value")) - } else if (call.method.contentEquals("readModel")) { - result.success(savedModels.get(call.argument("key"))) + if (call.method.contentEquals("saveInput")) { + savedFromFlutter.put(call.argument("key"), call.argument("value")) + } else if (call.method.contentEquals("readInput")) { + result.success(savedFromFlutter.get(call.argument("key"))) + } else if (call.method.contentEquals("wasRestarted")) { + result.success(savedInstanceState != null) } } } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - outState.putParcelable("savedModels", toBundle(savedModels)); + outState.putParcelable("savedFromFlutter", toBundle(savedFromFlutter)); } override fun onRestoreInstanceState(savedInstanceState: Bundle) { super.onRestoreInstanceState(savedInstanceState) - savedModels = fromBundle(savedInstanceState.getParcelable("savedModels")) + savedFromFlutter = fromBundle(savedInstanceState.getParcelable("savedFromFlutter")) } } diff --git a/lib/main.dart b/lib/main.dart index 04450cb..27cabd2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,8 +1,44 @@ +import 'dart:collection'; +import 'dart:convert'; + import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_app/random_words.dart'; +import 'dart:async'; void main() => runApp(new MyApp()); +class Keys { + static GlobalKey key = new GlobalKey(); +} + +class Routes { + static Queue routes = new Queue(); + static var _firstTime = true; + + static save() async { + const platform = const MethodChannel('app.channel.shared.data'); + platform.invokeMethod( + "saveInput", {"key": "routes", "value": JSON.encode(routes.toList())}); + } + + static restore(BuildContext context) async { + if (!_firstTime) { + return; + } + const platform = const MethodChannel('app.channel.shared.data'); + String s = await platform.invokeMethod("readInput", {"key": "routes"}); + if (s != null) { + routes = new Queue(); + routes.addAll(JSON.decode(s)); + } + _firstTime = false; + for (String route in routes) { + Navigator.of(context).pushNamed(route); + } + } +} + class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { @@ -11,7 +47,11 @@ class MyApp extends StatelessWidget { theme: new ThemeData( primarySwatch: Colors.blue, ), - home: new MyHomePage(title: 'Flutter Demo Home Page'), + home: new MyHomePage(title: 'Startup Name Generator'), + routes: { + '/saved': (BuildContext context) => + new SavedPage(title: 'Saved Suggestions'), + }, ); } } @@ -29,11 +69,70 @@ class _MyHomePageState extends State { @override Widget build(BuildContext context) { + Routes.restore(context); return new Scaffold( appBar: new AppBar( title: new Text(widget.title), + actions: [ + new IconButton(icon: new Icon(Icons.list), onPressed: _pushSaved), + ], ), - body: new RandomWords("list"), + body: new RandomWords(Keys.key, "list"), ); } -} \ No newline at end of file + + void _pushSaved() async { + Routes.routes.addLast('/saved'); + await Routes.save(); + Navigator.of(context).pushNamed('/saved'); + } +} + +class SavedPage extends StatefulWidget { + SavedPage({Key key, this.title}) : super(key: key); + + final String title; + + @override + _SavedPageState createState() => new _SavedPageState(); +} + +class _SavedPageState extends State { + + @override + Widget build(BuildContext context) { + if (!Keys.key.currentState.model.isInitialized()) { + return new Container(); + } + final tiles = Keys.key.currentState.model.saved.map( + (pair) { + return new ListTile( + title: new Text( + pair, + style: Keys.key.currentState.biggerFont, + ), + ); + }, + ); + final divided = ListTile + .divideTiles( + context: context, + tiles: tiles, + ) + .toList(); + return new Scaffold( + appBar: new AppBar( + title: new Text('Saved Suggestions'), + ), + body: new WillPopScope( + onWillPop: _onWillPop, + child: new ListView(children: divided),), + ); + } + + Future _onWillPop() async { + Routes.routes.removeLast(); + await Routes.save(); + return true; + } +} diff --git a/lib/model.dart b/lib/model.dart index 84591f2..3519b0d 100644 --- a/lib/model.dart +++ b/lib/model.dart @@ -1,7 +1,7 @@ import 'dart:async'; -abstract class Model { +abstract class Restorable { save(String key); - Future restore(String key); + Future restore(String key); isInitialized(); } \ No newline at end of file diff --git a/lib/random_words.dart b/lib/random_words.dart index 83d1fbf..92a0343 100644 --- a/lib/random_words.dart +++ b/lib/random_words.dart @@ -1,34 +1,36 @@ import 'package:flutter/material.dart'; +import 'package:flutter_app/random_words_input.dart'; import 'package:flutter_app/random_words_model.dart'; import 'package:english_words/english_words.dart'; class RandomWords extends StatefulWidget { - final String modelKey; + final String stateKey; - RandomWords(this.modelKey); + RandomWords(Key key, this.stateKey) :super(key: key); @override - createState() => new RandomWordsState(modelKey); + createState() => new RandomWordsState(); } class RandomWordsState extends State { - String modelKey; RandomWordsModel model = new RandomWordsModel(); + RandomWordsInput input = new RandomWordsInput(); - final _biggerFont = const TextStyle(fontSize: 18.0); + final biggerFont = const TextStyle(fontSize: 18.0); final ScrollController scrollController = new ScrollController(); - RandomWordsState(String stateKey) { - this.modelKey = stateKey; + RandomWordsState() { _init(); } _init() async { - RandomWordsModel newModel = await model.restore(modelKey); + RandomWordsModel newModel = await model.restore(widget.stateKey); + RandomWordsInput newInput = await input.restore(widget.stateKey); setState(() { model = newModel; - scrollController.jumpTo(model.scrollPosition); + input = newInput; + scrollController.jumpTo(input.scrollPosition); }); } @@ -60,7 +62,7 @@ class RandomWordsState extends State { for (WordPair pair in newSuggestions) { model.suggestions.add(pair.asPascalCase); } - model.save(modelKey); + model.save(widget.stateKey); } return _buildRow(model.suggestions[index]); @@ -69,16 +71,31 @@ class RandomWordsState extends State { } _onNotification(Notification n) { - model.scrollPosition = scrollController.position.pixels; - model.save(modelKey); + input.scrollPosition = scrollController.position.pixels; + input.save(widget.stateKey); } Widget _buildRow(String word) { + final alreadySaved = model.saved.contains(word); return new ListTile( title: new Text( word, - style: _biggerFont, + style: biggerFont, ), + trailing: new Icon( + alreadySaved ? Icons.favorite : Icons.favorite_border, + color: alreadySaved ? Colors.red : null, + ), + onTap: () { + setState(() { + if (alreadySaved) { + model.saved.remove(word); + } else { + model.saved.add(word); + } + }); + model.save(widget.stateKey); + }, ); } } \ No newline at end of file diff --git a/lib/random_words_input.dart b/lib/random_words_input.dart new file mode 100644 index 0000000..ff1e871 --- /dev/null +++ b/lib/random_words_input.dart @@ -0,0 +1,44 @@ +import 'dart:async'; + +import 'package:flutter/services.dart'; +import 'package:flutter_app/model.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'dart:convert'; +import 'package:shared_preferences/shared_preferences.dart'; +part 'random_words_input.g.dart'; + +@JsonSerializable() +class RandomWordsInput extends Object with _$RandomWordsInputSerializerMixin implements Restorable { + double scrollPosition = -1.0; + + RandomWordsInput(); + + factory RandomWordsInput.fromJson(Map json) => _$RandomWordsInputFromJson(json); + + save(String key) async { + String json = JSON.encode(this); + const platform = const MethodChannel('app.channel.shared.data'); + platform.invokeMethod("saveInput", {"key": key, "value": json}); + } + + Future restore(String key) async { + const platform = const MethodChannel('app.channel.shared.data'); + String s = await platform.invokeMethod("readInput", {"key" : key}); + + if (s != null) { + var restoredModel = new RandomWordsInput.fromJson(JSON.decode(s)); + scrollPosition = restoredModel.scrollPosition; + } else { + _empty(); + } + return this; + } + + _empty() { + scrollPosition = 0.0; + } + + bool isInitialized() { + return scrollPosition >= 0; + } +} \ No newline at end of file diff --git a/lib/random_words_input.g.dart b/lib/random_words_input.g.dart new file mode 100644 index 0000000..9965ff1 --- /dev/null +++ b/lib/random_words_input.g.dart @@ -0,0 +1,17 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'random_words_input.dart'; + +// ************************************************************************** +// Generator: JsonSerializableGenerator +// ************************************************************************** + +RandomWordsInput _$RandomWordsInputFromJson(Map json) => + new RandomWordsInput() + ..scrollPosition = (json['scrollPosition'] as num)?.toDouble(); + +abstract class _$RandomWordsInputSerializerMixin { + double get scrollPosition; + Map toJson() => + {'scrollPosition': scrollPosition}; +} diff --git a/lib/random_words_model.dart b/lib/random_words_model.dart index 144e01c..7053fb2 100644 --- a/lib/random_words_model.dart +++ b/lib/random_words_model.dart @@ -1,15 +1,16 @@ import 'dart:async'; +import 'package:flutter/services.dart'; import 'package:flutter_app/model.dart'; import 'package:json_annotation/json_annotation.dart'; import 'dart:convert'; -import 'package:flutter/services.dart'; +import 'package:shared_preferences/shared_preferences.dart'; part 'random_words_model.g.dart'; @JsonSerializable() -class RandomWordsModel extends Object with _$RandomWordsModelSerializerMixin implements Model { +class RandomWordsModel extends Object with _$RandomWordsModelSerializerMixin implements Restorable { var suggestions; - double scrollPosition = 0.0; + var saved; RandomWordsModel(); @@ -17,24 +18,38 @@ class RandomWordsModel extends Object with _$RandomWordsModelSerializerMixin imp save(String key) async { String json = JSON.encode(this); - const platform = const MethodChannel('app.channel.shared.data'); - platform.invokeMethod("saveModel", {"key": key, "value": json}); + SharedPreferences prefs = await SharedPreferences.getInstance(); + prefs.setString(key, json); + print(suggestions); } Future restore(String key) async { const platform = const MethodChannel('app.channel.shared.data'); - String s = await platform.invokeMethod("readModel", {"key": key}); + bool wasRestarted = await platform.invokeMethod("wasRestarted"); + + if (!wasRestarted) { + _empty(); + return this; + } + + SharedPreferences prefs = await SharedPreferences.getInstance(); + String s = prefs.getString(key); if (s != null) { var restoredModel = new RandomWordsModel.fromJson(JSON.decode(s)); suggestions = restoredModel.suggestions; - scrollPosition = restoredModel.scrollPosition; + saved = restoredModel.saved; } else { - suggestions = []; + _empty(); } return this; } + _empty() { + suggestions = []; + saved = []; + } + bool isInitialized() { return suggestions != null; } diff --git a/lib/random_words_model.g.dart b/lib/random_words_model.g.dart index 527fd22..5d0d5a4 100644 --- a/lib/random_words_model.g.dart +++ b/lib/random_words_model.g.dart @@ -8,14 +8,13 @@ part of 'random_words_model.dart'; RandomWordsModel _$RandomWordsModelFromJson(Map json) => new RandomWordsModel() - ..suggestions = json['suggestions'] - ..scrollPosition = (json['scrollPosition'] as num)?.toDouble(); + ..suggestions = + (json['suggestions'] as List)?.map((e) => e as String)?.toList() + ..saved = (json['saved'] as List)?.map((e) => e as String)?.toList(); abstract class _$RandomWordsModelSerializerMixin { - dynamic get suggestions; - double get scrollPosition; - Map toJson() => { - 'suggestions': suggestions, - 'scrollPosition': scrollPosition - }; + List get suggestions; + List get saved; + Map toJson() => + {'suggestions': suggestions, 'saved': saved}; } diff --git a/pubspec.lock b/pubspec.lock index 80a4713..eb077a6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -354,6 +354,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.28.0" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.0" shelf: dependency: transitive description: @@ -500,4 +507,5 @@ packages: source: hosted version: "2.1.13" sdks: - dart: ">=2.0.0-dev.23.0 <=2.0.0-edge.0d5cf900b021bf5c9fa593ffa12b15bcd1cc5fe0" + dart: ">=2.0.0-dev.28.0 <=2.0.0-edge.0d5cf900b021bf5c9fa593ffa12b15bcd1cc5fe0" + flutter: ">=0.1.4 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index bd99743..b9ede86 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,6 +11,7 @@ dependencies: english_words: ^3.1.0 build_runner: ^0.7.6 json_serializable: ^0.3.2 + shared_preferences: ^0.4.0 dev_dependencies: flutter_test: