keyboard_utils_plugin 1.1.1
keyboard_utils_plugin: ^1.1.1 copied to clipboard
fork from https://github.com/IsaiasSantana/keyboard_utils
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:keyboard_utils_plugin/keyboard_utils.dart';
import 'package:keyboard_utils_plugin/keyboard_listener.dart' as keyboard_listener;
void main() {
runApp(const MyApp2());
}
class MyApp2 extends StatelessWidget {
const MyApp2({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Keyboard Utils Demo (main2)',
theme: ThemeData(primarySwatch: Colors.blue),
home: const MainPage(),
);
}
}
class BarItemModel {
String label;
IconData iconData;
BarItemModel(this.label, this.iconData);
}
class MainPage extends StatefulWidget {
const MainPage({super.key});
@override
State<MainPage> createState() => MainPageState();
}
class MainPageState extends State<MainPage> {
int currentIndex = 0;
List<BarItemModel> get barItems => [
BarItemModel('input', Icons.keyboard),
BarItemModel('normal', Icons.chat_bubble_outline),
];
double get bottomSafe => MediaQuery.of(context).padding.bottom;
@override
Widget build(BuildContext context) {
final isToInputPage = currentIndex == 0;
return Scaffold(
body: Container(
alignment: Alignment.center,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
margin: const EdgeInsets.only(bottom: 16),
child: Text('Current Index: $currentIndex'),
),
Container(
margin: const EdgeInsets.only(bottom: 24),
child: Text('Bottom Safe Area: $bottomSafe'),
),
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => isToInputPage ? const InputPage() : const NormalPage(),
),
);
},
child: Text('Open: ${isToInputPage ? "Input" : "Normal"} Page'),
),
],
),
),
bottomNavigationBar: BottomNavigationBar(
selectedItemColor: Colors.blue,
unselectedItemColor: Colors.grey,
currentIndex: currentIndex,
onTap: (index) => setState(() => currentIndex = index),
items: barItems
.map((e) => BottomNavigationBarItem(icon: Icon(e.iconData), label: e.label))
.toList(),
),
);
}
}
// Minimal bloc demonstrating KeyboardUtils stream usage
class KeyboardBloc {
final KeyboardUtils _keyboardUtils = KeyboardUtils();
final StreamController<double> _streamController = StreamController<double>();
Stream<double> get stream => _streamController.stream;
KeyboardUtils get keyboardUtils => _keyboardUtils;
late int _idKeyboardListener;
void start() {
_idKeyboardListener = _keyboardUtils.add(
listener: keyboard_listener.KeyboardListener(
willHideKeyboard: () {
_streamController.sink.add(_keyboardUtils.keyboardHeight);
},
willShowKeyboard: (double keyboardHeight) {
_streamController.sink.add(keyboardHeight);
},
),
);
}
void dispose() {
_keyboardUtils.unsubscribeListener(subscribingId: _idKeyboardListener);
if (_keyboardUtils.canCallDispose()) {
_keyboardUtils.dispose();
}
_streamController.close();
}
}
// Input sample using KeyboardUtils to read dynamic keyboard height
class InputPage extends StatefulWidget {
const InputPage({super.key});
@override
State<InputPage> createState() => _InputPageState();
}
class _InputPageState extends State<InputPage> {
final KeyboardBloc _bloc = KeyboardBloc();
final KeyboardUtils _keyboardUtils = KeyboardUtils();
double dynamicKeyboardHeight = 0;
late int _listenerId;
double get safeBottom => MediaQuery.of(context).padding.bottom;
double get safeTop => MediaQuery.of(context).padding.top;
double get devicePixelRatio => MediaQuery.of(context).devicePixelRatio;
@override
void initState() {
super.initState();
_listenerId = _keyboardUtils.add(
listener: keyboard_listener.KeyboardListener(
willHideKeyboard: () {
dynamicKeyboardHeight = 0;
setState(() {});
},
willShowKeyboard: (height) {
dynamicKeyboardHeight = height;
setState(() {});
},
),
);
_bloc.start();
}
@override
void dispose() {
_bloc.dispose();
_keyboardUtils.unsubscribeListener(subscribingId: _listenerId);
super.dispose();
}
@override
Widget build(BuildContext context) {
final viewInsetsBottom = MediaQuery.of(context).viewInsets.bottom;
final viewPaddingBottom = MediaQuery.of(context).viewPadding.bottom;
return Scaffold(
appBar: AppBar(title: const Text('Keyboard Utils - Input')),
resizeToAvoidBottomInset: false,
body: Container(
padding: EdgeInsets.only(bottom: safeBottom),
child: Center(
child: Column(
children: <Widget>[
Container(
margin: const EdgeInsets.only(bottom: 16),
child: Text(
'Top safe: $safeTop\n'
'Bottom safe: $safeBottom\n'
'Bottom inset: $viewPaddingBottom\n'
'Is open: ${_keyboardUtils.isKeyboardOpen}\n'
'Insets keyboard height: $viewInsetsBottom\n'
'Device pixel ratio: $devicePixelRatio\n'
'Dynamic keyboard height: $dynamicKeyboardHeight',
),
),
const Spacer(),
Container(
height: 50,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: const TextField(
decoration: InputDecoration(hintText: 'Please input'),
),
),
// Keep a gap equal to dynamic keyboard height for clarity in this demo
Container(height: dynamicKeyboardHeight),
],
),
),
),
);
}
}
// Normal sample reacting to viewInsets changes
class NormalPage extends StatefulWidget {
const NormalPage({super.key});
@override
State<NormalPage> createState() => _NormalPageState();
}
class _NormalPageState extends State<NormalPage> with WidgetsBindingObserver {
final List<String> message = [];
final TextEditingController controller = TextEditingController();
double keyboardHeight = 0;
double get bottomSafe => MediaQuery.of(context).padding.bottom;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
controller.dispose();
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeMetrics() {
final viewInsets = MediaQuery.of(context).viewInsets;
setState(() {
keyboardHeight = viewInsets.bottom;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Normal - Bottom Safe: $bottomSafe'),
centerTitle: true,
),
resizeToAvoidBottomInset: false,
body: Column(
children: [
Expanded(child: _messageView()),
_inputView(),
_bottomView(),
],
),
);
}
Widget _messageView() {
if (message.isEmpty) {
return Container(
alignment: Alignment.center,
color: Colors.grey.withOpacity(0.2),
child: const Text('no message'),
);
}
return ListView.builder(
itemCount: message.length,
itemBuilder: (context, index) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
color: index.isEven ? Colors.grey.withOpacity(0.2) : Colors.green.withOpacity(0.2),
constraints: const BoxConstraints(minHeight: 44),
alignment: Alignment.centerLeft,
child: Text(message[index]),
);
},
);
}
Widget _inputView() {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10),
color: Colors.red.withOpacity(0.15),
child: Row(
children: [
Expanded(
child: TextField(
decoration: const InputDecoration(
hintText: 'Please input',
border: InputBorder.none,
isCollapsed: true,
),
controller: controller,
maxLines: 4,
minLines: 1,
showCursor: true,
),
),
TextButton(
onPressed: () {
final value = controller.text.trim();
if (value.isNotEmpty) {
setState(() => message.add(value));
controller.clear();
}
},
child: const Text('send'),
),
],
),
);
}
Widget _bottomView() {
final tempHeight = keyboardHeight;
return Container(
padding: EdgeInsets.only(bottom: bottomSafe),
height: bottomSafe + tempHeight + 1,
child: const Column(
children: [
Divider(height: 1),
Expanded(
child: SingleChildScrollView(
child: SizedBox.shrink(),
),
),
],
),
);
}
}