Skip to content

Instantly share code, notes, and snippets.

@callmephil
Created May 8, 2025 15:27
Show Gist options
  • Save callmephil/28ed2aa12dcbbc8c32af44494386ae40 to your computer and use it in GitHub Desktop.
Save callmephil/28ed2aa12dcbbc8c32af44494386ae40 to your computer and use it in GitHub Desktop.
card overlay
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
/// Global Chat Overlay Manager
class ChatOverlayService {
static final navigatorKey = GlobalKey<NavigatorState>();
static final _chatKey = GlobalKey<_ChatContainerState>();
static final chatContainer = ChatContainer(key: _chatKey);
static void toggle() => _chatKey.currentState?.toggle();
static void show() => _chatKey.currentState?.show();
static void hide() => _chatKey.currentState?.hide();
}
/// The app entry point
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: ChatOverlayService.navigatorKey,
builder: (context, child) {
return Stack(children: [child!, ChatOverlayService.chatContainer]);
},
home: const HomeScreen(),
);
}
}
/// Sample home screen
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Global Chat Overlay")),
body: const Center(child: Text("Main screen")),
floatingActionButton: FloatingActionButton(
onPressed: ChatOverlayService.toggle,
child: const Icon(Icons.chat),
),
);
}
}
/// Reusable floating container
class ChatContainer extends StatefulWidget {
const ChatContainer({super.key});
@override
State<ChatContainer> createState() => _ChatContainerState();
}
class _ChatContainerState extends State<ChatContainer>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
late final Animation<Offset> _offsetAnimation;
bool _visible = false;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 250),
);
_offsetAnimation = Tween<Offset>(
begin: const Offset(0, 1),
end: Offset.zero,
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.linear,
));
}
void show() {
if (!_visible) {
setState(() => _visible = true);
_controller.forward();
}
}
void hide() async {
await _controller.reverse();
setState(() => _visible = false);
}
void toggle() => _visible ? hide() : show();
@override
Widget build(BuildContext context) {
if (!_visible) return const SizedBox.shrink();
return Positioned(
bottom: 20,
left: 20,
child: Material(
type: MaterialType.transparency,
child: SlideTransition(
position: _offsetAnimation,
child: ChatPopup(onClose: hide),
),
),
);
}
}
/// The chat popup box
class ChatPopup extends StatelessWidget {
final VoidCallback onClose;
const ChatPopup({super.key, required this.onClose});
@override
Widget build(BuildContext context) {
return AnimatedContainer(
duration: Durations.medium1,
width: 300,
height: 400,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
// boxShadow: const [BoxShadow(blurRadius: 10, color: Colors.black26)],
),
child: Column(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: const BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Chat',
style: TextStyle(color: Colors.white, fontSize: 18),
),
IconButton(
icon: const Icon(Icons.close, color: Colors.white),
onPressed: onClose,
),
],
),
),
const Expanded(child: Center(child: Text('Chat messages here'))),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
decoration: InputDecoration(
hintText: "Type a message...",
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
],
),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment