Implement message_list

This commit is contained in:
2024-05-21 23:20:34 +02:00
parent 20358a700a
commit 34925b6678
14 changed files with 334 additions and 60 deletions

View File

@@ -0,0 +1,115 @@
import 'package:flutter/material.dart';
class AnchoredOverlay extends StatelessWidget {
final bool showOverlay;
final Widget Function(BuildContext, Offset anchor) overlayBuilder;
final Widget child;
const AnchoredOverlay({
super.key,
required this.showOverlay,
required this.overlayBuilder,
required this.child,
});
@override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
return OverlayBuilder(
showOverlay: showOverlay,
overlayBuilder: (BuildContext overlayContext) {
RenderBox box = context.findRenderObject() as RenderBox;
final center = box.size.center(box.localToGlobal(const Offset(0.0, 0.0)));
return overlayBuilder(overlayContext, center);
},
child: child,
);
});
}
}
class OverlayBuilder extends StatefulWidget {
final bool showOverlay;
final Widget Function(BuildContext) overlayBuilder;
final Widget child;
const OverlayBuilder({
super.key,
this.showOverlay = false,
required this.overlayBuilder,
required this.child,
});
@override
State<OverlayBuilder> createState() => _OverlayBuilderState();
}
class _OverlayBuilderState extends State<OverlayBuilder> {
OverlayEntry? overlayEntry;
@override
void initState() {
super.initState();
if (widget.showOverlay) {
WidgetsBinding.instance.addPostFrameCallback((_) => showOverlay());
}
}
@override
void didUpdateWidget(OverlayBuilder oldWidget) {
super.didUpdateWidget(oldWidget);
WidgetsBinding.instance.addPostFrameCallback((_) => syncWidgetAndOverlay());
}
@override
void reassemble() {
super.reassemble();
WidgetsBinding.instance.addPostFrameCallback((_) => syncWidgetAndOverlay());
}
@override
void dispose() {
if (isShowingOverlay()) {
hideOverlay();
}
super.dispose();
}
bool isShowingOverlay() => overlayEntry != null;
void showOverlay() {
overlayEntry = OverlayEntry(
builder: widget.overlayBuilder,
);
addToOverlay(overlayEntry!);
}
void addToOverlay(OverlayEntry entry) async {
print('addToOverlay');
Overlay.of(context).insert(entry);
}
void hideOverlay() {
print('hideOverlay');
if (overlayEntry != null) {
overlayEntry!.remove();
overlayEntry = null;
}
}
void syncWidgetAndOverlay() {
if (isShowingOverlay() && !widget.showOverlay) {
hideOverlay();
} else if (!isShowingOverlay() && widget.showOverlay) {
showOverlay();
}
}
@override
Widget build(BuildContext context) {
return widget.child;
}
}

View File

@@ -0,0 +1,20 @@
import 'package:flutter/material.dart';
class CenterAbout extends StatelessWidget {
final Offset position;
final Widget child;
const CenterAbout({super.key, required this.position, required this.child});
@override
Widget build(BuildContext context) {
return Positioned(
top: position.dy,
left: position.dx,
child: FractionalTranslation(
translation: const Offset(-0.5, -0.5),
child: child,
),
);
}
}

View File

@@ -0,0 +1,107 @@
import 'package:flutter/material.dart';
class FABBottomAppBarItem {
FABBottomAppBarItem({required this.iconData, required this.text});
IconData iconData;
String text;
}
class FABBottomAppBar extends StatelessWidget {
FABBottomAppBar({
super.key,
required this.items,
this.centerItemText,
this.height = 60.0,
this.iconSize = 24.0,
this.backgroundColor,
this.color,
this.selectedColor,
this.notchedShape,
this.onTabSelected,
this.selectedIndex = 0,
}) {
assert(items.length == 2 || items.length == 4);
}
final List<FABBottomAppBarItem> items;
final String? centerItemText;
final double height;
final double iconSize;
final Color? backgroundColor;
final Color? color;
final Color? selectedColor;
final NotchedShape? notchedShape;
final ValueChanged<int>? onTabSelected;
final int selectedIndex;
@override
Widget build(BuildContext context) {
List<Widget> items = List.generate(this.items.length, (int index) {
return _buildTabItem(
item: this.items[index],
index: index,
onPressed: _updateIndex,
);
});
items.insert(items.length >> 1, _buildMiddleTabItem());
return BottomAppBar(
shape: notchedShape,
color: backgroundColor,
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: items,
),
);
}
Widget _buildMiddleTabItem() {
return Expanded(
child: SizedBox(
height: height,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SizedBox(height: iconSize),
Text(
centerItemText ?? '',
style: TextStyle(color: color),
),
],
),
),
);
}
Widget _buildTabItem({required FABBottomAppBarItem item, required int index, required ValueChanged<int> onPressed}) {
Color color = (selectedIndex == index ? selectedColor : this.color) ?? Colors.black;
return Expanded(
child: SizedBox(
height: height,
child: Material(
type: MaterialType.transparency,
child: InkWell(
onTap: () => onPressed(index),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(item.iconData, color: color, size: iconSize),
Text(
item.text,
style: TextStyle(color: color),
)
],
),
),
),
),
);
}
void _updateIndex(int index) {
if (onTabSelected != null) onTabSelected!(index);
}
}

View File

@@ -0,0 +1,81 @@
import 'package:flutter/material.dart';
// https://stackoverflow.com/questions/46480221/flutter-floating-action-button-with-speed-dail
class FabWithIcons extends StatefulWidget {
FabWithIcons({super.key, required this.icons, required this.onIconTapped});
final List<IconData> icons;
final ValueChanged<int> onIconTapped;
@override
State createState() => FabWithIconsState();
}
class FabWithIconsState extends State<FabWithIcons> with TickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 250),
);
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: List.generate(widget.icons.length, (int index) {
return _buildChild(index);
}).toList()
..add(
_buildFab(),
),
);
}
Widget _buildChild(int index) {
Color backgroundColor = Theme.of(context).cardColor;
Color foregroundColor = Theme.of(context).secondaryHeaderColor;
return Container(
height: 70.0,
width: 56.0,
alignment: FractionalOffset.topCenter,
child: ScaleTransition(
scale: CurvedAnimation(
parent: _controller,
curve: Interval(0.0, 1.0 - index / widget.icons.length / 2.0, curve: Curves.easeOut),
),
child: FloatingActionButton(
backgroundColor: backgroundColor,
mini: true,
child: Icon(widget.icons[index], color: foregroundColor),
onPressed: () => _onTapped(index),
),
),
);
}
Widget _buildFab() {
return FloatingActionButton(
onPressed: () {
if (_controller.isDismissed) {
_controller.forward();
} else {
_controller.reverse();
}
},
tooltip: 'Increment',
elevation: 2.0,
child: const Icon(Icons.add),
);
}
void _onTapped(int index) {
_controller.reverse();
widget.onIconTapped(index);
}
}