basic setup (layout + icons)

This commit is contained in:
2024-02-10 19:57:17 +01:00
parent 7e798eb606
commit 306d9a006a
100 changed files with 490654 additions and 110 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,115 @@
import 'package:flutter/material.dart';
class FABBottomAppBarItem {
FABBottomAppBarItem({required this.iconData, required this.text});
IconData iconData;
String text;
}
class FABBottomAppBar extends StatefulWidget {
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,
}) {
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;
@override
State<StatefulWidget> createState() => FABBottomAppBarState();
}
class FABBottomAppBarState extends State<FABBottomAppBar> {
int _selectedIndex = 0;
_updateIndex(int index) {
if (widget.onTabSelected != null) widget.onTabSelected!(index);
setState(() {
_selectedIndex = index;
});
}
@override
Widget build(BuildContext context) {
List<Widget> items = List.generate(widget.items.length, (int index) {
return _buildTabItem(
item: widget.items[index],
index: index,
onPressed: _updateIndex,
);
});
items.insert(items.length >> 1, _buildMiddleTabItem());
return BottomAppBar(
shape: widget.notchedShape,
color: widget.backgroundColor,
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: items,
),
);
}
Widget _buildMiddleTabItem() {
return Expanded(
child: SizedBox(
height: widget.height,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SizedBox(height: widget.iconSize),
Text(
widget.centerItemText ?? '',
style: TextStyle(color: widget.color),
),
],
),
),
);
}
Widget _buildTabItem({required FABBottomAppBarItem item, required int index, required ValueChanged<int> onPressed}) {
Color color = (_selectedIndex == index ? widget.selectedColor : widget.color) ?? Colors.black;
return Expanded(
child: SizedBox(
height: widget.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: widget.iconSize),
Text(
item.text,
style: TextStyle(color: color),
)
],
),
),
),
),
);
}
}

View File

@@ -0,0 +1,80 @@
import 'package:flutter/material.dart';
// https://stackoverflow.com/questions/46480221/flutter-floating-action-button-with-speed-dail
class FabWithIcons extends StatefulWidget {
FabWithIcons({required this.icons, required this.onIconTapped});
final List<IconData> icons;
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',
child: Icon(Icons.add),
elevation: 2.0,
);
}
void _onTapped(int index) {
_controller.reverse();
widget.onIconTapped(index);
}
}