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);
}
}

View File

@@ -1,125 +1,24 @@
import 'package:flutter/material.dart';
import 'nav_layout.dart';
void main() {
runApp(const MyApp());
runApp(const SCNApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
class SCNApp extends StatelessWidget {
const SCNApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
title: 'SimpleCloudNotifier',
theme: ThemeData(
// This is the theme of your application.
//
// TRY THIS: Try running your application with "flutter run". You'll see
// the application has a purple toolbar. Then, without quitting the app,
// try changing the seedColor in the colorScheme below to Colors.green
// and then invoke "hot reload" (save your changes or press the "hot
// reload" button in a Flutter-supported IDE, or press "r" if you used
// the command line to start the app).
//
// Notice that the counter didn't reset back to zero; the application
// state is not lost during the reload. To reset the state, use hot
// restart instead.
//
// This works for code too, not just values: Most code changes can be
// tested with just a hot reload.
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// TRY THIS: Try changing the color here to a specific color (to
// Colors.amber, perhaps?) and trigger a hot reload to see the AppBar
// change color while the other colors stay the same.
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
// Column is also a layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
//
// TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint"
// action in the IDE, or press "p" in the console), to see the
// wireframe for each widget.
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
home: const SCNNavLayout(),
);
}
}

109
flutter/lib/nav_layout.dart Normal file
View File

@@ -0,0 +1,109 @@
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'bottom_fab/fab_bottom_app_bar.dart';
import 'pages/message_list/message_list.dart';
class SCNNavLayout extends StatefulWidget {
const SCNNavLayout({super.key});
@override
State<SCNNavLayout> createState() => _SCNNavLayoutState();
}
class _SCNNavLayoutState extends State<SCNNavLayout> {
int _selectedIndex = 0;
static const TextStyle optionStyle = TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
static const List<Widget> _subPages = <Widget>[
MessageListPage(title: 'Messages 1'),
MessageListPage(title: 'Messages 2'),
MessageListPage(title: 'Messages 3'),
MessageListPage(title: 'Messages 4'),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
void _onFABTapped(int index) {
setState(() {
//TODO
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: _buildAppBar(context),
body: Center(
child: _subPages.elementAt(_selectedIndex),
),
bottomNavigationBar: _buildNavBar(context),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButton: _buildFAB(context),
);
}
Widget _buildFAB(BuildContext context) {
return FloatingActionButton(
onPressed: () {},
tooltip: 'Increment',
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(17))),
elevation: 2.0,
child: const Icon(FontAwesomeIcons.solidPaperPlaneTop),
);
}
Widget _buildNavBar(BuildContext context) {
return FABBottomAppBar(
onTabSelected: _onItemTapped,
color: Colors.grey,
selectedColor: Colors.black,
notchedShape: const AutomaticNotchedShape(
RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(15),
topRight: Radius.circular(15),
),
),
RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(17)),
),
),
items: [
FABBottomAppBarItem(iconData: FontAwesomeIcons.solidEnvelope, text: 'Messages'),
FABBottomAppBarItem(iconData: FontAwesomeIcons.solidSnake, text: 'Channels'),
FABBottomAppBarItem(iconData: FontAwesomeIcons.solidFileUser, text: 'Account'),
FABBottomAppBarItem(iconData: FontAwesomeIcons.solidGear, text: 'Settings'),
],
);
}
AppBar _buildAppBar(BuildContext context) {
return AppBar(
title: const Text('Simple Cloud Notifier 2.0'),
actions: <Widget>[
IconButton(
icon: const Icon(FontAwesomeIcons.solidSpiderBlackWidow),
tooltip: 'Debug',
onPressed: () {},
),
IconButton(
icon: const Icon(FontAwesomeIcons.solidMagnifyingGlass),
tooltip: 'Search',
onPressed: () {},
),
IconButton(
icon: const Icon(FontAwesomeIcons.solidQrcode),
tooltip: 'Show Account QR Code',
onPressed: () {},
),
],
backgroundColor: Theme.of(context).secondaryHeaderColor,
);
}
}

View File

@@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
class MessageListPage extends StatelessWidget {
final String title;
const MessageListPage({super.key, required this.title});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text(
title,
style: const TextStyle(fontSize: 24),
),
),
);
}
}