WIP: Can change chest

This commit is contained in:
poka 2021-11-09 04:22:04 +01:00
parent f9bb49b3d0
commit 4a6bc55fc3
15 changed files with 364 additions and 161 deletions

BIN
assets/chests/0.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@ -1,5 +1,6 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gecko/models/chestData.dart';
import 'package:gecko/models/walletData.dart'; import 'package:gecko/models/walletData.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:logger/logger.dart'; import 'package:logger/logger.dart';
@ -14,7 +15,7 @@ SharedPreferences prefs;
String endPointGVA; String endPointGVA;
int ramSys; int ramSys;
Box<WalletData> walletBox; Box<WalletData> walletBox;
Box chestBox; Box<ChestData> chestBox;
Box configBox; Box configBox;
// String cesiumPod = "https://g1.data.le-sou.org"; // String cesiumPod = "https://g1.data.le-sou.org";

View File

@ -18,6 +18,7 @@ import 'package:flutter/services.dart';
import 'package:gecko/globals.dart'; import 'package:gecko/globals.dart';
import 'package:gecko/models/cesiumPlus.dart'; import 'package:gecko/models/cesiumPlus.dart';
import 'package:gecko/models/changePin.dart'; import 'package:gecko/models/changePin.dart';
import 'package:gecko/models/chestData.dart';
import 'package:gecko/models/generateWallets.dart'; import 'package:gecko/models/generateWallets.dart';
import 'package:gecko/models/history.dart'; import 'package:gecko/models/history.dart';
import 'package:gecko/models/home.dart'; import 'package:gecko/models/home.dart';
@ -52,8 +53,9 @@ Future<void> main() async {
// Configure Hive and open boxes // Configure Hive and open boxes
await Hive.initFlutter(appPath.path); await Hive.initFlutter(appPath.path);
Hive.registerAdapter(WalletDataAdapter()); Hive.registerAdapter(WalletDataAdapter());
Hive.registerAdapter(ChestDataAdapter());
walletBox = await Hive.openBox<WalletData>("walletBox"); walletBox = await Hive.openBox<WalletData>("walletBox");
chestBox = await Hive.openBox("chestBox"); chestBox = await Hive.openBox<ChestData>("chestBox");
configBox = await Hive.openBox("configBox"); configBox = await Hive.openBox("configBox");
_walletsProvider.getDefaultWallet(); _walletsProvider.getDefaultWallet();
@ -162,7 +164,7 @@ class Gecko extends StatelessWidget {
bodyText1: TextStyle(), bodyText1: TextStyle(),
bodyText2: TextStyle(), bodyText2: TextStyle(),
).apply( ).apply(
bodyColor: Color(0xff855F2D), bodyColor: Color(0xFF000000),
), ),
colorScheme: ColorScheme.fromSwatch() colorScheme: ColorScheme.fromSwatch()
.copyWith(secondary: Colors.grey[850]), .copyWith(secondary: Colors.grey[850]),

View File

@ -3,6 +3,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'dart:async'; import 'dart:async';
import 'package:gecko/globals.dart'; import 'package:gecko/globals.dart';
import 'package:gecko/models/chestData.dart';
class ChangePinProvider with ChangeNotifier { class ChangePinProvider with ChangeNotifier {
bool ischangedPin = false; bool ischangedPin = false;
@ -12,7 +13,7 @@ class ChangePinProvider with ChangeNotifier {
Future<NewWallet> changePin(_name, _oldPin) async { Future<NewWallet> changePin(_name, _oldPin) async {
try { try {
final _dewif = chestBox.get(0); final _dewif = chestBox.get(configBox.get('currentChest')).dewif;
NewWallet newWalletFile = await DubpRust.changeDewifPin( NewWallet newWalletFile = await DubpRust.changeDewifPin(
dewif: _dewif, dewif: _dewif,
@ -29,10 +30,12 @@ class ChangePinProvider with ChangeNotifier {
} }
} }
Future storeWallet(context, _name, NewWallet _newWalletFile) async { Future storeNewPinChest(context, NewWallet _newWalletFile) async {
chestBox.put(0, _newWalletFile.dewif); ChestData currentChest = chestBox.getAt(configBox.get('currentChest'));
currentChest.dewif = _newWalletFile.dewif;
// currentChest.name = _name;
chestBox.add(currentChest);
Navigator.pop(context); Navigator.pop(context);
return _name;
} }
} }

20
lib/models/chestData.dart Normal file
View File

@ -0,0 +1,20 @@
import 'package:hive_flutter/hive_flutter.dart';
part 'chestData.g.dart';
@HiveType(typeId: 1)
class ChestData extends HiveObject {
@HiveField(0)
String dewif;
@HiveField(2)
String name;
ChestData({this.dewif, this.name});
// representation of WalletData when debugging
@override
String toString() {
return this.name;
}
}

View File

@ -0,0 +1,44 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'chestData.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class ChestDataAdapter extends TypeAdapter<ChestData> {
@override
final int typeId = 1;
@override
ChestData read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return ChestData(
dewif: fields[0] as String,
name: fields[2] as String,
);
}
@override
void write(BinaryWriter writer, ChestData obj) {
writer
..writeByte(2)
..writeByte(0)
..write(obj.dewif)
..writeByte(2)
..write(obj.name);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is ChestDataAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

View File

@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:gecko/globals.dart'; import 'package:gecko/globals.dart';
import 'package:gecko/models/chestData.dart';
import 'package:gecko/models/walletData.dart'; import 'package:gecko/models/walletData.dart';
import 'package:pdf/pdf.dart'; import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw; import 'package:pdf/widgets.dart' as pw;
@ -40,11 +41,23 @@ class GenerateWalletsProvider with ChangeNotifier {
void storeHDWChest( void storeHDWChest(
NewWallet _wallet, String _name, BuildContext context) async { NewWallet _wallet, String _name, BuildContext context) async {
int chestNumber = chestBox.length;
WalletData myWallet = WalletData myWallet =
WalletData(chest: 0, number: 0, name: _name, derivation: 3); WalletData(chest: chestNumber, number: 0, name: _name, derivation: 3);
String chestName;
if (chestNumber == 0) {
chestName = 'Coffre à Gecko';
} else {
chestName = 'Coffre à Gecko ${chestNumber + 1}';
}
walletBox.add(myWallet); walletBox.add(myWallet);
chestBox.put(0, _wallet.dewif); ChestData thisChest = ChestData(
configBox.put('currentChest', 0); dewif: _wallet.dewif,
name: chestName,
);
chestBox.add(thisChest);
configBox.put('currentChest', chestNumber);
// walletBox.get(1) // walletBox.get(1)
} }

View File

@ -50,7 +50,7 @@ class HistoryProvider with ChangeNotifier {
Future<String> pay(BuildContext context, String pinCode) async { Future<String> pay(BuildContext context, String pinCode) async {
// MyWalletsProvider _myWalletProvider = MyWalletsProvider(); // MyWalletsProvider _myWalletProvider = MyWalletsProvider();
String dewif = chestBox.get(0); String dewif = chestBox.get(configBox.get('currentChest')).dewif;
try { try {
await DubpRust.simplePaymentFromTransparentAccount( await DubpRust.simplePaymentFromTransparentAccount(
accountIndex: defaultWallet.derivation, accountIndex: defaultWallet.derivation,

View File

@ -22,7 +22,7 @@ class MyWalletsProvider with ChangeNotifier {
return false; return false;
} }
final List _walletList = readAllWallets(0); final List _walletList = readAllWallets(getCurrentChest());
if (_walletList.isEmpty) { if (_walletList.isEmpty) {
log.i('No wallets detected'); log.i('No wallets detected');
@ -45,7 +45,7 @@ class MyWalletsProvider with ChangeNotifier {
WalletData getWalletData(List<int> _id) { WalletData getWalletData(List<int> _id) {
if (_id.isEmpty) return WalletData(); if (_id.isEmpty) return WalletData();
int _chest = _id[0]; int _chest = _id[getCurrentChest()];
int _nbr = _id[1]; int _nbr = _id[1];
var _targetedWallet; var _targetedWallet;
@ -63,7 +63,7 @@ class MyWalletsProvider with ChangeNotifier {
MyWalletsProvider myWalletsProvider = MyWalletsProvider(); MyWalletsProvider myWalletsProvider = MyWalletsProvider();
if (configBox.get('defaultWallet') == null) { if (configBox.get('defaultWallet') == null) {
configBox.put('defaultWallet', [0, 0]); configBox.put('defaultWallet', [getCurrentChest(), 0]);
} }
defaultWallet = myWalletsProvider defaultWallet = myWalletsProvider
@ -120,7 +120,7 @@ class MyWalletsProvider with ChangeNotifier {
Future<void> generateNewDerivation(context, String _name) async { Future<void> generateNewDerivation(context, String _name) async {
int _newDerivationNbr; int _newDerivationNbr;
int _newWalletNbr; int _newWalletNbr;
int _chest = 0; int _chest = getCurrentChest();
List<WalletData> _walletConfig = readAllWallets(_chest); List<WalletData> _walletConfig = readAllWallets(_chest);
if (_walletConfig.isEmpty) { if (_walletConfig.isEmpty) {

View File

@ -76,7 +76,7 @@ class WalletOptionsProvider with ChangeNotifier {
context, WalletData _wallet, String _pin, int _pinLenght) async { context, WalletData _wallet, String _pin, int _pinLenght) async {
isWalletUnlock = false; isWalletUnlock = false;
try { try {
String _localDewif = chestBox.get(0); String _localDewif = chestBox.get(configBox.get('currentChest')).dewif;
String _localPubkey; String _localPubkey;
if ((_localPubkey = await _getPubkeyFromDewif( if ((_localPubkey = await _getPubkeyFromDewif(
@ -114,7 +114,7 @@ class WalletOptionsProvider with ChangeNotifier {
int getPinLenght(_walletNbr) { int getPinLenght(_walletNbr) {
String _localDewif; String _localDewif;
if (_walletNbr is int) { if (_walletNbr is int) {
_localDewif = chestBox.get(0); _localDewif = chestBox.get(configBox.get('currentChest')).dewif;
} else { } else {
_localDewif = _walletNbr; _localDewif = _walletNbr;
} }

View File

@ -92,8 +92,8 @@ class ChangePinScreen extends StatelessWidget with ChangeNotifier {
onPressed: _changePin.newPin.text != '' onPressed: _changePin.newPin.text != ''
? () { ? () {
_changePin.newPin.text = ''; _changePin.newPin.text = '';
_changePin.storeWallet( _changePin.storeNewPinChest(
context, walletName, _newWalletFile); context, _newWalletFile);
} }
: null, : null,
child: Text('Confirmer', style: TextStyle(fontSize: 28))), child: Text('Confirmer', style: TextStyle(fontSize: 28))),

View File

@ -0,0 +1,78 @@
import 'package:flutter/services.dart';
import 'package:gecko/globals.dart';
import 'package:gecko/screens/home.dart';
import 'package:flutter/material.dart';
// import 'package:gecko/models/home.dart';
// import 'package:provider/provider.dart';
// ignore: must_be_immutable
class ChooseChest extends StatelessWidget {
TextEditingController tplController = TextEditingController();
@override
Widget build(BuildContext context) {
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
int currentChest = configBox.get('currentChest');
return Scaffold(
appBar: AppBar(
title: SizedBox(
height: 22,
child: Text('Sélectionner mon coffre'),
)),
floatingActionButton: Container(
height: 80.0,
width: 80.0,
child: FittedBox(
child: FloatingActionButton(
heroTag: "tplButton",
onPressed: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) {
return HomeScreen();
}),
),
child: Container(
height: 40.0,
width: 40.0,
child: Icon(Icons.home, color: Colors.grey[850]),
),
backgroundColor:
floattingYellow, //smoothYellow, //Color.fromARGB(500, 204, 255, 255),
))),
body: SafeArea(
child: Column(children: <Widget>[
SizedBox(height: 150),
Center(
child: Image.asset(
'assets/chests/$currentChest.png',
),
),
SizedBox(height: 20),
Text(chestBox.get(currentChest).name),
ElevatedButton(
style: ElevatedButton.styleFrom(
primary: yellowC, // background
onPrimary: Colors.black, // foreground
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) {
return HomeScreen();
}),
);
},
child: Text('Retour Accueil', style: TextStyle(fontSize: 20))),
SizedBox(height: 20),
GestureDetector(
onTap: () {
Navigator.popUntil(
context,
ModalRoute.withName('/'),
);
},
child: Icon(Icons.home))
]),
));
}
}

View File

@ -5,14 +5,13 @@ import 'package:gecko/models/walletData.dart';
import 'package:gecko/models/walletOptions.dart'; import 'package:gecko/models/walletOptions.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gecko/screens/commonElements.dart'; import 'package:gecko/screens/commonElements.dart';
import 'package:gecko/screens/myWallets/chooseChest.dart';
import 'package:gecko/screens/myWallets/walletOptions.dart'; import 'package:gecko/screens/myWallets/walletOptions.dart';
import 'package:gecko/screens/onBoarding/0_noKeychainFound.dart'; import 'package:gecko/screens/onBoarding/0_noKeychainFound.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
// ignore: must_be_immutable
class WalletsHome extends StatelessWidget { class WalletsHome extends StatelessWidget {
final _derivationKey = GlobalKey<FormState>(); final _derivationKey = GlobalKey<FormState>();
int firstWalletDerivation;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -26,63 +25,58 @@ class WalletsHome extends StatelessWidget {
myWalletProvider.readAllWallets(_currentChest); myWalletProvider.readAllWallets(_currentChest);
final bool isWalletsExists = myWalletProvider.checkIfWalletExist(); final bool isWalletsExists = myWalletProvider.checkIfWalletExist();
if (myWalletProvider.listWallets.isEmpty) {
firstWalletDerivation = myWalletProvider.listWallets[0].derivation;
myWalletProvider.getDefaultWallet();
}
log.d("${myWalletProvider.pinCode},${myWalletProvider.pinLenght}");
return WillPopScope( return WillPopScope(
onWillPop: () { onWillPop: () {
Navigator.popUntil( Navigator.popUntil(
context, context,
ModalRoute.withName('/'), ModalRoute.withName('/'),
); );
return Future<bool>.value(true); return Future<bool>.value(true);
}, },
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
leading: IconButton( leading: IconButton(
icon: Icon(Icons.arrow_back, color: Colors.black), icon: Icon(Icons.arrow_back, color: Colors.black),
onPressed: () { onPressed: () {
Navigator.popUntil( Navigator.popUntil(
context, context,
ModalRoute.withName('/'), ModalRoute.withName('/'),
); );
}), }),
title: Text('Mes portefeuilles', title: Text('Mes portefeuilles',
key: Key('myWallets'), key: Key('myWallets'), style: TextStyle(color: Colors.grey[850])),
style: TextStyle(color: Colors.grey[850])), backgroundColor: Color(0xffFFD58D),
backgroundColor: Color(0xffFFD58D), ),
body: SafeArea(
child:
!isWalletsExists ? NoKeyChainScreen() : myWalletsTiles(context),
),
),
);
}
Widget chestOptions(BuildContext context) {
return Column(children: [
SizedBox(
height: 90,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
elevation: 2,
primary: floattingYellow, // background
onPrimary: Colors.black, // foreground
), ),
floatingActionButton: Visibility( onPressed: () => Navigator.push(
visible: (isWalletsExists && firstWalletDerivation != -1), context,
child: Container( MaterialPageRoute(builder: (context) {
height: 80.0, return ChooseChest();
width: 80.0, }),
child: FittedBox( ),
child: FloatingActionButton( child: Text(
key: Key('addDerivation'), "Changer de coffre",
heroTag: "buttonGenerateWallet", style: TextStyle(fontSize: 16),
onPressed: () { ),
showDialog( ))
context: context, ]);
builder: (BuildContext context) {
return addNewDerivation(context, 1);
});
},
child: Container(
height: 40,
width: 40,
child: Icon(Icons.person_add_alt_1_rounded,
color: Colors.grey[850])),
backgroundColor: floattingYellow)))),
body: SafeArea(
child: !isWalletsExists
? NoKeyChainScreen()
: myWalletsTiles(context))));
} }
Widget myWalletsTiles(BuildContext context) { Widget myWalletsTiles(BuildContext context) {
@ -110,97 +104,138 @@ class WalletsHome extends StatelessWidget {
List _listWallets = _myWalletProvider.listWallets; List _listWallets = _myWalletProvider.listWallets;
return GridView.count( return CustomScrollView(slivers: <Widget>[
key: Key('listWallets'), SliverGrid.count(
crossAxisCount: 2, key: Key('listWallets'),
childAspectRatio: 1, crossAxisCount: 2,
crossAxisSpacing: 0, childAspectRatio: 1,
mainAxisSpacing: 0, crossAxisSpacing: 0,
children: <Widget>[ mainAxisSpacing: 0,
for (WalletData _repository in _listWallets) children: <Widget>[
Padding( for (WalletData _repository in _listWallets)
Padding(
padding: EdgeInsets.all(16), padding: EdgeInsets.all(16),
child: GestureDetector( child: GestureDetector(
onTap: () async { onTap: () async {
await _walletOptions.readLocalWallet( await _walletOptions.readLocalWallet(context, _repository,
context, _myWalletProvider.pinCode, _myWalletProvider.pinLenght);
_repository, Navigator.push(
_myWalletProvider.pinCode, context,
_myWalletProvider.pinLenght); SmoothTransition(
Navigator.push( page: WalletOptions(
context, wallet: _repository,
SmoothTransition( )));
// Navigator.push(context,
// MaterialPageRoute(builder: (context) {
// return UnlockingWallet(wallet: _repository);
// }));
},
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(12)),
child: Column(children: <Widget>[
Expanded(
child: Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
gradient: RadialGradient(
radius: 1,
colors: [
Colors.green[100],
Colors.green[500],
],
)),
child:
// SvgPicture.asset('assets/chopp-gecko2.png',
// semanticsLabel: 'Gecko', height: 48),
Image.asset(
'assets/chopp-gecko2.png',
),
)),
ListTile(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(12))),
// contentPadding: const EdgeInsets.only(left: 7.0),
tileColor: _repository.id()[1] == defaultWallet.id()[1]
? orangeC
: Color(0xffFFD58D),
// leading: Text('IMAGE'),
// subtitle: Text(_repository.split(':')[3],
// style: TextStyle(fontSize: 12.0, fontFamily: 'Monospace')),
title: Center(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 5),
child: Text(_repository.name,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16.0,
color: _repository.id()[1] ==
defaultWallet.id()[1]
? Color(0xffF9F9F1)
: Colors.black)))),
// dense: true,
onTap: () {
Navigator.push(
context,
SmoothTransition(
page: WalletOptions( page: WalletOptions(
wallet: _repository, wallet: _repository,
))); ),
// Navigator.push(context,
// MaterialPageRoute(builder: (context) {
// return UnlockingWallet(wallet: _repository);
// }));
},
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(12)),
child: Column(children: <Widget>[
Expanded(
child: Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
gradient: RadialGradient(
radius: 1,
colors: [
Colors.green[100],
Colors.green[500],
],
)),
child:
// SvgPicture.asset('assets/chopp-gecko2.png',
// semanticsLabel: 'Gecko', height: 48),
Image.asset(
'assets/chopp-gecko2.png',
), ),
)), );
ListTile( },
shape: RoundedRectangleBorder( )
borderRadius: BorderRadius.vertical( ]),
bottom: Radius.circular(12))), ),
// contentPadding: const EdgeInsets.only(left: 7.0), ),
tileColor: ),
_repository.id()[1] == defaultWallet.id()[1] addNewDerivation(context)
? orangeC ]),
: Color(0xffFFD58D), // SliverToBoxAdapter(child: Spacer()),
// leading: Text('IMAGE'), SliverPadding(
padding: EdgeInsets.symmetric(horizontal: 30),
// subtitle: Text(_repository.split(':')[3], sliver: SliverToBoxAdapter(child: chestOptions(context)),
// style: TextStyle(fontSize: 12.0, fontFamily: 'Monospace')), ),
title: Center( ]);
child: Padding(
padding:
EdgeInsets.symmetric(horizontal: 5),
child: Text(_repository.name,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16.0,
color: _repository.id()[1] ==
defaultWallet.id()[1]
? Color(0xffF9F9F1)
: Colors.black)))),
// dense: true,
onTap: () {
Navigator.push(
context,
SmoothTransition(
page: WalletOptions(
wallet: _repository,
)));
},
)
]))))
]);
} }
Widget addNewDerivation(context, int _walletNbr) { Widget addNewDerivation(context) {
return Padding(
padding: EdgeInsets.all(16),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(12)),
child: Column(children: <Widget>[
Expanded(
child: InkWell(
key: Key('addDerivation'),
onTap: () {
showDialog(
context: context,
builder: (BuildContext context) {
return addNewDerivationPopup(context);
});
},
child: Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(color: floattingYellow),
child: Center(
child: Text(
'+',
style: TextStyle(
fontSize: 150,
fontWeight: FontWeight.w700,
color: Color(0xFFFCB437)),
)),
)),
)
])));
}
Widget addNewDerivationPopup(context) {
final TextEditingController _newDerivationName = TextEditingController(); final TextEditingController _newDerivationName = TextEditingController();
MyWalletsProvider _myWalletProvider = MyWalletsProvider _myWalletProvider =
Provider.of<MyWalletsProvider>(context); Provider.of<MyWalletsProvider>(context);

View File

@ -76,6 +76,7 @@ flutter:
- images/ - images/
- config/gva_endpoints.json - config/gva_endpoints.json
- assets/ - assets/
- assets/chests/
- assets/icon/ - assets/icon/
- assets/onBoarding/ - assets/onBoarding/
- assets/onBoarding/progress_bar/ - assets/onBoarding/progress_bar/

View File

@ -370,16 +370,22 @@ void main() {
await goBack(); await goBack();
await driver.waitFor(find.text('Renommage wallet 2')); await driver.waitFor(find.text('Renommage wallet 2'));
await createDerivation('Derivation 8'); await createDerivation('Derivation 8');
await driver.scrollIntoView(find.text('+'));
await createDerivation('Derivation 9'); await createDerivation('Derivation 9');
await createDerivation('Derivation 10'); await createDerivation('Derivation 10');
await driver.scrollIntoView(find.text('+'));
await createDerivation('Derivation 11'); await createDerivation('Derivation 11');
await createDerivation('Derivation 12'); await createDerivation('Derivation 12');
await driver.scrollIntoView(find.text('+'));
await createDerivation('Derivation 13'); await createDerivation('Derivation 13');
await createDerivation('Derivation 14'); await createDerivation('Derivation 14');
await driver.scrollIntoView(find.text('+'));
await createDerivation('Derivation 15'); await createDerivation('Derivation 15');
await createDerivation('Derivation 16'); await createDerivation('Derivation 16');
await driver.scrollIntoView(find.text('+'));
await createDerivation('Derivation 17'); await createDerivation('Derivation 17');
await createDerivation('Derivation 18'); await createDerivation('Derivation 18');
await driver.scrollIntoView(find.text('+'));
await createDerivation('Derivation 19'); await createDerivation('Derivation 19');
await createDerivation('Derivation 20'); await createDerivation('Derivation 20');
await sleep(400); await sleep(400);