keyboard_manager.dart 11 KB

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