Update example app with full tests
This commit is contained in:
		
							parent
							
								
									a77ef8df00
								
							
						
					
					
						commit
						e32c54d685
					
				@ -1,10 +1,11 @@
 | 
			
		||||
// Copyright (c) 2020
 | 
			
		||||
// Author: Hugo Pointcheval
 | 
			
		||||
import 'dart:developer';
 | 
			
		||||
import 'dart:typed_data';
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:native_crypto/native_crypto.dart';
 | 
			
		||||
import 'package:native_crypto_example/pages/kemPage.dart';
 | 
			
		||||
 | 
			
		||||
import 'pages/benchmarkPage.dart';
 | 
			
		||||
import 'pages/cipherPage.dart';
 | 
			
		||||
import 'pages/hashKeyDerivationPage.dart';
 | 
			
		||||
 | 
			
		||||
void main() => runApp(MyApp());
 | 
			
		||||
 | 
			
		||||
@ -14,163 +15,20 @@ class MyApp extends StatefulWidget {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _MyAppState extends State<MyApp> {
 | 
			
		||||
  final textController = TextEditingController();
 | 
			
		||||
  final pwdController = TextEditingController();
 | 
			
		||||
 | 
			
		||||
  String _output = 'none';
 | 
			
		||||
  String _bench;
 | 
			
		||||
 | 
			
		||||
  AESCipher aes;
 | 
			
		||||
  CipherText cipherText;
 | 
			
		||||
  Uint8List plainText;
 | 
			
		||||
  SecretKey key;
 | 
			
		||||
 | 
			
		||||
  void _generateKey() async {
 | 
			
		||||
    var output;
 | 
			
		||||
    try {
 | 
			
		||||
      aes = await AESCipher.generate(
 | 
			
		||||
        AESKeySize.bits256,
 | 
			
		||||
        CipherParameters(
 | 
			
		||||
          BlockCipherMode.CBC,
 | 
			
		||||
          PlainTextPadding.PKCS5,
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
      output = 'Key generated. Length: ${aes.secretKey.encoded.length}';
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      print(e);
 | 
			
		||||
      output = e.message;
 | 
			
		||||
    }
 | 
			
		||||
  int _currentIndex = 0;
 | 
			
		||||
  final List<Widget> _children = [
 | 
			
		||||
    HashKeyDerivationPage(),
 | 
			
		||||
    CipherPage(),
 | 
			
		||||
    KemPage(),
 | 
			
		||||
    BenchmarkPage(),
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  void onTabTapped(int index) {
 | 
			
		||||
    setState(() {
 | 
			
		||||
      _output = output;
 | 
			
		||||
      _currentIndex = index;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _pbkdf2() async {
 | 
			
		||||
    final password = pwdController.text.trim();
 | 
			
		||||
 | 
			
		||||
    var output;
 | 
			
		||||
    if (password.isEmpty) {
 | 
			
		||||
      output = 'Password is empty';
 | 
			
		||||
    } else {
 | 
			
		||||
      PBKDF2 _pbkdf2 =
 | 
			
		||||
          PBKDF2(keyLength: 32, iteration: 1000, hash: HashAlgorithm.SHA512);
 | 
			
		||||
      await _pbkdf2.derive(password: password, salt: 'salty');
 | 
			
		||||
      key = _pbkdf2.key;
 | 
			
		||||
      output = 'Key successfully derived.';
 | 
			
		||||
    }
 | 
			
		||||
    setState(() {
 | 
			
		||||
      _output = output;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _encrypt() async {
 | 
			
		||||
    final plainText = textController.text.trim();
 | 
			
		||||
 | 
			
		||||
    var output;
 | 
			
		||||
    if (plainText.isEmpty) {
 | 
			
		||||
      output = 'Entry is empty';
 | 
			
		||||
    } else {
 | 
			
		||||
      var stringToBytes = TypeHelper().stringToBytes(plainText);
 | 
			
		||||
      cipherText = await aes.encrypt(stringToBytes);
 | 
			
		||||
      output = 'String successfully encrypted.';
 | 
			
		||||
    }
 | 
			
		||||
    setState(() {
 | 
			
		||||
      _output = output;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _alter() async {
 | 
			
		||||
    var output;
 | 
			
		||||
    if (cipherText == null || cipherText.bytes.isEmpty) {
 | 
			
		||||
      output = 'Encrypt before altering payload!';
 | 
			
		||||
    } else {
 | 
			
		||||
      // Add 1 to the first byte
 | 
			
		||||
      Uint8List _altered = cipherText.bytes;
 | 
			
		||||
      _altered[0] += 1;
 | 
			
		||||
      // Recreate cipher text with altered data
 | 
			
		||||
      cipherText = AESCipherText(_altered, cipherText.iv);
 | 
			
		||||
      output = 'Payload altered.';
 | 
			
		||||
    }
 | 
			
		||||
    setState(() {
 | 
			
		||||
      _output = output;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _decrypt() async {
 | 
			
		||||
    var output;
 | 
			
		||||
    if (cipherText == null || cipherText.bytes.isEmpty) {
 | 
			
		||||
      output = 'Encrypt before decrypting!';
 | 
			
		||||
    } else {
 | 
			
		||||
      try {
 | 
			
		||||
        plainText = await aes.decrypt(cipherText);
 | 
			
		||||
        var bytesToString = TypeHelper().bytesToString(plainText);
 | 
			
		||||
        output = 'String successfully decrypted:\n\n$bytesToString';
 | 
			
		||||
      } on DecryptionException catch (e) {
 | 
			
		||||
        output = e.message;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    setState(() {
 | 
			
		||||
      _output = output;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<String> _benchmark(int megabytes) async {
 | 
			
		||||
    String output;
 | 
			
		||||
    var bigFile = Uint8List(megabytes * 1000000);
 | 
			
		||||
 | 
			
		||||
    var before = DateTime.now();
 | 
			
		||||
    var encryptedBigFile = await aes.encrypt(bigFile);
 | 
			
		||||
    var after = DateTime.now();
 | 
			
		||||
 | 
			
		||||
    var benchmark =
 | 
			
		||||
        after.millisecondsSinceEpoch - before.millisecondsSinceEpoch;
 | 
			
		||||
 | 
			
		||||
    output = '$megabytes MB\nAES Encryption: $benchmark ms\n';
 | 
			
		||||
 | 
			
		||||
    before = DateTime.now();
 | 
			
		||||
    await aes.decrypt(encryptedBigFile);
 | 
			
		||||
    after = DateTime.now();
 | 
			
		||||
 | 
			
		||||
    benchmark = after.millisecondsSinceEpoch - before.millisecondsSinceEpoch;
 | 
			
		||||
 | 
			
		||||
    output += 'AES Decryption: $benchmark ms\n\n';
 | 
			
		||||
 | 
			
		||||
    return output;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _testPerf({int megabytes}) async {
 | 
			
		||||
    var output = '';
 | 
			
		||||
    if (megabytes != null) {
 | 
			
		||||
      output = await _benchmark(megabytes);
 | 
			
		||||
      setState(() {
 | 
			
		||||
        _output = output;
 | 
			
		||||
      });
 | 
			
		||||
    } else {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        _bench = 'Open the logcat!';
 | 
			
		||||
      });
 | 
			
		||||
      for (int i = 1; i <= 50; i += 10) {
 | 
			
		||||
        var benchmark = await _benchmark(i);
 | 
			
		||||
        log(benchmark, name: 'fr.pointcheval.native_crypto');
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    // Generate AES instance on init.
 | 
			
		||||
    _generateKey();
 | 
			
		||||
    super.initState();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void dispose() {
 | 
			
		||||
    // Clean up the controller when the widget is disposed.
 | 
			
		||||
    textController.dispose();
 | 
			
		||||
    pwdController.dispose();
 | 
			
		||||
    super.dispose();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return MaterialApp(
 | 
			
		||||
@ -179,126 +37,33 @@ class _MyAppState extends State<MyApp> {
 | 
			
		||||
          centerTitle: true,
 | 
			
		||||
          title: const Text('Native Crypto'),
 | 
			
		||||
        ),
 | 
			
		||||
        body: SingleChildScrollView(
 | 
			
		||||
          child: Padding(
 | 
			
		||||
            padding: const EdgeInsets.fromLTRB(20, 10, 10, 20),
 | 
			
		||||
            child: Center(
 | 
			
		||||
              child: Column(
 | 
			
		||||
                children: <Widget>[
 | 
			
		||||
                  TextField(
 | 
			
		||||
                    controller: pwdController,
 | 
			
		||||
                    decoration: InputDecoration(
 | 
			
		||||
                      hintText: 'Test password',
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                  SizedBox(height: 20),
 | 
			
		||||
                  FlatButton(
 | 
			
		||||
                      onPressed: _pbkdf2,
 | 
			
		||||
                      color: Colors.blue,
 | 
			
		||||
                      child: Text(
 | 
			
		||||
                        'Pbkdf2',
 | 
			
		||||
                        style: TextStyle(color: Colors.white),
 | 
			
		||||
                      )),
 | 
			
		||||
                  SizedBox(height: 30),
 | 
			
		||||
                  TextField(
 | 
			
		||||
                    controller: textController,
 | 
			
		||||
                    decoration: InputDecoration(
 | 
			
		||||
                      hintText: 'Text to encrypt.',
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                  SizedBox(height: 20),
 | 
			
		||||
                  FlatButton(
 | 
			
		||||
                      onPressed: _encrypt,
 | 
			
		||||
                      color: Colors.blue,
 | 
			
		||||
                      child: Text(
 | 
			
		||||
                        'Encrypt String',
 | 
			
		||||
                        style: TextStyle(color: Colors.white),
 | 
			
		||||
                      )),
 | 
			
		||||
                  FlatButton(
 | 
			
		||||
                      onPressed: _alter,
 | 
			
		||||
                      color: Colors.blue,
 | 
			
		||||
                      child: Text(
 | 
			
		||||
                        'Alter encrypted payload',
 | 
			
		||||
                        style: TextStyle(color: Colors.white),
 | 
			
		||||
                      )),
 | 
			
		||||
                  FlatButton(
 | 
			
		||||
                      onPressed: _decrypt,
 | 
			
		||||
                      color: Colors.blue,
 | 
			
		||||
                      child: Text(
 | 
			
		||||
                        'Decrypt String',
 | 
			
		||||
                        style: TextStyle(color: Colors.white),
 | 
			
		||||
                      )),
 | 
			
		||||
                  SizedBox(height: 20),
 | 
			
		||||
                  // Output
 | 
			
		||||
                  Text(
 | 
			
		||||
                    _output,
 | 
			
		||||
                    textAlign: TextAlign.center,
 | 
			
		||||
                    style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
 | 
			
		||||
                  ),
 | 
			
		||||
                  SizedBox(height: 20),
 | 
			
		||||
                  FlatButton(
 | 
			
		||||
                      onPressed: () {
 | 
			
		||||
                        _testPerf(megabytes: 1);
 | 
			
		||||
                      },
 | 
			
		||||
                      color: Colors.blue,
 | 
			
		||||
                      child: Text(
 | 
			
		||||
                        'Benchmark 1 MB',
 | 
			
		||||
                        style: TextStyle(color: Colors.white),
 | 
			
		||||
                      )),
 | 
			
		||||
                  FlatButton(
 | 
			
		||||
                      onPressed: () {
 | 
			
		||||
                        _testPerf(megabytes: 10);
 | 
			
		||||
                      },
 | 
			
		||||
                      color: Colors.blue,
 | 
			
		||||
                      child: Text(
 | 
			
		||||
                        'Benchmark 10 MB',
 | 
			
		||||
                        style: TextStyle(color: Colors.white),
 | 
			
		||||
                      )),
 | 
			
		||||
                  FlatButton(
 | 
			
		||||
                      onPressed: () {
 | 
			
		||||
                        _testPerf(megabytes: 50);
 | 
			
		||||
                      },
 | 
			
		||||
                      color: Colors.blue,
 | 
			
		||||
                      child: Text(
 | 
			
		||||
                        'Benchmark 50 MB',
 | 
			
		||||
                        style: TextStyle(color: Colors.white),
 | 
			
		||||
                      )),
 | 
			
		||||
                  SizedBox(height: 20),
 | 
			
		||||
                  FlatButton(
 | 
			
		||||
                      onPressed: () {
 | 
			
		||||
                        _testPerf();
 | 
			
		||||
                      },
 | 
			
		||||
                      color: Colors.blue,
 | 
			
		||||
                      child: Text(
 | 
			
		||||
                        'Full benchmark',
 | 
			
		||||
                        style: TextStyle(color: Colors.white),
 | 
			
		||||
                      )),
 | 
			
		||||
 | 
			
		||||
                  (_bench != null && _bench.isNotEmpty)
 | 
			
		||||
                      ? Text(_bench)
 | 
			
		||||
                      : Container(),
 | 
			
		||||
                ],
 | 
			
		||||
              ),
 | 
			
		||||
        body: _children[_currentIndex],
 | 
			
		||||
        bottomNavigationBar: BottomNavigationBar(
 | 
			
		||||
          selectedItemColor: Colors.blue,
 | 
			
		||||
          unselectedItemColor: Colors.black,
 | 
			
		||||
          showUnselectedLabels: true,
 | 
			
		||||
          onTap: onTabTapped, // new
 | 
			
		||||
          currentIndex: _currentIndex, // new
 | 
			
		||||
          items: [
 | 
			
		||||
            BottomNavigationBarItem(
 | 
			
		||||
              icon: Icon(Icons.vpn_key),
 | 
			
		||||
              label: 'Key',
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
            BottomNavigationBarItem(
 | 
			
		||||
              icon: Icon(Icons.lock),
 | 
			
		||||
              label: 'Encryption',
 | 
			
		||||
            ),
 | 
			
		||||
            BottomNavigationBarItem(
 | 
			
		||||
              icon: Icon(Icons.connect_without_contact),
 | 
			
		||||
              label: 'KEM',
 | 
			
		||||
            ),
 | 
			
		||||
            BottomNavigationBarItem(
 | 
			
		||||
              icon: Icon(Icons.timer),
 | 
			
		||||
              label: 'Benchmark',
 | 
			
		||||
            ),
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Contains some useful functions.
 | 
			
		||||
class TypeHelper {
 | 
			
		||||
  /// Returns bytes `Uint8List` from a `String`.
 | 
			
		||||
  Uint8List stringToBytes(String source) {
 | 
			
		||||
    var list = source.runes.toList();
 | 
			
		||||
    var bytes = Uint8List.fromList(list);
 | 
			
		||||
    return bytes;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Returns a `String` from bytes `Uint8List`.
 | 
			
		||||
  String bytesToString(Uint8List bytes) {
 | 
			
		||||
    var string = String.fromCharCodes(bytes);
 | 
			
		||||
    return string;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										115
									
								
								example/lib/pages/benchmarkPage.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								example/lib/pages/benchmarkPage.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,115 @@
 | 
			
		||||
// Copyright (c) 2020
 | 
			
		||||
// Author: Hugo Pointcheval
 | 
			
		||||
import 'dart:typed_data';
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:native_crypto/native_crypto.dart';
 | 
			
		||||
 | 
			
		||||
import '../session.dart';
 | 
			
		||||
import '../widgets/button.dart';
 | 
			
		||||
import '../widgets/output.dart';
 | 
			
		||||
 | 
			
		||||
class BenchmarkPage extends StatefulWidget {
 | 
			
		||||
  const BenchmarkPage({key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  _BenchmarkPageState createState() => _BenchmarkPageState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _BenchmarkPageState extends State<BenchmarkPage> {
 | 
			
		||||
  final Output keyContent = Output(
 | 
			
		||||
    textEditingController: TextEditingController(),
 | 
			
		||||
  );
 | 
			
		||||
  final Output benchmarkStatus = Output(
 | 
			
		||||
    textEditingController: TextEditingController(),
 | 
			
		||||
    large: true,
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  Future<void> _benchmark() async {
 | 
			
		||||
    if (Session.secretKey == null || Session.secretKey.isEmpty) {
 | 
			
		||||
      benchmarkStatus
 | 
			
		||||
          .print('No SecretKey!\nGo in Key tab and generate or derive one.');
 | 
			
		||||
      return;
 | 
			
		||||
    } else if (!Session.aesCipher.isInitialized) {
 | 
			
		||||
      benchmarkStatus.print(
 | 
			
		||||
          'Cipher not initialized!\nGo in Key tab and generate or derive one.');
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    benchmarkStatus.print("Benchmark 1/5/10/25/50MB\n");
 | 
			
		||||
    List<int> testedSizes = [1, 5, 10, 25, 50];
 | 
			
		||||
 | 
			
		||||
    var beforeBench = DateTime.now();
 | 
			
		||||
    for (int size in testedSizes) {
 | 
			
		||||
      var bigFile = Uint8List(size * 1000000);
 | 
			
		||||
      var before = DateTime.now();
 | 
			
		||||
      var encryptedBigFile = await Session.aesCipher.encrypt(bigFile);
 | 
			
		||||
      var after = DateTime.now();
 | 
			
		||||
      var benchmark =
 | 
			
		||||
          after.millisecondsSinceEpoch - before.millisecondsSinceEpoch;
 | 
			
		||||
      benchmarkStatus.append('[$size MB] Encryption took $benchmark ms\n');
 | 
			
		||||
      before = DateTime.now();
 | 
			
		||||
      await Session.aesCipher.decrypt(encryptedBigFile);
 | 
			
		||||
      after = DateTime.now();
 | 
			
		||||
      benchmark = after.millisecondsSinceEpoch - before.millisecondsSinceEpoch;
 | 
			
		||||
      benchmarkStatus.append('[$size MB] Decryption took $benchmark ms\n\n');
 | 
			
		||||
    }
 | 
			
		||||
    var afterBench = DateTime.now();
 | 
			
		||||
    var benchmark =
 | 
			
		||||
        afterBench.millisecondsSinceEpoch - beforeBench.millisecondsSinceEpoch;
 | 
			
		||||
    var sum = testedSizes.reduce((a, b) => a + b);
 | 
			
		||||
    benchmarkStatus.append(
 | 
			
		||||
        'Benchmark finished.\nGenerated, encrypted and decrypted $sum MB in $benchmark ms');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _clear() {
 | 
			
		||||
    benchmarkStatus.clear();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    super.initState();
 | 
			
		||||
    if (Session.secretKey != null) {
 | 
			
		||||
      keyContent.print(Session.secretKey.encoded.toString());
 | 
			
		||||
      Session.aesCipher = AESCipher(
 | 
			
		||||
        Session.secretKey,
 | 
			
		||||
        CipherParameters(
 | 
			
		||||
          BlockCipherMode.CBC,
 | 
			
		||||
          PlainTextPadding.PKCS5,
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return SingleChildScrollView(
 | 
			
		||||
      child: Padding(
 | 
			
		||||
        padding: const EdgeInsets.all(8.0),
 | 
			
		||||
        child: Column(
 | 
			
		||||
          children: [
 | 
			
		||||
            Align(
 | 
			
		||||
              child: Text("Secret Key"),
 | 
			
		||||
              alignment: Alignment.centerLeft,
 | 
			
		||||
            ),
 | 
			
		||||
            keyContent,
 | 
			
		||||
            Row(
 | 
			
		||||
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
 | 
			
		||||
              children: [
 | 
			
		||||
                Button(
 | 
			
		||||
                  onPressed: _benchmark,
 | 
			
		||||
                  label: "Launch benchmark",
 | 
			
		||||
                ),
 | 
			
		||||
                Button(
 | 
			
		||||
                  onPressed: _clear,
 | 
			
		||||
                  label: "Clear",
 | 
			
		||||
                ),
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
            benchmarkStatus,
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										205
									
								
								example/lib/pages/cipherPage.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										205
									
								
								example/lib/pages/cipherPage.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,205 @@
 | 
			
		||||
// Copyright (c) 2020
 | 
			
		||||
// Author: Hugo Pointcheval
 | 
			
		||||
import 'dart:typed_data';
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:native_crypto/native_crypto.dart';
 | 
			
		||||
 | 
			
		||||
import '../session.dart';
 | 
			
		||||
import '../utils.dart';
 | 
			
		||||
import '../widgets/button.dart';
 | 
			
		||||
import '../widgets/output.dart';
 | 
			
		||||
 | 
			
		||||
class CipherPage extends StatefulWidget {
 | 
			
		||||
  const CipherPage({key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  _CipherPageState createState() => _CipherPageState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _CipherPageState extends State<CipherPage> {
 | 
			
		||||
  final Output keyContent = Output(
 | 
			
		||||
    textEditingController: TextEditingController(),
 | 
			
		||||
  );
 | 
			
		||||
  final Output encryptionStatus = Output(
 | 
			
		||||
    textEditingController: TextEditingController(),
 | 
			
		||||
  );
 | 
			
		||||
  final Output decryptionStatus = Output(
 | 
			
		||||
    textEditingController: TextEditingController(),
 | 
			
		||||
  );
 | 
			
		||||
  final Output cipherExport = Output(
 | 
			
		||||
    textEditingController: TextEditingController(),
 | 
			
		||||
    large: true,
 | 
			
		||||
    editable: true,
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  final TextEditingController _plainTextController = TextEditingController();
 | 
			
		||||
  CipherText cipherText;
 | 
			
		||||
 | 
			
		||||
  void _encrypt() async {
 | 
			
		||||
    final plainText = _plainTextController.text.trim();
 | 
			
		||||
 | 
			
		||||
    if (Session.secretKey == null || Session.secretKey.isEmpty) {
 | 
			
		||||
      encryptionStatus
 | 
			
		||||
          .print('No SecretKey!\nGo in Key tab and generate or derive one.');
 | 
			
		||||
    } else if (!Session.aesCipher.isInitialized) {
 | 
			
		||||
      encryptionStatus.print(
 | 
			
		||||
          'Cipher not initialized!\nGo in Key tab and generate or derive one.');
 | 
			
		||||
    } else if (plainText.isEmpty) {
 | 
			
		||||
      encryptionStatus.print('Entry is empty');
 | 
			
		||||
    } else {
 | 
			
		||||
      var stringToBytes = TypeHelper.stringToBytes(plainText);
 | 
			
		||||
      cipherText = await Session.aesCipher.encrypt(stringToBytes);
 | 
			
		||||
      encryptionStatus.print('String successfully encrypted.\n');
 | 
			
		||||
      encryptionStatus.append("IV: " +
 | 
			
		||||
          cipherText.iv.toString() +
 | 
			
		||||
          "\nCipherText: " +
 | 
			
		||||
          cipherText.bytes.toString());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _alter() async {
 | 
			
		||||
    if (cipherText == null || cipherText.bytes.isEmpty) {
 | 
			
		||||
      decryptionStatus.print('Encrypt before altering CipherText!');
 | 
			
		||||
    } else {
 | 
			
		||||
      // Add 1 to the first byte
 | 
			
		||||
      Uint8List _altered = cipherText.bytes;
 | 
			
		||||
      _altered[0] += 1;
 | 
			
		||||
      // Recreate cipher text with altered data
 | 
			
		||||
      cipherText = AESCipherText(_altered, cipherText.iv);
 | 
			
		||||
      encryptionStatus.print('String successfully encrypted.\n');
 | 
			
		||||
      encryptionStatus.append("IV: " +
 | 
			
		||||
          cipherText.iv.toString() +
 | 
			
		||||
          "\nCipherText: " +
 | 
			
		||||
          cipherText.bytes.toString());
 | 
			
		||||
      decryptionStatus.print('CipherText altered!\nDecryption will fail.');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _decrypt() async {
 | 
			
		||||
    if (Session.secretKey == null || Session.secretKey.isEmpty) {
 | 
			
		||||
      decryptionStatus
 | 
			
		||||
          .print('No SecretKey!\nGo in Key tab and generate or derive one.');
 | 
			
		||||
    } else if (!Session.aesCipher.isInitialized) {
 | 
			
		||||
      decryptionStatus.print(
 | 
			
		||||
          'Cipher not initialized!\nGo in Key tab and generate or derive one.');
 | 
			
		||||
    } else if (cipherText == null || cipherText.bytes.isEmpty) {
 | 
			
		||||
      decryptionStatus.print('Encrypt before decrypting!');
 | 
			
		||||
    } else {
 | 
			
		||||
      try {
 | 
			
		||||
        Uint8List plainText = await Session.aesCipher.decrypt(cipherText);
 | 
			
		||||
        var bytesToString = TypeHelper.bytesToString(plainText);
 | 
			
		||||
        decryptionStatus
 | 
			
		||||
            .print('String successfully decrypted:\n\n$bytesToString');
 | 
			
		||||
      } on DecryptionException catch (e) {
 | 
			
		||||
        decryptionStatus.print(e.message);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _export() async {
 | 
			
		||||
    if (cipherText == null) {
 | 
			
		||||
      decryptionStatus.print('Encrypt data before export!');
 | 
			
		||||
    } else {
 | 
			
		||||
      Uint8List payload = Uint8List.fromList(cipherText.iv + cipherText.bytes);
 | 
			
		||||
      String data = TypeHelper.bytesToBase64(payload);
 | 
			
		||||
      decryptionStatus.print('CipherText successfully exported');
 | 
			
		||||
      cipherExport.print(data);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _import() async {
 | 
			
		||||
    final String data = cipherExport.read();
 | 
			
		||||
    if (data.isEmpty) {
 | 
			
		||||
      encryptionStatus.print('CipherText import failed');
 | 
			
		||||
    } else {
 | 
			
		||||
      Uint8List payload = TypeHelper.base64ToBytes(data);
 | 
			
		||||
      Uint8List iv = payload.sublist(0, 16);
 | 
			
		||||
      Uint8List bytes = payload.sublist(16);
 | 
			
		||||
      cipherText = AESCipherText(bytes, iv);
 | 
			
		||||
      encryptionStatus.print('CipherText successfully imported\n');
 | 
			
		||||
      encryptionStatus.append("IV: " +
 | 
			
		||||
          cipherText.iv.toString() +
 | 
			
		||||
          "\nCipherText: " +
 | 
			
		||||
          cipherText.bytes.toString());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    super.initState();
 | 
			
		||||
    if (Session.secretKey != null) {
 | 
			
		||||
      keyContent.print(Session.secretKey.encoded.toString());
 | 
			
		||||
      Session.aesCipher = AESCipher(
 | 
			
		||||
        Session.secretKey,
 | 
			
		||||
        CipherParameters(
 | 
			
		||||
          BlockCipherMode.CBC,
 | 
			
		||||
          PlainTextPadding.PKCS5,
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void dispose() {
 | 
			
		||||
    super.dispose();
 | 
			
		||||
    _plainTextController.dispose();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return SingleChildScrollView(
 | 
			
		||||
      child: Padding(
 | 
			
		||||
        padding: const EdgeInsets.all(8.0),
 | 
			
		||||
        child: Column(
 | 
			
		||||
          children: [
 | 
			
		||||
            Align(
 | 
			
		||||
              child: Text("Secret Key"),
 | 
			
		||||
              alignment: Alignment.centerLeft,
 | 
			
		||||
            ),
 | 
			
		||||
            keyContent,
 | 
			
		||||
            TextField(
 | 
			
		||||
              controller: _plainTextController,
 | 
			
		||||
              decoration: InputDecoration(
 | 
			
		||||
                hintText: 'Plain text',
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
            Button(
 | 
			
		||||
              onPressed: _encrypt,
 | 
			
		||||
              label: "Encrypt",
 | 
			
		||||
            ),
 | 
			
		||||
            encryptionStatus,
 | 
			
		||||
            Row(
 | 
			
		||||
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
 | 
			
		||||
              children: [
 | 
			
		||||
                Button(
 | 
			
		||||
                  onPressed: _alter,
 | 
			
		||||
                  label: "Alter cipher",
 | 
			
		||||
                ),
 | 
			
		||||
                Button(
 | 
			
		||||
                  onPressed: _decrypt,
 | 
			
		||||
                  label: "Decrypt",
 | 
			
		||||
                ),
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
            decryptionStatus,
 | 
			
		||||
            Row(
 | 
			
		||||
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
 | 
			
		||||
              children: [
 | 
			
		||||
                Button(
 | 
			
		||||
                  onPressed: _export,
 | 
			
		||||
                  label: "Export cipher",
 | 
			
		||||
                ),
 | 
			
		||||
                Button(
 | 
			
		||||
                  onPressed: _import,
 | 
			
		||||
                  label: "Import cipher",
 | 
			
		||||
                ),
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
            cipherExport
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										180
									
								
								example/lib/pages/hashKeyDerivationPage.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								example/lib/pages/hashKeyDerivationPage.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,180 @@
 | 
			
		||||
// Copyright (c) 2020
 | 
			
		||||
// Author: Hugo Pointcheval
 | 
			
		||||
import 'dart:typed_data';
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:native_crypto/native_crypto.dart';
 | 
			
		||||
 | 
			
		||||
import '../session.dart';
 | 
			
		||||
import '../utils.dart';
 | 
			
		||||
import '../widgets/button.dart';
 | 
			
		||||
import '../widgets/output.dart';
 | 
			
		||||
 | 
			
		||||
class HashKeyDerivationPage extends StatefulWidget {
 | 
			
		||||
  const HashKeyDerivationPage({key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  _HashKeyDerivationPageState createState() => _HashKeyDerivationPageState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _HashKeyDerivationPageState extends State<HashKeyDerivationPage> {
 | 
			
		||||
  final Output keyContent = Output(
 | 
			
		||||
    textEditingController: TextEditingController(),
 | 
			
		||||
  );
 | 
			
		||||
  final Output keyStatus = Output(
 | 
			
		||||
    textEditingController: TextEditingController(),
 | 
			
		||||
  );
 | 
			
		||||
  final Output keyExport = Output(
 | 
			
		||||
    textEditingController: TextEditingController(),
 | 
			
		||||
    large: true,
 | 
			
		||||
    editable: true,
 | 
			
		||||
  );
 | 
			
		||||
  final Output pbkdf2Status = Output(
 | 
			
		||||
    textEditingController: TextEditingController(),
 | 
			
		||||
  );
 | 
			
		||||
  final Output hashStatus = Output(
 | 
			
		||||
    textEditingController: TextEditingController(),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  final TextEditingController _pwdTextController = TextEditingController();
 | 
			
		||||
  final TextEditingController _messageTextController = TextEditingController();
 | 
			
		||||
  final TextEditingController _keyTextController = TextEditingController();
 | 
			
		||||
 | 
			
		||||
  void _generate() async {
 | 
			
		||||
    try {
 | 
			
		||||
      Session.secretKey = await SecretKey.generate(256, CipherAlgorithm.AES);
 | 
			
		||||
      keyContent.print(Session.secretKey.encoded.toString());
 | 
			
		||||
      keyStatus.print(
 | 
			
		||||
          "Secret Key successfully generated.\nLength: ${Session.secretKey.encoded.length} bytes");
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      keyStatus.print(e.message);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _pbkdf2() async {
 | 
			
		||||
    final password = _pwdTextController.text.trim();
 | 
			
		||||
 | 
			
		||||
    if (password.isEmpty) {
 | 
			
		||||
      pbkdf2Status.print('Password is empty');
 | 
			
		||||
    } else {
 | 
			
		||||
      PBKDF2 _pbkdf2 =
 | 
			
		||||
          PBKDF2(keyLength: 32, iteration: 1000, hash: HashAlgorithm.SHA512);
 | 
			
		||||
      await _pbkdf2.derive(password: password, salt: 'salty');
 | 
			
		||||
      SecretKey key = _pbkdf2.key;
 | 
			
		||||
      pbkdf2Status.print('Key successfully derived.');
 | 
			
		||||
      Session.secretKey = key;
 | 
			
		||||
      keyContent.print(Session.secretKey.encoded.toString());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _export() async {
 | 
			
		||||
    if (Session.secretKey == null || Session.secretKey.isEmpty) {
 | 
			
		||||
      keyStatus
 | 
			
		||||
          .print('No SecretKey!\nGenerate or derive one before exporting!');
 | 
			
		||||
    } else {
 | 
			
		||||
      String key = TypeHelper.bytesToBase64(Session.secretKey.encoded);
 | 
			
		||||
      keyStatus.print('Key successfully exported');
 | 
			
		||||
      keyExport.print(key);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _import() async {
 | 
			
		||||
    final String key = keyExport.read();
 | 
			
		||||
    if (key.isEmpty) {
 | 
			
		||||
      keyStatus.print('Key import failed');
 | 
			
		||||
    } else {
 | 
			
		||||
      Uint8List keyBytes = TypeHelper.base64ToBytes(key);
 | 
			
		||||
      Session.secretKey =
 | 
			
		||||
          SecretKey.fromBytes(keyBytes, algorithm: CipherAlgorithm.AES);
 | 
			
		||||
      keyStatus.print('Key successfully imported');
 | 
			
		||||
      keyContent.print(Session.secretKey.encoded.toString());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _hash() async {
 | 
			
		||||
    final message = _messageTextController.text.trim();
 | 
			
		||||
 | 
			
		||||
    if (message.isEmpty) {
 | 
			
		||||
      hashStatus.print('Message is empty');
 | 
			
		||||
    } else {
 | 
			
		||||
      MessageDigest md = MessageDigest.getInstance("sha256");
 | 
			
		||||
      Uint8List hash = await md.digest(TypeHelper.stringToBytes(message));
 | 
			
		||||
      hashStatus.print('Message successfully hashed.\n' + hash.toString());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    super.initState();
 | 
			
		||||
    if (Session.secretKey != null) {
 | 
			
		||||
      keyContent.print(Session.secretKey.encoded.toString());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void dispose() {
 | 
			
		||||
    super.dispose();
 | 
			
		||||
    _pwdTextController.dispose();
 | 
			
		||||
    _messageTextController.dispose();
 | 
			
		||||
    _keyTextController.dispose();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return SingleChildScrollView(
 | 
			
		||||
      child: Padding(
 | 
			
		||||
        padding: const EdgeInsets.all(8.0),
 | 
			
		||||
        child: Column(
 | 
			
		||||
          children: [
 | 
			
		||||
            Align(
 | 
			
		||||
              child: Text("Secret Key"),
 | 
			
		||||
              alignment: Alignment.centerLeft,
 | 
			
		||||
            ),
 | 
			
		||||
            keyContent,
 | 
			
		||||
            Button(
 | 
			
		||||
              onPressed: _generate,
 | 
			
		||||
              label: "Generate key",
 | 
			
		||||
            ),
 | 
			
		||||
            keyStatus,
 | 
			
		||||
            TextField(
 | 
			
		||||
              controller: _pwdTextController,
 | 
			
		||||
              decoration: InputDecoration(
 | 
			
		||||
                hintText: 'Password',
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
            Button(
 | 
			
		||||
              onPressed: _pbkdf2,
 | 
			
		||||
              label: "Apply PBKDF2",
 | 
			
		||||
            ),
 | 
			
		||||
            pbkdf2Status,
 | 
			
		||||
            TextField(
 | 
			
		||||
              controller: _messageTextController,
 | 
			
		||||
              decoration: InputDecoration(
 | 
			
		||||
                hintText: 'Message',
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
            Button(
 | 
			
		||||
              onPressed: _hash,
 | 
			
		||||
              label: "Hash",
 | 
			
		||||
            ),
 | 
			
		||||
            hashStatus,
 | 
			
		||||
            Row(
 | 
			
		||||
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
 | 
			
		||||
              children: [
 | 
			
		||||
                Button(
 | 
			
		||||
                  onPressed: _export,
 | 
			
		||||
                  label: "Export key",
 | 
			
		||||
                ),
 | 
			
		||||
                Button(
 | 
			
		||||
                  onPressed: _import,
 | 
			
		||||
                  label: "Import key",
 | 
			
		||||
                ),
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
            keyExport
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								example/lib/pages/kemPage.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								example/lib/pages/kemPage.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
// Copyright (c) 2020
 | 
			
		||||
// Author: Hugo Pointcheval
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
class KemPage extends StatefulWidget {
 | 
			
		||||
  KemPage({Key key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  _KemPageState createState() => _KemPageState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _KemPageState extends State<KemPage> {
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Container(
 | 
			
		||||
      child: Center(
 | 
			
		||||
        child: Text("Not implemented."),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								example/lib/session.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								example/lib/session.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
			
		||||
// Copyright (c) 2020
 | 
			
		||||
// Author: Hugo Pointcheval
 | 
			
		||||
 | 
			
		||||
import 'package:native_crypto/native_crypto.dart';
 | 
			
		||||
 | 
			
		||||
class Session {
 | 
			
		||||
  static SecretKey secretKey;
 | 
			
		||||
  static AESCipher aesCipher;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								example/lib/utils.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								example/lib/utils.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
			
		||||
// Copyright (c) 2020
 | 
			
		||||
// Author: Hugo Pointcheval
 | 
			
		||||
import 'dart:typed_data';
 | 
			
		||||
import 'dart:convert';
 | 
			
		||||
 | 
			
		||||
/// Contains some useful functions.
 | 
			
		||||
class TypeHelper {
 | 
			
		||||
  /// Returns bytes [Uint8List] from a [String].
 | 
			
		||||
  static Uint8List stringToBytes(String source) {
 | 
			
		||||
    var list = source.runes.toList();
 | 
			
		||||
    var bytes = Uint8List.fromList(list);
 | 
			
		||||
    return bytes;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Returns a [String] from bytes [Uint8List].
 | 
			
		||||
  static String bytesToString(Uint8List bytes) {
 | 
			
		||||
    var string = String.fromCharCodes(bytes);
 | 
			
		||||
    return string;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Returns a `base64` [String] from bytes [Uint8List].
 | 
			
		||||
  static String bytesToBase64(Uint8List bytes) {
 | 
			
		||||
    return base64.encode(bytes);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Returns a [Uint8List] from a `base64` [String].
 | 
			
		||||
  static Uint8List base64ToBytes(String encoded) {
 | 
			
		||||
    return base64.decode(encoded);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								example/lib/widgets/button.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								example/lib/widgets/button.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
			
		||||
// Copyright (c) 2020
 | 
			
		||||
// Author: Hugo Pointcheval
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
class Button extends StatelessWidget {
 | 
			
		||||
  const Button({Key key, this.onPressed, this.label}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  final void Function() onPressed;
 | 
			
		||||
  final String label;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Container(
 | 
			
		||||
      child: FlatButton(
 | 
			
		||||
        onPressed: onPressed,
 | 
			
		||||
        color: Colors.blue,
 | 
			
		||||
        child: Text(
 | 
			
		||||
          label,
 | 
			
		||||
          style: TextStyle(color: Colors.white),
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										50
									
								
								example/lib/widgets/output.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								example/lib/widgets/output.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,50 @@
 | 
			
		||||
// Copyright (c) 2020
 | 
			
		||||
// Author: Hugo Pointcheval
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
class Output extends StatelessWidget {
 | 
			
		||||
  const Output(
 | 
			
		||||
      {Key key,
 | 
			
		||||
      this.textEditingController,
 | 
			
		||||
      this.large: false,
 | 
			
		||||
      this.editable: false})
 | 
			
		||||
      : super(key: key);
 | 
			
		||||
 | 
			
		||||
  final TextEditingController textEditingController;
 | 
			
		||||
  final bool large;
 | 
			
		||||
  final bool editable;
 | 
			
		||||
 | 
			
		||||
  void print(String message) {
 | 
			
		||||
    textEditingController.text = message;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void append(String message) {
 | 
			
		||||
    textEditingController.text += message;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void appendln(String message) {
 | 
			
		||||
    textEditingController.text += message + "\n";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void clear() {
 | 
			
		||||
    textEditingController.clear();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  String read() {
 | 
			
		||||
    return textEditingController.text;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Container(
 | 
			
		||||
      child: TextField(
 | 
			
		||||
        enableInteractiveSelection: true,
 | 
			
		||||
        readOnly: editable ? false : true,
 | 
			
		||||
        minLines: large ? 3 : 1,
 | 
			
		||||
        maxLines: large ? 50 : 5,
 | 
			
		||||
        decoration: InputDecoration(border: OutlineInputBorder()),
 | 
			
		||||
        controller: textEditingController,
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user