part of cool_ui; typedef GetKeyboardHeight = double Function(BuildContext context); typedef KeyboardBuilder = Widget Function(BuildContext context,KeyboardController controller); class CoolKeyboard { static JSONMethodCodec _codec = const JSONMethodCodec(); static KeyboardConfig _currentKeyboard; static Map _keyboards = {}; static BuildContext _context; static OverlayEntry _keyboardEntry; static KeyboardController _keyboardController; static GlobalKey _pageKey; static bool isInterceptor = false; static double get keyboardHeight =>_keyboardHeight; static double _keyboardHeight; static init(BuildContext context){ _context = context; interceptorInput(); } static interceptorInput(){ if(isInterceptor) return; isInterceptor = true; BinaryMessages.setMockMessageHandler("flutter/textinput", (ByteData data) async{ var methodCall = _codec.decodeMethodCall(data); switch(methodCall.method){ case 'TextInput.show': if(_currentKeyboard != null){ openKeyboard(); return _codec.encodeSuccessEnvelope(null); }else{ return await _sendPlatformMessage("flutter/textinput", data); } break; case 'TextInput.hide': if(_currentKeyboard != null){ hideKeyboard(); return _codec.encodeSuccessEnvelope(null); }else{ return await _sendPlatformMessage("flutter/textinput", data); } break; case 'TextInput.setEditingState': var editingState = TextEditingValue.fromJSON(methodCall.arguments); if(editingState != null && _keyboardController != null){ _keyboardController.value = editingState; return _codec.encodeSuccessEnvelope(null); } break; case 'TextInput.clearClient': hideKeyboard(animation:true); clearKeyboard(); break; case 'TextInput.setClient': var setInputType = methodCall.arguments[1]['inputType']; InputClient client; _keyboards.forEach((inputType,keyboardConfig){ if(inputType.name == setInputType['name']){ client = InputClient.fromJSON(methodCall.arguments); clearKeyboard(); _currentKeyboard = keyboardConfig; _keyboardController = KeyboardController(client:client)..addListener((){ var callbackMethodCall = MethodCall("TextInputClient.updateEditingState",[_keyboardController.client.connectionId,_keyboardController.value.toJSON()]); BinaryMessages.handlePlatformMessage("flutter/textinput", _codec.encodeMethodCall(callbackMethodCall), (data){}); }); } }); if(client != null){ await _sendPlatformMessage("flutter/textinput", _codec.encodeMethodCall(MethodCall('TextInput.hide'))); return _codec.encodeSuccessEnvelope(null); }else{ hideKeyboard(animation:false); clearKeyboard(); } break; } ByteData response = await _sendPlatformMessage("flutter/textinput", data); return response; }); } static Future _sendPlatformMessage(String channel, ByteData message) { final Completer completer = Completer(); ui.window.sendPlatformMessage(channel, message, (ByteData reply) { try { completer.complete(reply); } catch (exception, stack) { FlutterError.reportError(FlutterErrorDetails( exception: exception, stack: stack, library: 'services library', context: 'during a platform message response callback', )); } }); return completer.future; } static addKeyboard(CKTextInputType inputType,KeyboardConfig config){ _keyboards[inputType] = config; } static openKeyboard(){ if(_keyboardEntry != null) return; _pageKey = GlobalKey(); _keyboardHeight = _currentKeyboard.getHeight(_context); _context.ancestorStateOfType(const TypeMatcher()).setState((){}); var tempKey = _pageKey; _keyboardEntry = OverlayEntry(builder: (ctx) { if(_currentKeyboard != null && _keyboardHeight != null) { return KeyboardPage( key: tempKey, child: Builder(builder: (ctx){ return _currentKeyboard.builder(ctx,_keyboardController); }), height:_keyboardHeight ); }else{ return Container(); } }); Overlay.of(_context).insert(_keyboardEntry); } static hideKeyboard({bool animation=true}){ if(_keyboardEntry != null) { _keyboardHeight = null; _pageKey.currentState.animationController.addStatusListener((status) { if (status == AnimationStatus.dismissed || status == AnimationStatus.completed) { if (_keyboardEntry != null) { _keyboardEntry.remove(); _keyboardEntry = null; } } }); if (animation) { _pageKey.currentState.exitKeyboard(); } else{ _keyboardEntry.remove(); _keyboardEntry = null; } } _pageKey = null; _context.ancestorStateOfType(const TypeMatcher()).setState((){}); } static clearKeyboard(){ _currentKeyboard = null; if(_keyboardController != null){ _keyboardController.dispose(); _keyboardController = null; } } static sendPerformAction(TextInputAction action){ var callbackMethodCall = MethodCall("TextInputClient.performAction", [ _keyboardController.client.connectionId, action.toString() ]); BinaryMessages.handlePlatformMessage( "flutter/textinput", _codec.encodeMethodCall(callbackMethodCall), ( data) {}); } } class KeyboardConfig{ final KeyboardBuilder builder; final GetKeyboardHeight getHeight; const KeyboardConfig({this.builder,this.getHeight}); } class InputClient{ final int connectionId; final TextInputConfiguration configuration; const InputClient({this.connectionId,this.configuration}); factory InputClient.fromJSON(List encoded) { return InputClient(connectionId: encoded[0],configuration: TextInputConfiguration( inputType:CKTextInputType.fromJSON(encoded[1]['inputType']), obscureText:encoded[1]['obscureText'], autocorrect:encoded[1]['autocorrect'], actionLabel:encoded[1]['actionLabel'], inputAction:_toTextInputAction(encoded[1]['inputAction']), textCapitalization:_toTextCapitalization(encoded[1]['textCapitalization']), keyboardAppearance:_toBrightness(encoded[1]['keyboardAppearance']) )); } static TextInputAction _toTextInputAction(String action) { switch (action) { case 'TextInputAction.none': return TextInputAction.none; case 'TextInputAction.unspecified': return TextInputAction.unspecified; case 'TextInputAction.go': return TextInputAction.go; case 'TextInputAction.search': return TextInputAction.search; case 'TextInputAction.send': return TextInputAction.send; case 'TextInputAction.next': return TextInputAction.next; case 'TextInputAction.previuos': return TextInputAction.previous; case 'TextInputAction.continue_action': return TextInputAction.continueAction; case 'TextInputAction.join': return TextInputAction.join; case 'TextInputAction.route': return TextInputAction.route; case 'TextInputAction.emergencyCall': return TextInputAction.emergencyCall; case 'TextInputAction.done': return TextInputAction.done; case 'TextInputAction.newline': return TextInputAction.newline; } throw FlutterError('Unknown text input action: $action'); } static TextCapitalization _toTextCapitalization(String capitalization){ switch(capitalization){ case 'TextCapitalization.none': return TextCapitalization.none; case 'TextCapitalization.characters': return TextCapitalization.characters; case 'TextCapitalization.sentences': return TextCapitalization.sentences; case 'TextCapitalization.words': return TextCapitalization.words; } throw FlutterError('Unknown text capitalization: $capitalization'); } static Brightness _toBrightness(String brightness){ switch(brightness){ case 'Brightness.dark': return Brightness.dark; case 'Brightness.light': return Brightness.light; } throw FlutterError('Unknown Brightness: $brightness'); } } class CKTextInputType extends TextInputType{ final String name; const CKTextInputType({this.name,bool signed,bool decimal}) : super.numberWithOptions(signed:signed,decimal:decimal); @override Map toJson() { return { 'name': name, 'signed': signed, 'decimal': decimal, }; } factory CKTextInputType.fromJSON(Map encoded) { return CKTextInputType( name: encoded['name'], signed: encoded['signed'], decimal: encoded['decimal'] ); } } class KeyboardPage extends StatefulWidget{ final Widget child; final double height; const KeyboardPage({this.child,this.height,Key key}):super(key:key); @override State createState() =>KeyboardPageState(); } class KeyboardPageState extends State with SingleTickerProviderStateMixin{ AnimationController animationController; Animation doubleAnimation; double bottom; @override void initState() { // TODO: implement initState super.initState(); animationController = new AnimationController(duration: new Duration(milliseconds: 100),vsync: this) ..addListener(()=>setState((){})); doubleAnimation = new Tween(begin: 0.0, end: widget.height).animate(animationController)..addListener(()=>setState((){})); animationController.forward(from:0.0); } @override Widget build(BuildContext context) { return Positioned( child: IntrinsicHeight( child: widget.child ), bottom: (widget.height - doubleAnimation.value) * -1 ); } @override void dispose() { super.dispose(); animationController.dispose(); } exitKeyboard(){ animationController.reverse(); } }