keyboard_manager.dart 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. part of cool_ui;
  2. typedef GetKeyboardHeight = double Function(BuildContext context);
  3. typedef KeyboardBuilder = Widget Function(BuildContext context,KeyboardController controller);
  4. class CoolKeyboard {
  5. static JSONMethodCodec _codec = const JSONMethodCodec();
  6. static KeyboardConfig _currentKeyboard;
  7. static Map<CKTextInputType,KeyboardConfig> _keyboards = {};
  8. static BuildContext _context;
  9. static OverlayEntry _keyboardEntry;
  10. static KeyboardController _keyboardController;
  11. static GlobalKey<KeyboardPageState> _pageKey;
  12. static bool isInterceptor = false;
  13. static double get keyboardHeight =>_keyboardHeight;
  14. static double _keyboardHeight;
  15. static init(BuildContext context){
  16. _context = context;
  17. interceptorInput();
  18. }
  19. static interceptorInput(){
  20. if(isInterceptor)
  21. return;
  22. isInterceptor = true;
  23. BinaryMessages.setMockMessageHandler("flutter/textinput", (ByteData data) async{
  24. var methodCall = _codec.decodeMethodCall(data);
  25. switch(methodCall.method){
  26. case 'TextInput.show':
  27. if(_currentKeyboard != null){
  28. openKeyboard();
  29. return _codec.encodeSuccessEnvelope(null);
  30. }else{
  31. return await _sendPlatformMessage("flutter/textinput", data);
  32. }
  33. break;
  34. case 'TextInput.hide':
  35. if(_currentKeyboard != null){
  36. hideKeyboard();
  37. return _codec.encodeSuccessEnvelope(null);
  38. }else{
  39. return await _sendPlatformMessage("flutter/textinput", data);
  40. }
  41. break;
  42. case 'TextInput.setEditingState':
  43. var editingState = TextEditingValue.fromJSON(methodCall.arguments);
  44. if(editingState != null && _keyboardController != null){
  45. _keyboardController.value = editingState;
  46. return _codec.encodeSuccessEnvelope(null);
  47. }
  48. break;
  49. case 'TextInput.clearClient':
  50. hideKeyboard(animation:true);
  51. clearKeyboard();
  52. break;
  53. case 'TextInput.setClient':
  54. var setInputType = methodCall.arguments[1]['inputType'];
  55. InputClient client;
  56. _keyboards.forEach((inputType,keyboardConfig){
  57. if(inputType.name == setInputType['name']){
  58. client = InputClient.fromJSON(methodCall.arguments);
  59. clearKeyboard();
  60. _currentKeyboard = keyboardConfig;
  61. _keyboardController = KeyboardController(client:client)..addListener((){
  62. var callbackMethodCall = MethodCall("TextInputClient.updateEditingState",[_keyboardController.client.connectionId,_keyboardController.value.toJSON()]);
  63. BinaryMessages.handlePlatformMessage("flutter/textinput", _codec.encodeMethodCall(callbackMethodCall), (data){});
  64. });
  65. }
  66. });
  67. if(client != null){
  68. await _sendPlatformMessage("flutter/textinput", _codec.encodeMethodCall(MethodCall('TextInput.hide')));
  69. return _codec.encodeSuccessEnvelope(null);
  70. }else{
  71. hideKeyboard(animation:false);
  72. clearKeyboard();
  73. }
  74. break;
  75. }
  76. ByteData response = await _sendPlatformMessage("flutter/textinput", data);
  77. return response;
  78. });
  79. }
  80. static Future<ByteData> _sendPlatformMessage(String channel, ByteData message) {
  81. final Completer<ByteData> completer = Completer<ByteData>();
  82. ui.window.sendPlatformMessage(channel, message, (ByteData reply) {
  83. try {
  84. completer.complete(reply);
  85. } catch (exception, stack) {
  86. FlutterError.reportError(FlutterErrorDetails(
  87. exception: exception,
  88. stack: stack,
  89. library: 'services library',
  90. context: 'during a platform message response callback',
  91. ));
  92. }
  93. });
  94. return completer.future;
  95. }
  96. static addKeyboard(CKTextInputType inputType,KeyboardConfig config){
  97. _keyboards[inputType] = config;
  98. }
  99. static openKeyboard(){
  100. if(_keyboardEntry != null)
  101. return;
  102. _pageKey = GlobalKey<KeyboardPageState>();
  103. _keyboardHeight = _currentKeyboard.getHeight(_context);
  104. _context.ancestorStateOfType(const TypeMatcher<KeyboardMediaQueryState>()).setState((){});
  105. var tempKey = _pageKey;
  106. _keyboardEntry = OverlayEntry(builder: (ctx) {
  107. if(_currentKeyboard != null && _keyboardHeight != null)
  108. {
  109. return KeyboardPage(
  110. key: tempKey,
  111. child: Builder(builder: (ctx){
  112. return _currentKeyboard.builder(ctx,_keyboardController);
  113. }),
  114. height:_keyboardHeight
  115. );
  116. }else{
  117. return Container();
  118. }
  119. });
  120. Overlay.of(_context).insert(_keyboardEntry);
  121. }
  122. static hideKeyboard({bool animation=true}){
  123. if(_keyboardEntry != null) {
  124. _keyboardHeight = null;
  125. _pageKey.currentState.animationController.addStatusListener((status) {
  126. if (status == AnimationStatus.dismissed ||
  127. status == AnimationStatus.completed) {
  128. if (_keyboardEntry != null) {
  129. _keyboardEntry.remove();
  130. _keyboardEntry = null;
  131. }
  132. }
  133. });
  134. if (animation)
  135. {
  136. _pageKey.currentState.exitKeyboard();
  137. }
  138. else{
  139. _keyboardEntry.remove();
  140. _keyboardEntry = null;
  141. }
  142. }
  143. _pageKey = null;
  144. _context.ancestorStateOfType(const TypeMatcher<KeyboardMediaQueryState>()).setState((){});
  145. }
  146. static clearKeyboard(){
  147. _currentKeyboard = null;
  148. if(_keyboardController != null){
  149. _keyboardController.dispose();
  150. _keyboardController = null;
  151. }
  152. }
  153. static sendPerformAction(TextInputAction action){
  154. var callbackMethodCall = MethodCall("TextInputClient.performAction",
  155. [
  156. _keyboardController.client.connectionId,
  157. action.toString()
  158. ]);
  159. BinaryMessages.handlePlatformMessage(
  160. "flutter/textinput", _codec.encodeMethodCall(callbackMethodCall), (
  161. data) {});
  162. }
  163. }
  164. class KeyboardConfig{
  165. final KeyboardBuilder builder;
  166. final GetKeyboardHeight getHeight;
  167. const KeyboardConfig({this.builder,this.getHeight});
  168. }
  169. class InputClient{
  170. final int connectionId;
  171. final TextInputConfiguration configuration;
  172. const InputClient({this.connectionId,this.configuration});
  173. factory InputClient.fromJSON(List<dynamic> encoded) {
  174. return InputClient(connectionId: encoded[0],configuration: TextInputConfiguration(
  175. inputType:CKTextInputType.fromJSON(encoded[1]['inputType']),
  176. obscureText:encoded[1]['obscureText'],
  177. autocorrect:encoded[1]['autocorrect'],
  178. actionLabel:encoded[1]['actionLabel'],
  179. inputAction:_toTextInputAction(encoded[1]['inputAction']),
  180. textCapitalization:_toTextCapitalization(encoded[1]['textCapitalization']),
  181. keyboardAppearance:_toBrightness(encoded[1]['keyboardAppearance'])
  182. ));
  183. }
  184. static TextInputAction _toTextInputAction(String action) {
  185. switch (action) {
  186. case 'TextInputAction.none':
  187. return TextInputAction.none;
  188. case 'TextInputAction.unspecified':
  189. return TextInputAction.unspecified;
  190. case 'TextInputAction.go':
  191. return TextInputAction.go;
  192. case 'TextInputAction.search':
  193. return TextInputAction.search;
  194. case 'TextInputAction.send':
  195. return TextInputAction.send;
  196. case 'TextInputAction.next':
  197. return TextInputAction.next;
  198. case 'TextInputAction.previuos':
  199. return TextInputAction.previous;
  200. case 'TextInputAction.continue_action':
  201. return TextInputAction.continueAction;
  202. case 'TextInputAction.join':
  203. return TextInputAction.join;
  204. case 'TextInputAction.route':
  205. return TextInputAction.route;
  206. case 'TextInputAction.emergencyCall':
  207. return TextInputAction.emergencyCall;
  208. case 'TextInputAction.done':
  209. return TextInputAction.done;
  210. case 'TextInputAction.newline':
  211. return TextInputAction.newline;
  212. }
  213. throw FlutterError('Unknown text input action: $action');
  214. }
  215. static TextCapitalization _toTextCapitalization(String capitalization){
  216. switch(capitalization){
  217. case 'TextCapitalization.none':
  218. return TextCapitalization.none;
  219. case 'TextCapitalization.characters':
  220. return TextCapitalization.characters;
  221. case 'TextCapitalization.sentences':
  222. return TextCapitalization.sentences;
  223. case 'TextCapitalization.words':
  224. return TextCapitalization.words;
  225. }
  226. throw FlutterError('Unknown text capitalization: $capitalization');
  227. }
  228. static Brightness _toBrightness(String brightness){
  229. switch(brightness){
  230. case 'Brightness.dark':
  231. return Brightness.dark;
  232. case 'Brightness.light':
  233. return Brightness.light;
  234. }
  235. throw FlutterError('Unknown Brightness: $brightness');
  236. }
  237. }
  238. class CKTextInputType extends TextInputType{
  239. final String name;
  240. const CKTextInputType({this.name,bool signed,bool decimal}) : super.numberWithOptions(signed:signed,decimal:decimal);
  241. @override
  242. Map<String, dynamic> toJson() {
  243. return <String, dynamic>{
  244. 'name': name,
  245. 'signed': signed,
  246. 'decimal': decimal,
  247. };
  248. }
  249. factory CKTextInputType.fromJSON(Map<String,dynamic> encoded) {
  250. return CKTextInputType(
  251. name: encoded['name'],
  252. signed: encoded['signed'],
  253. decimal: encoded['decimal']
  254. );
  255. }
  256. }
  257. class KeyboardPage extends StatefulWidget{
  258. final Widget child;
  259. final double height;
  260. const KeyboardPage({this.child,this.height,Key key}):super(key:key);
  261. @override
  262. State<StatefulWidget> createState() =>KeyboardPageState();
  263. }
  264. class KeyboardPageState extends State<KeyboardPage> with SingleTickerProviderStateMixin{
  265. AnimationController animationController;
  266. Animation<double> doubleAnimation;
  267. double bottom;
  268. @override
  269. void initState() {
  270. // TODO: implement initState
  271. super.initState();
  272. animationController = new AnimationController(duration: new Duration(milliseconds: 100),vsync: this)
  273. ..addListener(()=>setState((){}));
  274. doubleAnimation =
  275. new Tween(begin: 0.0, end: widget.height).animate(animationController)..addListener(()=>setState((){}));
  276. animationController.forward(from:0.0);
  277. }
  278. @override
  279. Widget build(BuildContext context) {
  280. return Positioned(
  281. child: IntrinsicHeight(
  282. child: widget.child
  283. ),
  284. bottom: (widget.height - doubleAnimation.value) * -1
  285. );
  286. }
  287. @override
  288. void dispose() {
  289. super.dispose();
  290. animationController.dispose();
  291. }
  292. exitKeyboard(){
  293. animationController.reverse();
  294. }
  295. }