Skip to content

Instantly share code, notes, and snippets.

@slightfoot
Created November 15, 2023 19:28
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save slightfoot/f85b68042465febe95dc69d23f86ad8a to your computer and use it in GitHub Desktop.
Save slightfoot/f85b68042465febe95dc69d23f86ad8a to your computer and use it in GitHub Desktop.
Humpday Q&A - by Simon Lightfoot - 15/11/2023 - https://www.youtube.com/watch?v=1JuZCCbJma8
// MIT License
//
// Copyright (c) 2023 Simon Lightfoot
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
// Boolean 04:47 PM
// Q: I have an appbar at the root of the app in a
// scaffold (one per bottom tab) How can I change the
// AppBar title depending on the current page
// (with data) being shown in the body?
//
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData.dark(),
home: const Home(),
),
);
}
final systemBarLight = SystemUiOverlayStyle.light.copyWith(
statusBarColor: Colors.transparent,
systemNavigationBarColor: Colors.transparent,
);
final systemBarDark = SystemUiOverlayStyle.dark.copyWith(
statusBarColor: Colors.transparent,
systemNavigationBarColor: Colors.transparent,
);
mixin PageMixin<T extends StatefulWidget> on State<T> {
void pageActivated();
void pageDeactivated();
}
class Home extends StatefulWidget {
const Home({super.key});
static void setTitle(BuildContext context, String title) {
final state = context.findAncestorStateOfType<_HomeState>()!;
state.setTitle(title);
}
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
final _pageKeys = <GlobalKey<PageMixin>>[];
String _title = '';
int _index = -1;
@override
void initState() {
super.initState();
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
_pageKeys.add(GlobalKey<PageMixin>(debugLabel: 'Page 1'));
_pageKeys.add(GlobalKey<PageMixin>(debugLabel: 'Page 2'));
_pageKeys.add(GlobalKey<PageMixin>(debugLabel: 'Page 3'));
setPage(0);
}
void setPage(int index) {
scheduleMicrotask(() {
if (_index != -1) {
_pageKeys[_index].currentState?.pageDeactivated();
}
setState(() => _index = index);
_pageKeys[index].currentState?.pageActivated();
});
}
void setTitle(String title) {
scheduleMicrotask(() {
setState(() => _title = title);
});
}
@override
Widget build(BuildContext context) {
return AnnotatedRegion(
value: systemBarLight,
child: Material(
child: Placeholder(
child: Scaffold(
appBar: AppBar(
title: Text(_title),
),
backgroundColor: Colors.transparent,
resizeToAvoidBottomInset: false,
body: PageLayout(
bottom: BottomBar(
activeIndex: _index,
onPressed: setPage,
labels: const ['First', 'Second', 'Third'],
),
child: IndexedStack(
index: _index < 0 ? 0 : _index,
children: [
PageFirst(key: _pageKeys[0]),
PageSecond(key: _pageKeys[1]),
PageThird(key: _pageKeys[2]),
],
),
),
),
),
),
);
}
}
class PageLayout extends StatelessWidget {
const PageLayout({
super.key,
required this.child,
this.bottom,
});
final Widget child;
final Widget? bottom;
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
return Padding(
padding: MediaQuery.viewInsetsOf(context),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: SingleChildScrollView(
padding: bottom == null
? MediaQuery.viewPaddingOf(context)
: EdgeInsets.zero,
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight(
child: child,
),
),
),
),
if (bottom != null) //
bottom!,
],
),
);
},
);
}
}
class BottomBar extends StatelessWidget {
const BottomBar({
super.key,
required this.activeIndex,
required this.onPressed,
required this.labels,
});
final int activeIndex;
final ValueChanged<int> onPressed;
final List<String> labels;
@override
Widget build(BuildContext context) {
return Material(
color: Colors.deepOrange.shade900,
child: Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.viewPaddingOf(context).bottom,
),
child: UnconstrainedBox(
constrainedAxis: Axis.horizontal,
child: IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
for (int i = 0; i < labels.length; i++) //
Expanded(
child: Ink(
decoration: BoxDecoration(
color: i == activeIndex
? Colors.orangeAccent.shade700.withOpacity(0.6)
: null,
),
child: InkWell(
onTap: () => onPressed(i),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Center(
child: Text(
labels[i],
style: TextStyle(
fontSize: i == 1 ? 24.0 : 20.0,
),
textAlign: TextAlign.center,
),
),
),
),
),
),
],
),
),
),
),
);
}
}
class PageFirst extends StatefulWidget {
const PageFirst({super.key});
@override
State<PageFirst> createState() => _PageFirstState();
}
class _PageFirstState extends State<PageFirst> with PageMixin {
@override
void initState() {
super.initState();
}
@override
void pageActivated() {
print('page 1 activated');
Home.setTitle(context, 'First Page');
}
@override
void pageDeactivated() {
print('page 1 deactivated');
}
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 24.0),
SizedBox(
height: 72.0,
child: AnnotatedRegion(
value: systemBarDark,
child: const ColoredBox(
color: Colors.white,
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
for (int i = 0; i < 30; i++)
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
decoration: InputDecoration(
filled: true,
fillColor: Colors.grey.withOpacity(0.5),
),
textInputAction: TextInputAction.next,
),
),
],
),
),
),
],
);
}
}
class PageSecond extends StatefulWidget {
const PageSecond({super.key});
@override
State<PageSecond> createState() => _PageSecondState();
}
class _PageSecondState extends State<PageSecond>
with PageMixin, SingleTickerProviderStateMixin {
late final AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 4),
vsync: this,
);
_controller.addStatusListener((status) {
if (_controller.status == AnimationStatus.dismissed) {
_controller.forward();
} else if (_controller.status == AnimationStatus.completed) {
_controller.reverse();
}
});
}
@override
void pageActivated() {
print('page 2 activated');
Home.setTitle(context, 'Second Page');
switch (_controller.status) {
case AnimationStatus.dismissed:
case AnimationStatus.forward:
_controller.forward();
case AnimationStatus.completed:
case AnimationStatus.reverse:
_controller.reverse();
}
}
@override
void pageDeactivated() {
_controller.stop();
print('page 2 deactivated');
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Material(
color: Colors.black,
child: SizedBox(
height: 660.0,
child: Placeholder(
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Align(
alignment: Alignment(
0.0,
(_controller.value - 0.5) * 2,
),
child: child,
);
},
child: const DecoratedBox(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.red,
),
child: SizedBox.square(dimension: 36.0),
),
),
),
),
);
}
}
class PageThird extends StatefulWidget {
const PageThird({super.key});
@override
State<PageThird> createState() => _PageThirdState();
}
class _PageThirdState extends State<PageThird> with PageMixin {
@override
void pageActivated() {
print('page 3 activated');
Home.setTitle(context, 'Third Page');
}
@override
void pageDeactivated() {
print('page 3 deactivated');
}
@override
Widget build(BuildContext context) {
return Material(
color: Colors.green.shade900,
child: const Placeholder(color: Colors.white70),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment