Skip to content

Instantly share code, notes, and snippets.

@ykmnkmi
Created August 26, 2020 14:30
Show Gist options
  • Save ykmnkmi/e002179eeb6f8b1b5f4bc00cb46c982d to your computer and use it in GitHub Desktop.
Save ykmnkmi/e002179eeb6f8b1b5f4bc00cb46c982d to your computer and use it in GitHub Desktop.
Flutter web tab switch and unfocus example
import 'dart:ui' as ui show window;
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/widgets.dart';
void main() {
runApp(App());
}
class App extends StatefulWidget {
const App({Key key}) : super(key: key);
@override
AppState createState() {
return AppState();
}
}
class AppState extends State<App> {
@override
Widget build(BuildContext context) {
Widget result = DefaultTextStyle(
child: LoginPage(),
style: TextStyle(
color: Color(0xFF001030),
fontFamily: 'Segou UI',
fontSize: 16.0,
fontWeight: FontWeight.normal,
),
);
assert(() {
result = CheckedModeBanner(child: result);
return true;
}());
return MediaQuery(
child: DecoratedBox(
child: Shortcuts(
child: Actions(
actions: WidgetsApp.defaultActions,
child: FocusTraversalGroup(
child: FocusHandler(
child: Localizations(
child: Directionality(
child: result,
textDirection: TextDirection.ltr,
),
delegates: <LocalizationsDelegate<WidgetsLocalizations>>[
DefaultWidgetsLocalizations.delegate,
],
locale: Locale('en', 'kz'),
),
),
),
),
shortcuts: WidgetsApp.defaultShortcuts,
),
decoration: BoxDecoration(
color: Color(0xFFF2F5F9),
),
),
data: MediaQueryData.fromWindow(ui.window),
);
}
}
class FocusHandler extends InheritedWidget {
FocusHandler({Key key, @required Widget child})
: nodes = <FocusNode>{},
super(key: key, child: FocusHandlerContainer(child: child));
final Set<FocusNode> nodes;
void add(FocusNode node) {
nodes.add(node);
}
void remove(FocusNode node) {
nodes.add(node);
}
void unfocus([FocusNode except]) {
for (final node in nodes) {
if (node.hasFocus && node != except) {
node.unfocus();
}
}
}
@override
bool updateShouldNotify(FocusHandler oldWidget) {
return true;
}
static FocusHandler of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<FocusHandler>();
}
}
class FocusHandlerContainer extends StatefulWidget {
FocusHandlerContainer({Key key, @required this.child}) : super(key: key);
final Widget child;
@override
FocusHandlerContainerState createState() {
return FocusHandlerContainerState();
}
}
class FocusHandlerContainerState extends State<FocusHandlerContainer> {
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
child: widget.child,
onTap: () {
print('unfocus');
FocusHandler.of(context).unfocus();
},
);
}
}
class LoginPage extends StatefulWidget {
const LoginPage({Key key}) : super(key: key);
@override
LoginPageState createState() {
return LoginPageState();
}
}
class LoginPageState extends State<LoginPage> {
TextEditingController loginController;
TextEditingController passwordController;
@override
void initState() {
super.initState();
loginController = TextEditingController();
passwordController = TextEditingController();
}
@override
void dispose() {
passwordController.dispose();
loginController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Title(
child: Align(
child: ConstrainedBox(
child: Column(
children: <Widget>[
Padding(
child: Text('Sign In', style: TextStyle(fontSize: 16.0 * 1.75, fontWeight: FontWeight.bold)),
padding: EdgeInsets.only(bottom: 16.0 * 0.5),
),
Padding(
child: Input(
controller: loginController,
placeholder: Text('Login', style: TextStyle(color: Color(0xEE001030))),
),
padding: EdgeInsets.only(bottom: 16.0 * 0.5),
),
Padding(
child: Input(
controller: passwordController,
placeholder: Text('Password', style: TextStyle(color: Color(0xEE001030))),
obscureText: true,
),
padding: EdgeInsets.only(bottom: 16.0 * 0.5),
),
Row(
children: <Widget>[
Button(
child: Text('Sign In', style: TextStyle(color: Color(0xFFFFFFFF))),
onTap: () {
setState(() {
print('sign in');
});
},
),
],
)
],
mainAxisAlignment: MainAxisAlignment.center,
),
constraints: BoxConstraints(maxHeight: 16.0 * 20.0, maxWidth: 16.0 * 20.0),
),
),
color: Color(0xFFFFFFFF),
title: 'Sign In',
);
}
}
class Input extends StatefulWidget {
const Input({
Key key,
@required this.controller,
this.focusNode,
this.placeholder,
this.obscureText = false,
}) : super(key: key);
final TextEditingController controller;
final FocusNode focusNode;
final Widget placeholder;
final bool obscureText;
@override
InputState createState() {
return InputState();
}
}
class InputState extends State<Input> {
FocusNode focusNode;
void handleChange() {
setState(() {});
}
@override
void initState() {
super.initState();
focusNode = widget.focusNode ?? FocusNode();
focusNode.addListener(handleChange);
if (widget.placeholder != null) {
widget.controller.addListener(handleChange);
}
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (widget.focusNode == null) {
FocusHandler.of(context).add(focusNode);
}
}
@override
void didUpdateWidget(Input oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.focusNode == null && oldWidget.focusNode != null) {
focusNode.removeListener(handleChange);
focusNode = FocusNode();
FocusHandler.of(context).add(focusNode);
focusNode.addListener(handleChange);
} else if (widget.focusNode != null && oldWidget.focusNode == null) {
focusNode.removeListener(handleChange);
FocusHandler.of(context).remove(focusNode);
focusNode.dispose();
focusNode = widget.focusNode;
focusNode.addListener(handleChange);
}
if (widget.placeholder == null && oldWidget.placeholder != null) {
widget.controller.removeListener(handleChange);
} else if (widget.placeholder != null && oldWidget.placeholder == null) {
widget.controller.addListener(handleChange);
}
}
@override
void dispose() {
if (widget.placeholder != null) {
widget.controller.removeListener(handleChange);
}
focusNode.removeListener(handleChange);
if (widget.focusNode == null) {
FocusHandler.of(context).remove(focusNode);
focusNode.dispose();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
Widget result = EditableText(
backgroundCursorColor: const Color(0xFF001030),
controller: widget.controller,
cursorColor: const Color(0xFF001030),
focusNode: focusNode,
obscureText: widget.obscureText,
style: DefaultTextStyle.of(context).style,
);
if (widget.placeholder != null) {
result = Stack(
children: <Widget>[
Opacity(
child: widget.placeholder,
opacity: widget.controller.text.isNotEmpty || focusNode.hasFocus ? 0.0 : 1.0,
),
result,
],
);
}
return DecoratedBox(
child: Padding(
child: result,
padding: EdgeInsets.all(16.0 * 1.0),
),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(16.0 * 0.5)),
color: Color(0xFFFFFFFF),
),
);
}
}
class Button extends StatefulWidget {
const Button({Key key, @required this.child, this.focusNode, this.onTap}) : super(key: key);
final Widget child;
final FocusNode focusNode;
final VoidCallback onTap;
@override
ButtonState createState() {
return ButtonState();
}
}
class ButtonState extends State<Button> {
FocusNode focusNode;
bool mouseIn;
VoidCallback markNeedsBuild;
@override
void initState() {
super.initState();
markNeedsBuild = (context as StatefulElement).markNeedsBuild;
focusNode = widget.focusNode ?? FocusNode();
focusNode.addListener(markNeedsBuild);
mouseIn = false;
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (widget.focusNode == null) {
FocusHandler.of(context).add(focusNode);
}
}
@override
void didUpdateWidget(Button oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.focusNode == null && oldWidget.focusNode != null) {
focusNode.removeListener(markNeedsBuild);
focusNode = FocusNode();
FocusHandler.of(context).add(focusNode);
focusNode.addListener(markNeedsBuild);
} else if (widget.focusNode != null && oldWidget.focusNode == null) {
focusNode.removeListener(markNeedsBuild);
FocusHandler.of(context).remove(focusNode);
focusNode.dispose();
focusNode = widget.focusNode;
focusNode.addListener(markNeedsBuild);
}
}
@override
void dispose() {
focusNode.removeListener(markNeedsBuild);
if (widget.focusNode == null) {
FocusHandler.of(context).remove(focusNode);
focusNode.dispose();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
Color color = Color(0xFF3699FF);
Color focusColor = Color(0xFF1477DD);
return Focus(
child: GestureDetector(
child: MouseRegion(
child: DecoratedBox(
child: Padding(
child: widget.child,
padding: EdgeInsets.symmetric(
horizontal: 16.0 * 1.0,
vertical: 16.0 * 0.65,
),
),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(16.0 * 0.42)),
color: widget.onTap == null ? color.withOpacity(0.6) : mouseIn || focusNode.hasFocus ? focusColor : color,
),
),
cursor: widget.onTap == null ? SystemMouseCursors.basic : SystemMouseCursors.click,
onEnter: (PointerEnterEvent event) {
mouseIn = true;
markNeedsBuild();
},
onExit: (PointerExitEvent event) {
mouseIn = false;
markNeedsBuild();
},
),
onTap: () {
if (widget.onTap != null) {
widget.onTap();
FocusHandler.of(context).unfocus(focusNode);
}
},
),
focusNode: focusNode,
onFocusChange: (bool hasFocus) {
markNeedsBuild();
},
onKey: (FocusNode focusNode, RawKeyEvent event) {
if (event.isKeyPressed(LogicalKeyboardKey.enter) || event.isKeyPressed(LogicalKeyboardKey.space)) {
if (widget.onTap != null) {
widget.onTap();
}
return true;
}
return false;
},
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment